코틀린은 모듈로 프로그램을 설계한다.
모듈이란 프로젝트 아래의 개념이다.
클래스, 객체, 함수, 타입 별칭(type alias), 톱레벨 프로퍼티 등 다양한 요소로 구성된다.
모듈의 요소중 일부는 상태(state값)을 포합한다.
요소가 상태를 갖는 경우(var 이라던지.. mutable 객체라던지), 이력(history)에도 의존하게 된다.
val account = BankAccount()
println(account.balance) //0.00
account.addDeposit(100) //100
//히스토리 의존
println(account.balance) //100
account.withdraw(50.0)
println(account.balance) //50
상태는 시간에따라 변화하는 요소를 표현할 수 있다는것은 굉장히 유용하나, 상태를 관리하는것이 꽤 어렵다.
예를 들어 정렬된 리스트에 요소가 추가되었을때, 또 정렬을 해줘야하는점이 있다.
또한 개발자라면 공유 상태 관리가 얼마나 힘든것인지 알것이다.
아래의 부분의 예제는 Mutex와 관련된 예제이다.
//일반 멀티 스레드
fun runMultiThread() {
var num = 0
for (i in 1..1000) {
thread {
Thread.sleep(10)
num += 1
}
}
Thread.sleep(1000)
print(num)
}
한번에 num
이라는값으로 Thread가 동시 접근했을떄 1000이 정확하게 나오지 않는다.
이는 코루틴을 사용해도 마찬가지이다.(물론 쓰레드를 적게쓰기때문에 오차가 줄어들지만..)
fun runMultiThread() {
var num = 0
for (i in 1..1000) {
thread {
Thread.sleep(10)
num += 1
}
}
Thread.sleep(1000)
print(num)
}
결국 이를 해결하려면 synronized lock
을 사용하여 동시접근을 막아야한다.
하지만 한 쓰레드가 접근하면 lock()을 걸기 때문에 속도가 훨씬 느려진다.
공유 객체 문제의 포스팅은 이곳에서 확인하자.(추후 추가)
fun runSyncronizedMultiThread() {
var num = 0
val lock = Any()
for (i in 1..1000) {
thread {
Thread.sleep(10)
synchronized(lock = lock){
num+= 1
}
}
}
Thread.sleep(1000)
print(num)
}
가변성은 생각보다 단점이 많아서 이를 제한하는 프로그래밍도 존재하는데 이를 순수함수형 언어라고 한다(가변성에 제약이 너무나 많이 걸린다.)
가변성은 시스템의 상태를 나타내기 위한 중요한 방법이나, 변경이 일어나야 하는 부분을 신중하고 확실하게 결정하고 사용해야한다.
코틀린은 가변성을 제한할 수 있게 설계 되어있다.
val을 사용해 읽기 전용 프로퍼티를 만들수 있다.
val a = 10
a = 20
하지만 읽기전용 프로퍼티가 mutable 객체를 담고 있다면, 내부적으로 변할 수 있다.
val list = mutableListOf(1,2,3)
list.add(4)
또한 사용자 정의 getter로도 정의가 가능하다.
var name = "Choi"
var surname = "Woo"
val fullName : String
get() = "$name$surname" //Choi woo
surname = "Woo Sung"
fullName = "Choi woo Sung"
val
은 override 할시, var로도 표현이 가능하다.
//var 은 게터와 세터를 모두 제공한다.
//val 은 변경이 불가능하므로 게터만 제공한다.
//val 을 var로 오버라이드 할 수 있다.
interface Element {
var active : Boolean
val nonActive : Boolean
}
class ActualElement : Element {
override var active: Boolean
get() = TODO("Not yet implemented")
set(value) { this.active = value }
override var nonActive: Boolean
get() = TODO("Not yet implemented")
set(value) { this.active = value }
}
중요할것은 val
은 immutable
을 의미하지는 않는다.
만약 완전히 변경할 필요가 없다면 final
프로퍼티를 사용하자.
fullName은 게터로 정의 했으므로 스마트 캐스트를 사용할 수 없다.
fullName2 처럼 지역변수가 아닌 프로퍼티가 final이고, 사용자 정의 게터를 갖지 않을 경우 스마트 캐스트할 수 있다.
class immutableClass(){
val name :String? = "Choi"
val surname = "Woo"
val fullName : String?
get() = "$name$surname"
val fullName2 = name?.let{"$it $surname"}
fun main(){
if(fullName != null){
println(fullName.length) //Error
}
if(fullName2 != null){
println(fullName2.length)
}
}
}