객체와 싱글톤(object)객체 표현식(object expression)으로 사용하는 익명 객체supertype으로부터 익명객체를 상속하기객체 선언(object declartion)을 이용한 싱글톤탑레벨 함수 vs 싱글톤Object declartion 에서 상속클래스 생성필드와 속성lateinit vs lazy속성 제어 변경 (setter 커스텀 정의)접근 제어자초기화 블럭주 생성자보조 생성자주 생성자에게 위임컴패니언 객체와 클래스 멤버클래스 레벨 멤버컴패니언에 접근하기팩토리로 사용하는 컴패니언static 과는 다르다제네릭 클래스 생성데이터 클래스
객체와 싱글톤(object)
- 개발자가 필요하다면 클래스 정의 없이 객체를 생성할 수 있다. 간단한 상황이면 객체를 바로 사용할 수 있고, 추상클래스를 정의해야하는 복잡한 상황인 경우엔 클래스를 정의한 후 만들어서 사용할 수 있다.
객체 표현식(object expression)으로 사용하는 익명 객체
fun drawCircle() { val circle = object { val x = 10 val y = 20 val radius = 30 } println("Circle x: ${circle.x} y: ${circle.y} radius : ${circle.radius}") }
- 대부분의 객체 표현식은 지역변수들을 그룹핑 할 때만 유용하다
supertype으로부터 익명객체를 상속하기


