[Kotlin] - 코틀린 기초 (특별 클래스)
코틀린 공식문서에 있는 Example들을 하나씩 읽어보며 정리하자.
참고: https://play.kotlinlang.org/byExample/overview
Special Classes
Data Classes
Data Class를 사용하면 값을 저장하는 클래스(Dto, Vo)를 쉽게 만들 수 있다.
Data Class는 자동으로 메서드를 구현해 제공하고 override 가능하다.
- equals() / hashCode()
- toString()
- copy()
- componentN()
data class User(val name: String, val id: Int) { // 1
override fun equals(other: Any?) =
other is User && other.id == this.id // 2
}
fun main() {
val user = User("Alex", 1)
println(user) // 3
val secondUser = User("Alex", 1)
val thirdUser = User("Max", 2)
println("user == secondUser: ${user == secondUser}") // 4
println("user == thirdUser: ${user == thirdUser}")
// hashCode() function
println(user.hashCode()) // 5
println(secondUser.hashCode())
println(thirdUser.hashCode())
// copy() function
println(user.copy()) // 6
println(user === user.copy()) // 7
println(user.copy("Max")) // 8
println(user.copy(id = 3)) // 9
println("name = ${user.component1()}") // 10
println("id = ${user.component2()}")
}
/*
User(name=Alex, id=1)
user == secondUser: true
user == thirdUser: false
63347075
63347075
2390846
User(name=Alex, id=1)
false
User(name=Max, id=1)
User(name=Alex, id=3)
name = Alex
id = 1
*/
- data 키워드로 data class 정의한다.
- equals() 메서드 override
- toString 메서드 자동 생성되어 출력된다.
- override 된 equals() 메서드를 통해 비교 (data calss 에서 == 사용 시, 자동 생성된 equals() 메서드로 값비교를 한다.)
- 정확히 일치하는 속성을 가진 데이터 클래스 인스턴스는 동일한 hashCode를 갖는다. (user.hashCode() = secondUser.hashCode())
- 자동 생성된 copy() 메서드로 새로운 인스턴스를 쉽게 만들 수 있다.
- copy는 새 인스턴스를 생성하므로 ===(주소값 비교) 시 false를 반환한다.
- 복사할 때 특정 속성값을 변경할 수 있다. copy는 클래스 생성자와 같은 순서로 매개변수를 받는다.
- 지정 매개변수와 함께 copy 사용 시, 생성자 매개변수 순서를 무시할 수 있다.
- 자동 생성된 componentN 함수를 사용하면 선언 순서대로 값을 가져올 수 있다.
Enum Classes
Enum 클래스는 고유한 값의 집합을 나타내는 타입을 모델링할 때 사용된다.
enum class State {
IDLE, RUNNING, FINISHED // 1
}
fun main() {
val state = State.RUNNING // 2
val message = when (state) { // 3
State.IDLE -> "It's idle"
State.RUNNING -> "It's running"
State.FINISHED -> "It's finished"
}
println(message)
}
/*
It's running
*/
- 3개의 열거형 상수가 있는 간단한 enum 클래스를 정의한다. 열거형 상수의 수는 항상 유한해야하고 모두가 구분되어야 한다.
- 클래스 이름으로 열거형 상수에 접근한다.
- 열거형을 사용하면 컴파일러가 when-expression이 완전한지 추론할 수 있으므로 else-case가 없어도 된다.
열거형은 다른 클래스처럼 프로퍼티와 메서드를 가질 수 있다. 세미콜론으로 열거형 상수를 구분한다.
enum class Color(val rgb: Int) { // 1
RED(0xFF0000), // 2
GREEN(0x00FF00),
BLUE(0x0000FF),
YELLOW(0xFFFF00);
fun containsRed() = (this.rgb and 0xFF0000 != 0) // 3
}
fun main() {
val red = Color.RED
println(red) // 4
println(red.containsRed()) // 5
println(Color.BLUE.containsRed()) // 6
println(Color.YELLOW.containsRed()) // 7
}
/*
RED
true
false
true
*/
- 프로퍼티와 메서드를 갖는 열거형 클래스를 정의한다.
- 각 열거형 상수는 생성자의 매개변수에 대한 인수를 전달해야한다.
- 열거형 클래스의 멤버는 세미콜론으로 상수 정의와 구분된다.
- 기본 toString은 상수 이름(RED)을 반환한다.
- 열거형 상수에 대한 메서드를 호출한다.
- 열거형 클래스 이름을 통해 메서드를 호출한다.
- RED 와 YELLOW의 RGB 값은 첫번째 비트(FF)를 공유하므로 true가 출력된다.
Sealed Classes
Sealed classes를 사용하면 상속 사용을 제한할 수 있다.
sealed classes가 선언된 패키지 내부에서만 서브클래스를 만들 수 있다.
sealed class Mammal(val name: String) // 1
class Cat(val catName: String) : Mammal(catName) // 2
class Human(val humanName: String, val job: String) : Mammal(humanName)
fun greetMammal(mammal: Mammal): String {
when (mammal) { // 3
is Human -> return "Hello ${mammal.name}; You're working as a ${mammal.job}" // 4
is Cat -> return "Hello ${mammal.name}" // 5
} // 6
}
fun main() {
println(greetMammal(Cat("Snowy")))
}
/*
Hello Snowy
*/
- sealed calss를 정의한다.
- 서브클래스를 정의한다. 모든 서브클래스는 동일한 패키지에 있어야한다.
- when expression의 인자로 sealed class의 인스턴스를 사용한다.
- 스마트캐스트가 이뤄져 Mammal 이 Human 으로 캐스팅된다.
- 스마트 캐스트가 이뤄져 Mammal 이 Cat 으로 캐스팅된다.
- else 케이스는 필요없다. sealed class의 가능한 모든 서브클래스가 when 케이스에 포함되어 있기때문이다. non-sealed 슈퍼클래스를 쓴다면 else 케이스는 필요하다.
Object Keyword
코틀린에서 클래스와 객체는 대부분의 객체지향언어와 동일한 방시긍로 동작한다.
보통 클래스를 정의하고 그 클래스의 인스턴스를 여러 개 생성한다.
import java.util.Random
class LuckDispatcher { //1
fun getNumber() { //2
var objRandom = Random()
println(objRandom.nextInt(90))
}
}
fun main() {
val d1 = LuckDispatcher() //3
val d2 = LuckDispatcher()
d1.getNumber() //4
d2.getNumber()
}
/*
17
80
*/
- 클래스를 정의한다.
- 메서드를 정의한다.
- 인스턴스를 생성한다.
- 인스턴스의 메서드를 호출한다.
코틀린에는 object 키워드도 있다.
이 키워드는 단일 구현으로 데이터 타입을 얻는데 사용된다.
자바의 싱글톤이라고 생각하면 된다. (싱글톤은 두 개의 스레드가 동시에 클래스의 인스턴스를 생성하려고 하더라도 오직 한 개의 인스턴스만 생성되는 것을 보장하는 것을 말함)
코틀린에서 이를 달성하기 위해서는 간단하게 object 키워드로 선언하면 된다.
object 키워드로 생성한 인스턴스에는 클래스도 없고 생성자도 없다. 오직 게으른 인스턴스(lazy instance)이다.
왜 게으르다(lazy) 할까? 왜나면 객체에 접근할 때 한번 생성되기 때문이다. 접근하기 전까지는 생성되지 않는다.
Object Expression
클래스 선언에서 사용할 필요가 없다. 단일 객체를 생성하고 멤버를 선언한 후 하나의 함수 내에서 접근한다.
이와 같은 객체는 자바에서 종종 익명 클래스의 인스턴스로 생성된다.
fun rentPrice(standardDays: Int, festivityDays: Int, specialDays: Int): Unit { //1
val dayRates = object { //2
var standard: Int = 30 * standardDays
var festivity: Int = 50 * festivityDays
var special: Int = 100 * specialDays
}
val total = dayRates.standard + dayRates.festivity + dayRates.special //3
print("Total price: $$total") //4
}
fun main() {
rentPrice(10, 2, 1) //5
}
/*
Total price: $500
*/
- 파라미터를 갖는 함수를 생성한다.
- 결과값을 생성할 때 사용할 객체를 생성한다.
- 객체의 프로퍼티에 접근한다.
- 결과를 출력한다.
- 함수를 호출한다. 이때 실제로 객체가 만들어진다.
Object Declaration
object 선언으로도 사용할 수 있다. 이것은 expression 이 아니며 변수 할당에 사용할 수 없다.
object 선언된 객체에 직접 접근해서 사용해야한다.
object DoAuth { //1
fun takeParams(username: String, password: String) { //2
println("input Auth parameters = $username:$password")
}
}
fun main(){
DoAuth.takeParams("foo", "qwerty") //3
}
/*
input Auth parameters = foo:qwerty
*/
- objec 선언
- object 메서드를 정의한다
- 메서드를 호출한다. 이때 실제로 객체가 생성된다.
Companion Objects
클래스 안에 있는 object 선언은 또 다른 유용한 사례인 companion object를 정의한다.
문법적으로 자바의 static 메소드와 유사하다. 클래스 이름을 한정자(qualifier)로 사용하여 객체 멤버를 호출한다.
만약 코틀린에서 companion object를 사용할 계획이라면, pakage-level 함수 대신에 사용할 것을 고려해봐야한다.
class BigBen { //1
companion object Bonger { //2
fun getBongs(nTimes: Int) { //3
for (i in 1 .. nTimes) {
print("BONG ")
}
}
}
}
fun main() {
BigBen.getBongs(12) //4
}
/*
BONG BONG BONG BONG BONG BONG BONG BONG BONG BONG BONG BONG
*/
- 클래스를 정의한다.
- companion object 정의, 이름은 생략 가능
- companion object 메소드 정의
- 클래스 이름을 통한 companion object 메소드 호출