Development/Kotlin&Android

Kotlin 문법 Part 3

우봉수 2023. 3. 16. 13:14

Class

class Animal(val name:String)

class Person(val firstName: String, tmpLastName:String){
    var lastName:String = tmpLastName
        get () = field.uppercase()
        set (value){
            field = "[$value]"
        }
}

fun main(){
    val a = Animal("Dog")
    println("Animal: ${a.name}")
    val p = Person("Sukung","Kim")
    // getter 호출됨
    println("Name: ${p.firstName} ${p.lastName}")
    // setter 호출됨 []이 붙여짐
    p.lastName = "KIM"
    println("Name: ${p.firstName} ${p.lastName}")

}
  • 단순한 클래스 정의
    • class 클래스_이름(생성자 인자 리스트)
    • 생성자 인자들 중 val/var이 붙은 것은 클래스의 속성이 됨
  • 자바와 다른점
    • 코틀린은 기본적으로 public class임
    • 객체를 생성할 때 new 키워드를 쓰지 않음
    • 속성 접근 메소드를 자동으로 만들어주며, 커스텀 접근 메소드를 만들 수도 있음

Interface

interface ClickListener{
    fun onClick()
    fun onTouch() = println("touched")
}

class View(private val id:Int):ClickListener{
    override fun onClick() {
        println("clicked $id")
    }
}

fun main(){
    val v = View(10)
    v.onClick()
    v.onTouch()
}
  • 자바의 인터페이스와 비슷함
  • 구현이 있는 메소드 정의가 가능 하지만 자바처럼 default를 붙이진 않음
  • 인터페이스 구현 클래스의 메소드 구현에서 override를 반드시 적어주어야 함
    • 클래스 상속 받을 때 메소드 오버라이드 할 때에도 override를 반드시 명시해 주어야 함

기본적으로 상속, 오버라이드 금지

class NoInherit(val name:String)
// 컴파일 에러
//class Child2(val name:String):NoInherit(name)

open class Inherit(val name:String){
    fun first() = println("first")
    open fun second() = println("second")
}

open class child(name:String):Inherit(name){
    final override fun second() {
        println("override second")
    }
    open fun third() = println("third")
}

class GrandChild:child(""){
    override fun third() {
         println("override third")
    }
}
  • open 이라는 키워드가 붙어야 가능
  • 단 interface, abstract class는 기본적으로 open 키워드가 디폴트로 들어가 있음
  • class 는 기본적으로 상속, 메소드 오버라이드 금지
    • open class Test()
  • 메소드 오버라이드 접근 변경
    • final: 오버라이드 금지 기본적인 디폴트 설정임
    • open: 오버라이드 가능
    • abstract: 반드시 오버라이드 해야 함
    • override: 오버라이드 한 것을 명시

기본적으로 공개(public)

  • 코틀린은 클래스와 메소드 모두 기본적으로 public
  • 접근 변경자
    • public: 디폴트 옵션 자유롭게 접근 가능
    • internal: 같은 모듈에서만 접근 가능
    • protected: 상속 받은 클래스에서만 접근 가능
    • private: 같은 클래스 에서만 접근 가능

중첩 클래스

class Outer(var m:Int=0){
    // inner 키워드를 통해 내부 클래스 선언 
    inner class Inner{
        fun doSomething(){
            this@Outer.m++
            println("Inner.doSomething ${this@Outer.m}")
        }
    }
    // Outer 클래스의 속성에 접근이 불가능
    // 기본적으로 정적(static) 중첩 클래스로 만들어짐 
    class NoInner{
        fun doSomething(){
            println("NoInner.doSomething")
        }
    }
}

fun main(){
    // Outer클래스를 만들고 나서야 Inner 클래스를 만들 수 있음
    // 대신 Outer 클래스의 속성에 접근이 가능함
    val inner = Outer().Inner()
    inner.doSomething()
    // Outer클래스를 만들지 않고 바로 NoInner 클래스를 만들 수 있음
    val noInner = Outer.NoInner()
    noInner.doSomething()
}
  • 코틀린에서 중첩 클래스는 자바에서 static 중첩 클래스와 같음
  • 자바처럼 내부 클래스를 만들려면 중첩 클래스 앞에 inner 키워드 사용

