Table of contents
클래스 선언
Kotlin의 클래스 선언은 Java에 비해 훨씬 간결하다. 생성자를 클래스 헤더에 바로 쓸 수 있기 때문이다.
class User(val name: String, var age: Int)
이 한 줄이 Java의 다음 코드와 같은 역할을 한다.
public class User {
private final String name;
private int age;
public User(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() { return name; }
public int getAge() { return age; }
public void setAge(int age) { this.age = age; }
}
val로 선언하면 getter만, var로 선언하면 getter와 setter가 자동 생성된다. 프로퍼티라고 부르는데, Java의 필드 + getter/setter를 합친 개념이다.
val user = User("Kim", 25)
println(user.name) // getter 호출
user.age = 26 // setter 호출
data class
실무에서 가장 많이 쓰게 될 기능 중 하나다. DTO나 값 객체를 만들 때 Java에서는 equals(), hashCode(), toString(), copy() 같은 보일러플레이트를 직접 작성하거나 Lombok을 써야 했다. Kotlin은 data 한 단어로 해결된다.
data class Product(
val id: Long,
val name: String,
val price: Int
)
이렇게 선언하면 다음이 자동으로 생성된다.
val p1 = Product(1, "키보드", 50000)
val p2 = Product(1, "키보드", 50000)
println(p1) // Product(id=1, name=키보드, price=50000)
println(p1 == p2) // true (내용 비교)
println(p1.hashCode() == p2.hashCode()) // true
// copy — 일부 필드만 바꿔서 복사
val p3 = p1.copy(price = 45000)
println(p3) // Product(id=1, name=키보드, price=45000)
// 구조 분해
val (id, name, price) = p1
println("$name: ${price}원") // 키보드: 50000원
copy()는 불변 객체를 다룰 때 특히 유용하다. 객체 전체를 새로 만들지 않고 바꾸고 싶은 필드만 지정할 수 있기 때문이다.
object — 싱글톤
Java에서 싱글톤을 만들려면 private 생성자, static instance, 스레드 안전성까지 신경 써야 한다. Kotlin은 object를 쓰면 된다.
object Database {
val url = "jdbc:mysql://localhost:3306/mydb"
fun connect() {
println("Connected to $url")
}
}
fun main() {
Database.connect() // 인스턴스 생성 없이 바로 사용
}
object로 선언하면 언어 차원에서 싱글톤이 보장된다. 별도의 인스턴스 생성 없이 바로 접근할 수 있다.
companion object
Java의 static 메서드에 해당하는 기능이다. Kotlin에는 static 키워드가 없는 대신 companion object를 쓴다.
class User(val name: String) {
companion object {
fun create(name: String): User {
println("Creating user: $name")
return User(name)
}
}
}
fun main() {
val user = User.create("Kim") // 클래스명으로 직접 호출
}
팩토리 메서드 패턴을 구현할 때 자주 쓰인다.
상속
Kotlin의 클래스는 기본적으로 final이다. 상속을 허용하려면 open을 명시해야 한다. 이건 의도적인 설계 결정인데, “Effective Java”에서 “상속을 위해 설계하지 않은 클래스는 상속을 금지하라”고 권장하는 것과 같은 맥락이다.
open class Animal(val name: String) {
open fun sound(): String = "..."
}
class Dog(name: String) : Animal(name) {
override fun sound(): String = "멍멍"
}
class Cat(name: String) : Animal(name) {
override fun sound(): String = "야옹"
}
fun main() {
val animals = listOf(Dog("바둑이"), Cat("나비"))
for (animal in animals) {
println("${animal.name}: ${animal.sound()}")
}
}
상속할 클래스와 오버라이드할 메서드 모두 open이 필요하다. override는 Java의 @Override와 같지만, Kotlin에서는 필수 키워드다.
인터페이스
Kotlin의 인터페이스는 Java 8 이후의 인터페이스와 비슷하다. 추상 메서드와 기본 구현을 모두 가질 수 있다.
interface Drawable {
fun draw() // 추상 메서드
fun description(): String { // 기본 구현
return "도형"
}
}
class Circle(val radius: Double) : Drawable {
override fun draw() {
println("반지름 ${radius}인 원을 그린다")
}
}
클래스는 하나만 상속 가능하지만, 인터페이스는 여러 개를 구현할 수 있다. 이 원칙은 Java와 동일하다.
interface Clickable {
fun click()
}
interface Focusable {
fun focus()
}
class Button : Clickable, Focusable {
override fun click() = println("클릭!")
override fun focus() = println("포커스!")
}
다음 편에서는 Kotlin의 컬렉션과 람다를 다룬다. filter, map, forEach 같은 함수형 API와 scope function(let, apply, run)을 살펴보자.
Loading comments...