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))
}
  • 자바 제네릭과 비슷함

'Development > Kotlin&Android' 카테고리의 다른 글

안드로이드-레이아웃  (0) 2023.03.30
Kotlin 문법 Part 4  (0) 2023.03.23
Kotlin 문법 Part 2  (0) 2023.03.02
Kotlin 문법 Part 1  (0) 2023.02.28
스프링의 코틀린 지원  (0) 2023.02.27

<클래스>

ES6

생성자는 constructor()을 사용한다.

다음과 같이 상속도 가능하며 단 이때는 

  • 상속받은 클래스 안에서 super();을 통해 부모 클래스의 생성자를 호출 하여야 한다.
  • 부모 클래스가 자식 클래스 보다 먼저 선언 되어 있어야 한다.

ES7

생성자 constructor() 필요 없이 직접 변수명에 값을 할당해주는 것이 가능해졌다.

(물론 코드 뒤에서는 생성자를 만들어서 실행된다.)

또한 맴버함수의 프로퍼티 값을 화살표 함수로 표현 함으로써 this 키워드를 사용하지 않아도 된다.

 

C++ 기준 유일한 차이점은 

  • Struct는 디폴트 지정자(한정자)가 public 이고
  • Class는 디폴트 지정자(한정자)가 private 이다.

조심해야 될 점: C++ 기준 Struct도 상속이 가능하며 메소드 Struct를 가지는 것 또한 가능하다.

 

Swift 기준으로는 차이점이 많이 달라지는데 다음과 같다.

  • Struct는 Value-Type, Class는 Reference-Type
  • Struct는 상속이 불가하며 Class는 상속이 가능하다

참고하면 좋은 글: https://terry-some.tistory.com/75

 

'CS > Interview' 카테고리의 다른 글

Vector와 ArrayList 비교  (0) 2022.12.29

+ Recent posts