4. object 키워드
1) static 함수와 변수
class Person private constuctor (
var name: String,
var age: Int
) {
companion object Factory : Log {
const val MIN_AGE = 1
@JvmStatic
fun newBaby(name: String): Person {
return Person(name, MIN_AGE)
}
override fun log() {
println("나는 Person 클래스의 동행객체 Factory이다")
}
}
}
- Kotlin에서는 static 키워드가 없고, companion object 블록 안에 넣어둔 변수와 함수가 Java의 static 변수, 함수인 것 처럼 사용된다.
- companion object : 클래스와 동행하는 유일한 오브젝트
- 그냥 val이라고 쓰면 런타임 시에 변수가 할당된다. 그러나 앞에 const를 쓰면 컴파일 시에 변수가 할당된다. const는 기본 타입과 String에만 쓸 수 있다.
- companion object, 즉 동반객체도 하나의 객체로 간주된다. 때문에 이름을 붙일 수도 있고, interface를 구현할 수도 있다.
- companion object에 유틸성 함수들을 넣어도 되지만, 최상단 파일을 활용하는 것을 추천한다.
- @JvmStatic이라는 키워드를 붙이면 Java에서 static 필드나 메소드에 접근할 수 있는 것처럼 클래스. 으로 바로 접근할 수 있다. 없다면 클래스.Companion. 혹은 클래스.companion object 이름.으로 접근할 수 있다.
2) 싱글톤
- 싱글톤 : 단 하나의 인스턴스만 갖는 클래스
- 일반적으로 싱글톤을 Java에서 만들어주고 싶으면, 아래와 같이 작성하고 동시성 처리나 Enum class를 활용하는 방법도 있다.
public class Singleton {
private static final Singleton INSTANCE = new JavaSingleton();
private JavaSingleton() {}
public static JavaSingleton getInstance() {
return INSTANCE;
}
}
- Kotlin에서 싱글톤을 만들어주고 싶으면 앞에 object만 붙여주면 된다.
fun main() {
Singleton.a
}
object Singleton {
var a: Int = 0
}
3) 익명 클래스
- 익명 클래스 : 특정 인터페이스나 클래스를 상속받은 구현체를 일회성으로 사용할 때 쓰는 클래스
- Java에서는 new 타입이름()이라고 쓰지만 Kotlin에서는 object : 타입이름으로 작성한다.
fun main() {
moveSomething(object: Movable {
override fun move() {
println("무브 무브")
}
override fun fly() {
println("날다 날다")
}
})
}
private fun moveSomething(movable: Movable) {
movable.move()
movable.fly()
}
2. 중첩 클래스
1) 중첩 클래스의 종류
- Java의 중첩 클래스 종류
- Static을 사용하는 중첩 클래스
- Static을 사용하지 않는 중첩 클래스
- 내부 클래스(Inner Class) : 밖의 클래스를 직접 참조할 수 있다.
- 지역 클래스(Local Class) : 메소드 내부에 클래스를 정의한다.
- 익명 클래스(Anonymous Class) : 일회성 클래스
- 내부 클래스는 숨겨진 외부 클래스 정보를 가지고 있어, 참조를 해지하지 못하는 경우 메모리 누수가 생길 수 있고, 이를 디버깅하기 어렵다.
- 내부 클래스의 직렬화 형태가 명확하게 정의되지 않아 직렬화에 있어 제한이 있다.
- 따라서 Effective Java에서는 클래스 안에 클래스를 만들 때는 static 클래스를 사용하라고 가이드한다.
2) 코틀린의 중첩 클래스와 내부 클래스
- Kotlin의 static 중첩 클래스 : 기본적으로 바깥 클래스에 대한 연결이 없는 중첩 클래스가 만들어진다.
class JavaHouse (
private val address: String,
private val livingRoom: LivingRoom
) {
class LivingRoom(
private val area: Double
)
}
- Kotlin의 내부 클래스 : inner 라는 키워드를 명시적으로 붙여준다. 또한 바깥클래스를 참조하고 싶다면 this@바깥클래스를 사용한다.
class JavaHouse (
private val address: String,
private val livingRoom: LivingRoom
) {
inner class LivingRoom(
private val area: Double
) {
val address: String
get() = this@House.address
}
}
3. 다양한 클래스
1) Data Class
- Java의 계층 간에 데이터를 전달하기 위한 DTO(Data Transfer Object)는 데이터(필드), 생성자와 getter, equals, hashCode, toString으로 이루어져 있다. IDE를 활용할 수도 있고, lombok을 활용할 수도 있지만 클래스가 장황해지거나, 클래스 생성 이후 추가적인 처리를 해줘야 하는 단점이 있다.
- Kotlin에서는 data 키워드를 붙여주면 equals, hashCode, toString을 자동으로 만들어준다. 여기에 named argument를 쓰면 build pattern을 쓰는 것과 같은 효과도 있다.
- Java에서도 JDK16부터 Kotlin의 data class 같은 record class를 도입했다.
data class PersonDto(
val name: String,
val age: Int
)
2) Enum Class
- Enum class는 추가적인 클래스를 상속받을 수 없다. 인터페이스는 구현할 수 있으며, 각 코드가 싱글톤이다.
- Kotlin에서 when에 Enum class를 사용할 때는 Enum에 어떤 값이 있는지 이미 알고 있기 때문에 else를 추가로 작성할 필요가 없다. 즉 Enum에 대한 분기 처리를 할 때 Kotlin에서는 when을 사용해서 조금 더 읽기 쉬운 코드로 바꿀 수 있다.
fun handleCountry(country: Country) {
when (country) {
Country.KOREA ->
Country.AMERICA ->
}
}
enum class Country(
private val code: String
) {
KOREA("KO"),
AMERICA("US");
}
3) Sealed Class, Sealed Interface
- 상속이 가능하도록 추상 클래스를 만들었는데 외부에서는 이 클래스를 상속받지 않았으면 좋겠을 때 하위 클래스를 봉인하자는 니즈에서 Sealed Class가 나왔다.
- Sealed Class의 원리는 Enum과 마찬가지로 컴파일 타임 때 하위 클래스의 타입을 모두 기억한다. 즉, 런타임때 클래스 타입이 추가될 수 없다. 또한 하위 클래스는 같은 패키지에 있어야 한다.
- Enum과 다른 점은 클래스를 상속받을 수 있고, 하위 클래스는 멀티 인스턴스가 가능하다.
- Sealed 클래스는 추상화가 필요한 Entity, DTO에 사용된다. 또한 JDK17에서도 Sealed 클래스가 추가되었다.
sealed class HyundaiCar {
val name: String,
val price: Long
)
class Avante : HyundaiCar("아반떼", 1_000L)
class Sonata: HyundaiCar("소나타", 2_000L)
class Grandeur: HyundaiCar("그랜져", 3_000L)
private fun handleCar(car: HyundaiCar) {
when (car) {
is Avante ->
is Grandeur ->
is Sonata ->
}
}
참고