봉인(sealed) 클래스

sealed class Expr{
    class Num(val value:Int): Expr()
    class Sum(val left: Expr, val right: Expr): Expr()
}

fun eval(e: Expr): Int =
    when(e){
        is Expr.Num -> e.value
        is Expr.Sum -> eval(e.right)+eval(e.left)
    }

fun main(){
    val r = eval(Expr.Sum(Expr.Num(10),Expr.Num(10)))
    println(r)
}
  • 특정 클래스를 상속하는 클래스를 제한할 수 있다.

Constructor 생성자

class ConstEx1 constructor(tmp:Int){
    val prop: Int
    val prop2: Int
    init {
        prop = tmp
        prop2= tmp
    }
}

class ConstEx2 constructor(tmp:Int){
    val prop = tmp
}

class ConstEx3 constructor(val prop:Int)

open class ConstEx4(val prop:Int,val prop2:Int=0){
    constructor(_prop:Int,_prop2:Int,_prop3:Int): this(_prop+_prop3+_prop2){

    }
}

class ConstEx5(_prop:Int) : ConstEx4(_prop)

fun main(){
    ConstEx1(1)
    ConstEx2(2)
    ConstEx3(3)
    ConstEx4(4)
    ConstEx4(4,4)
    ConstEx4(4,4,4)
    ConstEx5(5)
}
  • Primary constructor: 클래스 이름 옆에 정의하는 생성자
  • Secondary constructor: 클래스 내부에 정의 하는 생성자

Property와 getter/setter

interface GetSetI{
    var prop: String
}

class GetSet(tmp:String):GetSetI{
    override var prop: String = tmp
        set(value) {
            field = value.substringBefore('@')
        }
        get() = field.uppercase()
    var prop2:Int =0
        private set
}

fun main(){
    val getset = GetSet("test@removed")
    println("getset.prop = ${getset.prop}")
    getset.prop = "ok@remeved"
    println("getset.prop = ${getset.prop}")
    // 컴파일 오류 getset.prop2 = 10
}
  • Property의 커스텀 setter와 getter를 정의할 수 있다.
    • public 속성에 대해 setter를 private으로 하면 클래스 밖에서는 get만 가능함
  • Interface에 property를 정의할 수 있으나, 이 property는 구현 클래스에서 반드시 오버라이드 해야 한다.

Data Class

data class MyClass(val a:Int,val b:String)

fun main(){
    val str1 = "Hello, kotil"
    val str2 = "Hello, kotil"
    val class1 = MyClass(10,"class1")
    val class2 = MyClass(10,"class1")
    val class3 = MyClass(20,"class2")

    println(str1 == str2)
    println(class1==class2)
    println(class1==class3)
    println(class1==class2)

}
  • class 정의 앞에 data 키워드 추가
  • data class 는 equals/ hashCode/ toString / copy를 자동으로 만들어준다.

object 키워드

interface ClickListner{
    fun onClick()
}
object ClickListenerImp : ClickListner{
    override fun onClick() {
        println("clicked")
    }
}

fun setClickListner(listner: ClickListner) = listner.onClick()

class Touch(){
    val objectNums : Int
        get() = num
    init{
        num++
    }
    // Touch 클래스가 아무리 많이 만들어져도 해당 클래스는 한 개만 존재 싱글톤
    companion object{
        var num:Int = 0
    }
}

fun main(){
    setClickListner(ClickListenerImp)
    setClickListner(object :ClickListner{
        override fun onClick() {
            println("click2")
        }
    })

    Touch()
    Touch()
    println(Touch().objectNums)
}
  • 클래스 정의 없이 바로 객체를 생성하는 방법
    • 싱글톤을 만들거나
    • companion object를 만들거나
    • anonymous object를 만들때 사용 (한 번만 사용하는 객체를 만들경우)
  • Companion object는 이 객체를 포함하는 클래스의 private 맴버에 접근 가능

Extension Method/Property

val String.lastChar:Char
    get() = get(length-1)

fun main(){
    println("Hello".lastChar)
}
fun<T> Collection<T>.join(separator:String = " "):String
{
    val result = StringBuilder()
    // this.withIndex (해당 컬렉션의 맴버)
    for((index,element)in withIndex()){
        if(index>0) result.append(separator)
        result.append(element)
    }
    return result.toString()
}

