모든 클래스는 Any의 하위 클래스이며, 기본적으로 상속 줄 수 없는 final class로 만들어짐
java와 반대로 기본이 final class로 선언(상속 불가)
extends 키워드 대신 콜론( : ) 사용
open class 부모 클래스 명{ //open으로 선언하여 파생 가능
}
class 자식 클래스 명: 부모 클래스 명( ){ //부모 클래스 상속, open 키워드 업음 -> 파생 불가
}
java는 메소드만 오버라이딩 가능하지만 코틀린은 프로퍼티도 가능
open class Human(var name: String = "홍길동", var age: Int){ //주생성자
fun play() = println("name : $name")
fun sing(vol: Int) = println("Sing age : $age")
}
//주생성자를 사용하는 상속
class Woman(name: String, age: Int): Human(name, age){
fun singHitone() = println("Happy song!")
}
//부생성자를 사용하는 상속
class Man: Human{
val race: String
constructor(name: String, age: Int, race: String): super(name, age){
this.race = race
}
}
fun main() {
var woman = Woman("사임당",20)
var man = Man("이순신",20,"아시아")
}
super
상위 클래스의 메서드, 프로퍼티, 생성자를 사용하는 키워드
super.메서드명 / super.프로퍼티명 / super( )
this
Type Polymorphism
Method Polymorphism
오버라이딩
오버로딩
method는 기본적으로 final method로 오버라이딩을 금지
open 키워드를 사용하여 오버라이딩 허용
open class Human(){
fun play(){} //최종 method로 오버라이딩 불가
open fun sing(){} //open mehtod로 오버라이딩 가능
open fun sing2(){}
}
open class Animal: Human(){
override fun sing() = println("override sing") //super 클래스 그대로 재정의하여 이 또한 open method
final override fun sing2() = println("override sing2 final") //final method로 오버라이딩 불가
}
open class Animal2: Animal(){
override fun sing() = println("override sing one more") //이 또한 마찬가지로 open method
}
런타임 시 만들어진 Object에서 최종적으로 오버라이딩된 메서드가 호출됨
open class Human(){
fun play() = println("Human play") //최종 method로 오버라이딩 불가
open fun sing() = println("Human sing") //open mehtod로 오버라이딩 가능
open fun sing2() = println("Human sing2")
}
open class Animal: Human(){
override fun sing() = println("Animal sing") //super 클래스 그대로 재정의하여 이 또한 open method
final override fun sing2() = println("Animal sing2") //final method로 오버라이딩 불가
}
open class Animal2: Animal(){
override fun sing() = println("Animal2 sing") //이 또한 마찬가지로 open method
}
fun main() {
var animal = Animal2()
animal.play()
animal.sing()
animal.sing2()
}
<---실행 결과--->
Human play
Animal2 sing
Animal sing2
자바에서 필드
단순한 변수 선언만 가지기 때문에 접근을 위한 메서드를 따로 만들어야 함
코틀린의 프로퍼티
변수 선언과 기본적인 접근 메서드를 모두 가지고 있음
접근 메서드는 생략(내부적으로 생성)
class User(id: Int, name: String, age: Int){
val id: Int = id // val로 선언 -> 읽기 전용
var name: String = name // var로 선언 -> 변경 가능
var age: Int = age
}
간략화하면 아래와 같고, getter / setter가 기본적으로 동작
class User(val id: Int, var name: String, var age: Int){ }
fun main() {
val user = User(1,"길동",30)
user.age = 25
println("${user.name}의 나이 = ${user.age}")
}
<---실행 결과--->
길동의 나이 = 25
기본적인 getter, setter를 대신해 지정 가능
불변형인 val은 getter만 설정 가능
filed 변수를 활용하여 기존 값과 새로운 값 비교 가능
- filed는 원래 값을 가지는 가상의 변수
class User(id: Int, name: String, age: Int){
val id: Int = id
get() = field
var name: String = name
get() = field
set(value){
field = value
}
var age: Int = age
get() = field
set(value){
println("현재값 : $field")
if(value<0 || value>150) println("다시 확인 바람")
else field = value
}
}
지연 초기화가 필요한 이유
변수나 객체의 값은 생성 시 반드시 초기화가 필요함
클래스 내에서 선언한 후 객체의 정보가 나중에 나타나는 경우 초기화 할 수 있는 방법이 필요함
지연 초기화를 위해 lateinit과 lazy 키워드 사용
의존성이 있는 초기화나 unit 테스트를 위한 코드 작성 시 프로퍼티 지연 초기화가 필요
클래스를 선언할 때 프로퍼티 선언은 null을 허용하지 않음
선언하려면 ? 형으로 선언하고 null 할당
lateinit 키워드를 사용하면 굳이 바로 할당하지 않아도 됨
var로 선언된 프로퍼티만 가능
프로퍼티에 대한 getter, setter 사용 불가
class Person{
lateinit var name: String
fun test(){
if(! ::name.isInitialized) { //초기화 여부 판단
println("not initailized")
}else{
println("initialized")
}
}
}
fun main() {
val gildong = Person()
gildong.test()
gildong.name = "gildong"
gildong.test()
}
<---실행 결과--->
not initailized
initialized
호출 시점에 by lazy{…} 부분의 초기화를 진행
불변의 변수 선언인 val에서만 사용 가능(읽기 전용)
val이므로 값을 다시 변경 불가
class Lazy{
init{println("init block")}
val subject by lazy{
println("lazy initialized")
"lazy test"//lazy 반환 값
}
fun flow(){
println("not initialized")
println("subject inital : $subject")
println("subject initialized : $subject")
}
}
fun main() {
val test = Lazy()
test.flow()
}
<---실행 결과--->
init block
not initialized
lazy initialized
subject inital : lazy test
subject initialized : lazy test
by lazy로 선언된 code block을 수행하는 것이므로, multi thread 동작 시 고려 필요
3가지 모드 지정 가능
SYNCHRONIZED
PUBLICATION
NONE
위탁자 → 수탁자 형태로 어떤 일의 책임 및 처리를 다른 클래스 또는 메서드에게 넘긴다는 의미
한 객체가 기능 일부를 다른 객체로 넘겨주어 첫 번째 객체 대신 수행하도록 하는 일
다른 클래스의 기능을 그대로 사용하면 상속 대신 위임
위임을 활용하면 한 객체의 변경이 다른 객체에 미치는 영향이 적어짐
Interface 타입의 위임만 가능
하나의 클래스가 다른 클래스에 위임하도록 선언하고 위임된 클래스가 가지는 멤버를 참조 없이 호출
코틀린 라이브러리는 대부분 open되어 있지 않아 상속이나 직접 확장이 어려움
다른 클래스의 멤버를 사용하도록 위임
interface Animal{
fun eat() = println("Animal.eat()")
}
class Cat: Animal{}
val cat = Cat()
class Robot: Animal by cat
fun main() {
var robot = Robot()
robot.eat()
println(robot::class.java)
}
<---실행 결과--->
Animal.eat()
class com.android.example.kotlinproject.Robot
프로퍼티를 표준 위임 함수인 observable( )로 위임 가능
class Observable{
var name: String by Delegates.observable("처음"){
property, oldValue, newValue -> println("$oldValue -> $newValue")
}
}
fun main() {
var ob = Observable()
ob.name = "두 번째"
ob.name = "세 번째"
}
<---실행 결과--->
처음 -> 두 번째
두 번째 -> 세 번째
프로퍼티를 표준 위임 함수인 vetoable( )로 위임 가능
observable( )과 거의 동일하지만 조건에 맞지 않으면 새로운 값 할당을 거부할 수 있음
true를 반환하면 새로운 값 할당, false를 반환하면 할당 거부
class Vetoable{
var age: Int by Delegates.vetoable(22){
property, oldValue, newValue ->
println("property : ${property.name}, $oldValue -> $newValue, result : ${oldValue>newValue}")
oldValue>newValue
}
}
fun main() {
var ve = Vetoable()
ve.age = 20
println(ve.age)
ve.age = 25
println(ve.age)
}
<---실행 결과--->
property : age, 22 -> 20, result : true
20
property : age, 20 -> 25, result : false
20
대신 companion object로 사용
class Person{
var id: Int = 0
var name: String = "hong"
companion object{ //고정된 static 내부 클래스처럼 정의
var language: String = "Ko"
fun work() = println("work()...")
}
}
fun main() {
println(Person.language)
Person.language = "En"
println(Person.language)
Person.work()
}
<---실행 결과--->
Ko
En
work()...