컴파일러가 생성한 메소드: 데이터 클래스와 클래스 위임
- 자바 플랫폼에서는 개발자가 작성한 클래스가 equals, hashCode, toString 등의 메소드를 오버라이드해야하는 경우가 있고,
이럴 경우 코드는 번잡해지기 마련이다.
- 코틀린 컴파일러는 이런 메소드를 기계적으로 생성하는 작업을 보이지 않는 곳에서 해준다.
모든 클래스가 정의해야 하는 메소드
- 자바와 마찬가지로 코틀린도 toString, equals, hashCode 등을 오버라이드 할 수 있다.
문자열 표현: toString()
- 기본 제공되는 객체의 문자열 표현은 Client@5e0f29a2 과 같은 방식인데, toString 메소드를 오버라이드 함으로써 디버깅과 로깅시 유용할 수 있다.
class Client(val name: String, val postalCode: Int) {
override fun toString() = "Client(name=$name, postalCode=$postalCode)"
}
fun main(args: Array<String>) {
val client = Client("hi", 123)
println(client) // Client(name=hi, postalCode=123)
}
객체의 동등성: equals()
- 서로 다른 두 객체가 내부에 동일한 데이터를 포함하는 경우 그 둘을 동등한 객체로 간주해야 할 수도 있다.
class Client(val name: String, val postalCode: Int) {
override fun toString() = "Client(name=$name, postalCode=$postalCode)"
}
val client1 = Client("hi", 123)
val client2 = Client("hi", 123)
println(client1 == client2) // false
- 위의 요구사항을 만족하려면 equals를 오버라이드해야 한다.
- 자바와 달리 코틀린에서는 == 연산자로 객체의 동등성을 검사한다.
- 즉, == 연산은 코틀린에서 equals를 호출하는 식으로 컴파일 된다.
- 자바의 ==와 같이 참조 비교를 위해서는 === 연산자를 사용해야 한다.
- 아래에서 equals 를 오버라이드해보자.
class Client(val name: String, val postalCode: Int) {
override fun toString() = "Client(name=$name, postalCode=$postalCode)"
override fun equals(other: Any?): Boolean {
if (other == null || other !is Client) return false
return name == other.name && postalCode == other.postalCode
}
}
fun main(args: Array<String>) {
val client1 = Client("hi", 123)
val client2 = Client("hi", 123)
println(client1 == client2) // true
}
- Any는 자바의 Object와 대응하는 클래스로, 코틀린의 모든 클래스의 최상위 클래스다.
해시 컨테이너: hashCode()
- JVM 언어에서는 equals()가 true를 반환하는 두 객체는 반드시 같은 hashCode()를 반환해야 한다.
- 실제로 HashSet은 객체 비교의 비용을 줄이기 위해서 우선 hashCode를 비교하고, 같은 경우 equals() 메소드로 실제 값을 비교한다.
- 이 문제를 고치려면 hashCode를 오버라이드해야 한다.
class Client(val name: String, val postalCode: Int) {
override fun toString() = "Client(name=$name, postalCode=$postalCode)"
override fun equals(other: Any?): Boolean {
if (other == null || other !is Client) return false
return name == other.name && postalCode == other.postalCode
}
override fun hashCode() = name.hashCode() * 31 + postalCode
}
- 코틀린은 이와 같이 오버라이드한 메소드들을 자동으로 생성해줄 수 있다.
데이터 클래스: 모든 클래스가 정의해야 하는 메소드 자동 생성
- 어떤 클래스가 데이터를 저장하는 역할만을 수행한다면 toString, equals, hashCode를 반드시 오버라이드해야 한다.
- 코틀린은 이런 메소드를 IDE를 통해 생성할 필요도 없이, data라는 변경자를 클래스 앞에 붙이면 필요한 메소드를 컴파일러가 자동으로 만들어준다.
- data 변경자가 붙은 클래스를 데이터 클래스라고 부른다.
data class Client(val name:String, val postalCode : Int)
- 이제 Client 클래스는 equals, hashCode, toString 메소드를 지금까지 위에서 작성했던 방식대로 개발자의 의도에 맞게 자동으로 만들어준다.
데이터 클래스와 불변성: copy() 메소드
- 코틀린 컴파일러는 이외에도 data 클래스에게 copy 메소드를 제공하는데 copy 메소드를 사용하면 불변 객체를 복사하면서 일부 프로퍼티를 바꿀 수 있게 해준다.
- 복사본은 원본과 다른 생명주기를 가지며, 복사를 하면서 일부 프로퍼티 값을 바꾸거나 복사본을 제거해도 원본을 참조하는 다른 부분에 전혀 영향을 끼치지 않는다.
data class Client(val name:String, val postalCode : Int)
fun main(args: Array<String>) {
val client = Client("hi", 123)
val copyClient = client.copy()
println(copyClient) // Client(name=hi, postalCode=123)
}
클래스 위임: by 키워드 사용
object 키워드: 클래스 선언과 인스턴스 생성
- 코틀린에서는 object 키워드를 통해 클래스를 정의하면서 동시에 인스턴스(객체)를 생성할 수 있다.
- object 키워드를 사용하는 여러 상황을 살펴보자.
- 객체 선언은 싱글턴을 정의하는 방법 중 하나다.
- 동반 객체는 인스턴스 메소드는 아니지만 어떤 클래스와 관련 있는 메소드와 팩토리 메소드를 담을 떄 쓰인다.
- 객체 식은 자바의 무명 내부 클래스 대신 쓰인다.
객체 선언: 싱글턴을 쉽게 만들기
- 객체지향 시스템을 설계하다 보면 인스턴스가 하나만 필요한 클래스가 유용한 경우가 많다.
- 자바에서는 보통 생성자를 private으로 제한하고 정적인 필드에 그 클래스의 유일한 객체를 저장하는 싱글턴 패턴을 통해 이를 구현한다.
- 코틀린은 객체 선언 기능을 통해 싱글턴을 언어에서 기본 지원한다.
- 객체 선언은 클래스 선언과 그 클래스에 속한 단일 인스턴스의 선언을 합친 선언이다.
object Payroll {
val allEmployees = arrayListOf<Person>()
fun calculateSalary() {...}
}
fun main(args: Array<String>) {
Payroll.allEmployees.add(Person(...))
}
- 객체 선언은 object 키워드로 시작하고, 클래스를 정의하고 그 클래스의 인스턴스를 만들어서 변수에 저장하는 모든 작업을 한 문장으로 처리한다.
- 객체 선언 안에도 프로퍼티, 메소드, 초기화 블록등이 들어갈 수 있지만 생성자는 객체 선언에 쓸 수 없다.
- 싱글턴 객체는 생성자 호출 없이 즉시 만들어지기 때문에 생성자 정의가 필요 없다.
- 객체 선언도 클래스나 인터페이스를 상속할 수 있다.
- 클래스 안에서 객체를 선언할 수도 있다.
- 그런 객체도 인스턴스는 단 하나뿐이다. 바깥 클래스의 인스턴스마다 객체가 생성되지 않는다.
동반 객체: 팩토리 메소드와 정적 멤버가 들어갈 장소
동반 객체를 일반 객체처럼 사용
객체 식: 무명 내부 클래스를 다른 방식으로 작성
Comments