Object declarations and expressions | Kotlin
A singleton ensures that a class has only one instance and provides a global point of access to it.
- 작은 변화만 주면
익명 객체는 인터페이스의 구현체가 된다
fun createRunnable() : Runnable { val runnable = object : Runnable { override fun run() { println("You called...") } } return runnable }
- 익명 내부 클래스가 둘 이상의 인터페이스를 구현해야 한다면
object: Runnable, AutoCloseable
과 같이 콤마로 구분하면 됨
fun specialTransaction(account: BankAccount) { // Creates an anonymous object that inherits from the BankAccount class and // implements the Transaction interface // The balance of the provided account is passed to the BankAccount superclass constructor val temporaryAccount = object : BankAccount(account.balance), Transaction { override val balance = account.balance + 500 // Temporary bonus // Implements the execute() function from the Transaction interface override fun execute() { println("Executing special transaction. New balance is $balance.") } } // Executes the transaction temporaryAccount.execute() }
객체 선언(object declartion)을 이용한 싱글톤
object Util { fun numberOfProcessors() = Runtime.getRuntime().availableProcessors() }
- object 키워드와 {} 블록 사이에 이름을 넣는다면, 코틀린은 이를 표현식이 아니라 명령문 또는 선언 으로 인식하고 싱글톤 객체를 만들어 준다
- 위의 코드를 입력함으로써 Util은 이미 객체인 상태이고 클래스로 취급되지 않음. Java의 private 생성자와 static 메서드만 가지고 있는 클래스라 생각하면 됨
- 싱글톤은 메서드만 가질 수 있는게 아니라 val과 var 로 선언된 속성 모두 가질 수 있음
object Sun : Runnable { val radiusInKM = 696000 var coreTemperatureInC = 15000000 override fun run() { println("spin...") } } fun moveIt(runnable: Runnable) { runnable.run() } println(Sun.radiusInKM) moveIt(Sun)
- 싱글톤에 뮤터블한 상태를 갖는 것은 멀티스레드 애플리케이션에서 문제를 유발할 수 있다.
탑레벨 함수 vs 싱글톤
- 사용할 함수들이 하이레벨이거나 일반적이거나 넓게 사용될 예정이라면 패키지 안에 직접 넣어서 탑레벨 함수로 사용하는게 좋다.
- 반대로 함수들이 서로 연관되어 있다면 싱글톤을 사용하는 게 좋다.
- 만약 함수들이 상태와 연관되어 있다면 싱글톤이 좋지만, 클래스가 더 좋은 선택일 가능성이 크다.
프로그램의 행동, 계산, 작동에 집중할 때 함수와 싱글톤이 적절. 하지만 상태를 다뤄야 한다면 클래스를 사용하는 게 더 좋은 선택
Object declartion 에서 상속
object DefaultListener : MouseAdapter() { override fun mouseClicked(e: MouseEvent) { ... } override fun mouseEntered(e: MouseEvent) { ... } }
- object declartion을 이용하면 변수에 할당을 할 수 없고 → local 일수 없고 함수내에서 nested로 사용이 불가능한 것(object expression 과 달리)
클래스 생성
class Car(val yearOfMake: Int) val car = Car(2019) println(car.yearOfMake) // 2019 car.yearOfMake = 2019 // ERROR: val cannot be reassigned // 읽기-쓰기 전용 속성 class Car(val yearOfMake: Int, var color: String)
- class 생성 시, val이나 var 로 정의하면 속성이 되고, 그냥 명시하면 생성자의 파라미터로만 이용이 됨
필드와 속성
Java에서는 위의 yearOfMake와 color가 속성보다는 필드에 가까움
그러나 이것들은 모두 필드가 아니고 속성이다. 코틀린에서는 클래스에 필드가 없다.
(필드와는 달리 프로퍼티는 get/set 접근자를 통해 멤버 변수에 접근하게 함)
fun useCarObject(): Pair<Int, String> { val car = Car(2019, "Red") val year = car.yearOfMake car.color = "Green" val color = car.color return year to color }
Compiled from "UseCar.kt" public final class UseCarKt { public static final kotlin.Pair<java.lang.Integer, java.lang.String> useCarObject(); Code: 0: new #10 // class Car 3: dup 4: sipush 2019 7: ldc #12 // String Red 9: invokespecial #16 // Method Car."<init>":(ILjava/lang/String;)V 12: astore_0 13: aload_0 14: invokevirtual #20 // Method Car.getYearOfMake:()I 17: istore_1 18: aload_0 19: ldc #22 // String Green 21: invokevirtual #26 // Method Car.setColor:(Ljava/lang/String;)V 24: aload_0 25: invokevirtual #30 // Method Car.getColor:()Ljava/lang/String; 28: astore_2 29: iload_1 30: invokestatic #36 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer; 33: aload_2 34: invokestatic #42 // Method kotlin/TuplesKt.to:(Ljava/lang/Object;Ljava/lang/Object;)Lkotlin/Pair; 37: areturn }
- 위의 코드를 컴파일해서 클래스 파일을 확인해보면 car.yearOfMake 로 접근할 때, getYearOfMake() 함수를 호출해서 값을 가지고 옴. 그리고 필드들은 private으로 선언되어 있음
- 코틀린에서는 getter,setter 대신 속성의 이름을 이용해서 속성에 접근할 수 있음
lateinit vs lazy
fun main() { lateinit var text: String val textLength: Int by lazy { text.length } // 대충 중간에 뭔가 했음 text = "H43RO_Velog" println(textLength) }
- lateinit : var 사용. 초기화 이후에 계속해서 값이 바뀔 수 있을 때 [ Kotlin docs 참고 ]
if (foo:bar.isInitialized) { println(foo.bar) }
.isInitialized
를 참조해보기- by lazy : val 사용. 초기화 이후에 읽기 전용 값으로 사용할 때
- 선언과 초기화가 같이 있음
lazy
프로퍼티 연산은 기본적으로 동기화된다.
속성 제어 변경 (setter 커스텀 정의)
getter와 setter가 자동생성되지만, 아래와 같이 커스텀하게 작성할 수 있음
class Car(val yearOfMake: Int, theColor: String) { var fuelLevel = 100 var color = theColor set(value) { if (value.isBlank()) { throw RuntimeException("no empty, please") } field = value } }
- 여기서 속성은 yearOfMake, fuelLevel, color 3개 속성이 있음
- 스페셜 키워드인
field
를 이용하여 참조되고 있는 필드에 값을 할당할 수 있음
- 파라미터 value는 다른 이름이어도 상관 없음
접근 제어자
코틀린에서 클래스의 속성과 메서드는 public 이 기본
public, private, protected, internal 네 개의 접근 제어자. 처음 두 개는 Java와 동일
protected는 파생(자식)클래스들의 메소드가 속성에 접근할 수 있는 권한
internal은 같은 모듈(함께 컴파일된 모든 소스 코드)에 있는 모든 소스 코드에서 속성이나 메소드에 접근 가능
- getter의 접근 권한은 속성의 접근 권한과 동일
- setter의 접근권한은 개발자가 원하는 대로 설정 가능함
var fuelLevel = 100 private set
초기화 블럭
주 생성자는 첫번째 블록에 나타남. 객체를 초기화하는 코드가 값들을 설정하는 것보다 더 복잡하다면 생성자용 바디를 만들 필요가 있음.
- init 블록은 주 생성자의 실행의 한 부분으로써 실행됨
- init 블록의 코드는 top-down 순으로 순차적으로 실행
- init 블록은 여러 개 정의할 수 있지만 필요할 때만 정의하기
- 클래스 안에서 첫 번째로 속성을 가장 위에 정의, 그 후 init 블록(필요한 경우), 그리고 보조 생성자 구현(필요한 경우)
class InitOrderDemo(name: String) { val firstProperty = "First property: $name".also(::println) init { println("First initializer block that prints $name") } val secondProperty = "Second property: ${name.length}".also(::println) init { println("Second initializer block that prints ${name.length}") } }
주생성자의 파라미터는 initializer block 내부에서 사용될 수 있음. 또한 property를 초기화 하는데도 사용가능함
주 생성자
class Person public constructor(firstName: String) { /*...*/ } // 아래와 같음 class Person(firstName: String) { /*...*/ } // 생성자에 annotatino이나 visibility modifier를 적용하려면 consturctor keyword를 사용해야함 class Customer public @Inject constructor(name: String) { /* ... */ } // private constructor class C private constructor(a:Int) { ... }
보조 생성자
- 주 생성자를 작성하지 않았다면 코틀린은 아규먼트가 없는 기본 생성자를 생성
- 만약 주 생성자가 모든 파라미터를 위한 기본 아규먼트를 갖고 있다면 코틀린은 주 생성자와 함께 아규먼트가 없는 생성자를 생성
- 보조 생성자의 파라미터도 역시 val 이나 var를 사용할 수 없다. 보조 생성자에는 속성 선언할 수 없기에
class Person(val pets: MutableList<Pet> = mutableListOf()) class Pet { constructor(owner: Person) { owner.pets.add(this) // adds this pet to the list of its owner's pets } }
주 생성자에게 위임
// 주생성자가 있다면, 각각의 보조 생성자는 주 생성자에게 위임을 꼭 해야함 class Person(val name: String) { val children: MutableList<Person> = mutableListOf() constructor(name: String, parent: Person) : this(name) { parent.children.add(this) } }
주 생성자가 없다 하더라도 delegation은 암묵적으로 일어나고, 초기화 블럭은 수행이 됨
class Constructors { init { println("Init block") } constructor(i: Int) { println("Constructor $i") } } // Init block // Constructor 1
컴패니언 객체와 클래스 멤버
클래스 레벨 멤버
class MachineOperator(val name: String) { fun checkin() = checkedIn++ fun checkout() = checkedIn-- companion object { var checkedIn = 0 fun minimumBreak() = "15 minutes every 2 hours" } } MachineOperator("Mater").checkin() println(MachineOperator.minimumBreak()) // 15 minutes every 2 hours println(MachineOperator.checkedIn) // 1
컴패니언에 접근하기
val ref = MachineOperator.Companion
그러나 컴패니언 객체에 이름을 지정하면 그것으로 참조가 가능함. 이름지정하지 않을때는
Companion
으로 참조companion object MachineOperatorFactory { var checkedIn = 0 }
팩토리로 사용하는 컴패니언
class MachineOperator private constructor(val name: String) { //... companion object { fun create(name: String): MachineOperator { val instance = MachineOperator(name) instance.checkin() return instance } } }
static 과는 다르다
컴패니언 객체의 멤버에 접근하면 코틀린 컴파일러는 싱글톤 객체로 라우팅을 한다. 그런데 Java와의 상호 운용성 측면에서 봤을 때 이런 동작은 문제를 야기할 수 있다.
코틀린엔 static 메서드가 없다. 가장 가까운 것이 컴패니언 객체의 함수인데, 이를 Java에서 쉽게 사용하기 위해 코틀린 컴파일러에게 이 메소드들을 바이트코드에 static으로 만들라는 지시를 해줘야 함
companion object { @JvmStatic fun create() = Counter(0) }
- 싱글톤이나 컴패니언 객체에 속한 메서드를 만들 때 Java에서 static 메서드로 접근을 쉽게 할 수 있도록 할 필요가 있는지 스스로에게 질문 후 그럴필요가 있을 때만
@JvmStatic
붙이기
제네릭 클래스 생성
class PriorityPair<T: Comparable<T>>(member1: T, member2: T) { val first: T val second: T init { if (member1 >= member2) { first = member1 second = member2 } else { first = member2 second = member1 } } override fun toString() = "${first}, ${second}" }
데이터 클래스
- 특정한 행동, 동작보다는 데이터를 옮기는 데 특화된 클래스임
- 주 생성자에는 val이나 var를 사용한 속성 정의가 적어도 하나 이상 필요
data class Task(val id: Int, val name: String, val completed: Boolean, val assigned: Boolean)
- equals(), hashCode(), toString() 메서드를 자동으로 만들어줌
- component1(), component2() 등의 메서드를 자동으로 만들어주고, 이 메서드들의 주된 목적은 구조분해임
data class Result(val result: Int, val status: Status) fun function(...): Result { // computations return Result(result, status) } // Now, to use this function: val (result, status) = function(...)