fun main()
{
    println(listOf("1","2","3").join())
    println(arrayListOf(1,2,3).join(","))
}
  • 이미 말들어진 클래스의 메소드와 속성을 클래스를 상속하거나 수정하지 않고도 추가 할 수 있음
  • 이렇게 추가도니 메소드는 오버라이드 안됨
  • 자바의 기존 Collection들에 다양한 유틸 함수들을 이 방법으로 추가
    • String의 새로운 유틸 함수: substringBefore, substringAfter 등

연산자 오버로딩

data class Complex(val real:Double,val img:Double){
    operator fun plus(other:Complex):Complex
        = Complex(real + other.real,img+other.img)
    override fun toString(): String = "$real+${img}i"
}

operator fun Complex.minus(other: Complex):Complex
    = Complex(real-other.real,img-other.img)

fun main(){
    val c1 = Complex(1.0,1.0)
    val c2 = Complex(2.0,2.0)
    val c3 = c1 + c2
    println(c3)
    println(c3==Complex(3.0,3.0))

}
  • 이항 산술 연산자 +,-,*,/,% 오버로딩 가능
    • 오버로딩할 때 연산자 이름 plus, minus, times, div, rem
  • 단항연산자
    • unaryPlus, unaryMinus, not, inc, dec
  • 비교 연산자
    • equals, compareTo

위임(Delegation)

interface Base{
    fun print()
    fun printHello()
}

class BaseImpl(val x :Int):Base{
    override fun print(){print(x)}
    override fun printHello() {
        println("Hello")
    }
}

class Derived(val b:Base):Base by b{
    override fun print() {
        b.print()
        println("ok")
    }
}

fun main(){
    val b = BaseImpl(10)
    val d = Derived(b)
    d.print()
    d.printHello()
}
  • 상속과 유사하지만 다른 개념
    • 상속은 강한 결합, 위임은 약한 결합
    • 상속 북가여도 위임은 가능
    • 위임(상속)할 클래스와 동일한 인터페이스를 구현해야 함
  • 위임 패턴은 위임(상속)할 객체를 맴버로 만들고 특정 메소드 호출시 위임 객체의 메소드로 포워드(호출)하는 방식으로 만듦
    • 위임 객체의 메소드 각각에 대해 메소드를 새로 만들어서 포워드 해야 함
  • 코틀린은 by 키워드로 위임을 쉽게 지원함

위임 속성(Delegated Property)

class User(val map:MutableMap<String, Any?>){
    val lazyValue: String by lazy{
        // 처음 실행시 출력됨
        println("computed!")
        "hello"
    }

    var name:String by map
    var age: Int by map
    var nameOb: String by Delegates.observable("<no name>"){
        prop,old,new ->
        println("$old -> $new")
    }
}

fun main(){
    val user = User(mutableMapOf(
        "name" to "SuKung",
        "age" to 25
    ))

    println(user.lazyValue)
    println(user.lazyValue)

    println(user.name)
    println(user.age)
    user.age = 30
    println(user.map)

    user.nameOb = "fisrt"
    user.nameOb = "second"
}
class MyClass{
    var newName: Int = 0
    @Deprecated("Use 'newNaem' instead",ReplaceWith("newName"))
    var oldName: Int by this::newName
}
  • by 키워드를 써서 속성의 get/set을 위임 객체의 getValue/setValue로 위임함
  • 이를 이용하여 특별한 성질을 갖는 속성을 만듦
    • Lazy 특성: 값을 처음 접근할 때 생성/계산함
    • Observable 특성: 값이 변경될 때 알려주는 리스너를 지정
    • Map을 이용하여 객체 속성 값을 저장
  • 다른 속성으로 위임할 수도 있음

Generic

fun <T:Number> myPlus(op1:T,op2:T):String = "$op1$op2"

class MyGeneric<T:Number>(private val prop:T){
    fun test(arg:T):String{
        println(arg)
        return myPlus(prop,arg)
    }
}
fun main(){
    println(myPlus(1,1))
    println(myPlus(1.0,1.0))
    println(MyGeneric(10).test(10))
}
  • 자바 제네릭과 비슷함