< Dev-Kidult />

Java to Kotlin - 3. 클래스와 함수 본문

개발/Back-end

Java to Kotlin - 3. 클래스와 함수

개른이 2022. 12. 28. 16:19

클래스

Constructor

자바에서는 클래스명과 동일한이름의 함수로 생성자를 지정할 수 있다.

class A {

    private String name;
    private String email;

    A() {}
    A(String name, String email) {
        this.name = name;
        this.email = email;
    }
}

var a = new A();

코틀린은 아래와 같다.

class A(var name: String?, var email: String?) : B() {
    init {
        // ...
    }
    constructor(name: String?) : this(name, null)
    constructor() : this(null)
}

var a = A()
  1. primary constructor class A(var name: String?, var email: String?)
    1. class A @Inject public constructor(var name: String?, var email: String?)
    2. 기본 생성자에도 constructor 키워드가 있으나 생략 할 수 있다.
    3. 가시접근자 혹은 어노테이션을 함께 쓸때는 constructor 키워드를 같이 써야 한다.
  2. init block init {...}
    1. init 블록은 클래스안에 하나 또는 여러개가 선언 될 수 있으며, 객체가 생성될 때 생성자보다 우선하여 호출된다.
  3. secondary constructor constructor() : this(null, null), constructor() : this(null)
  4. 객체를 선언할 때 자바와는 다르게 new키워드가 빠진다.

 

Inheritance

open class B(x: Int?) {
    constructor(): this(null)
    open val y: Int = 5
}
class A(x: Int?): B(x) {
    constructor(): super()
    override final val y: Int = 6
}

코틀린에서의 Class는 기본적으로 final Class이다. 확장을 해주려면 open 키워드를 붙여줘야 한다.

property 또한 오버라이드 하려면 open 키워드를 붙여줘야 한다.

그리고 오버라이드 된 필드 자체도 열려있으므로 하위객체에서 오버라이드를 막고 싶다면 final을 적도록 하자.

 

Static , Compaion Object

public Class Person {
    public static final int MAX_AGE = 50;
}

Person.MAX_AGE
class Person {
    companion object {
        const val MAX_AGE = 50
    }
}

Person.MAX_AGE
코틀린에서는 companion object를 통해서 상수를 선언할 수 있다.

하지만 꼭 그래야만 하는건 아니다. java와 달리 class가 1급객체인 언어라 아니라서 class로 감싸지 않아도 쓸 수 있다. (상황에 따라 다르지만 전 이쪽을 선호하는편)

물론 함수도 위와 같다.

 

Delegation

코틀린은 기본적으로 by키워드를 통해 위임패턴을 지원한다.

interface Base {
    fun printMessage()
    fun printMessageLine()
}

class BaseImpl(val x: Int) : Base {
    override fun printMessage() { print(x) }
    override fun printMessageLine() { println(x) }
}

class Derived(b: Base) : Base by b {
    override fun printMessage() { print("abc") }
}

fun main() {
    val b = BaseImpl(10)
    Derived(b).printMessage()
    Derived(b).printMessageLine()
}
위 예제를 보면 BaseImpl을 인자로 받아서 실행을 한다.

기본적으로 BaseImpl에서 오버라이드 된 함수를 실행시키지만 추가로 Derived에 오버라이드가 되어있다면 해당값 또는 함수를 참조한다.

 

Delegate Properties

코틀린에는 클래스 말고도 프로퍼티에도 위임을 할 수 있는 기능이 있다.

구현이 되어있는것도 있고, 구현도 할 수 있다.

class Delegate {
    operator fun getValue(thisRef: Any?, property: KProperty<*>): String {
        return "$thisRef, thank you for delegating '${property.name}' to me!"
    }

    operator fun setValue(thisRef: Any?, property: KProperty<*>, value: String) {
        println("$value has been assigned to '${property.name}' in $thisRef.")
    }
}

class Example {
    var p: String by Delegate()
}

val e = Example()
println(e.p)
//Example@~~~~~, thank you for delegating 'p' to me!
e.p = "NEW"
//NEW has been assigned to 'p' in Example@~~~~~.

deletgate로 쓰려면 위처럼 operator로 구현된 getValue, setValue 함수가 있어야 한다.

getValue 함수는 가져올 때 그리고 serValue는 해당 프로퍼티에 값을 넣을때 동작하게 된다.

 

lazy properties

val lazyValue: String by lazy {
    println("computed!")
    "Hello"
}

fun main() {
    println(lazyValue)
    println(lazyValue)
}

//computed!
//Hello
//Hello
lazy는 처음 값이 호출될 때 초기화가 진행된다.

 

Observable properties

class User {
    var name: String by Delegates.observable("<no name>") {
        prop, old, new ->
        println("$old -> $new")
    }
}

fun main() {
    val user = User()
    user.name = "first"
    user.name = "second"
}
//<no name> -> first
//first -> second
observable은 해당값에 값이 할당될 때 마다 호출된다.

 

Storing properties in a map

class User(val map: Map<String, Any?>) {
    val name: String by map
    val age: Int     by map
}

val user = User(mapOf(
    "name" to "John Doe",
    "age"  to 25
))

println(user.name) // Prints "John Doe"
println(user.age)  // Prints 25

맵에도 위같이 사용 할 수 있다.

그리고 MutableMap을 사용하는경우에는 var를 사용하여서 속성을 수정하는것도 가능하다.

 

Type aliases

말그래도 타입을 지정할 수 있는 기능이다.

typealias NodeSet = Set<Network.Node>
typealias FileTable<K> = MutableMap<K, MutableList<File>>

typealias MyHandler = (Int, String, Any) -> Unit
typealias Predicate<T> = (T) -> Boolean

클래스뿐만 아니라 고차함수도 지정가능하다.

함수

Extensions

코틀린에서는 함수확장이 가능하다.

fun <T> MutableList<T>.swap(index1: Int, index2: Int) {
    val tmp = this[index1]
    this[index1] = this[index2]
    this[index2] = tmp
}

val list = mutableListOf(1, 2, 3)
list.swap(0, 2)

 

Infix

앞서 range를 지정하는곳에서 이런 코드를 봤었을꺼다.

val range = 0 until 10 // eq 0.until(10)

val progression = 10 downTo 0 step 2 // eq 10.downTo(0).step(2)

val map = mapOf("one" to 1, "two" to 2) // eq "one".to(1)

위 코드 모두 infix 함수로 구현되어있는 함수이다.

 

일반적인 () 형태의 함수 호출방식이 아닌 띄어쓰기를 포함한 키워드를 통해서 함수를 호출하는 방식이다.

infix 함수를 구현하기 위해서는 아래 3가지 조건을 만족해야 한다.

  • 멤버 함수 또는 확장함수여야 한다.
  • 단일 매개변수여야 한다.
  • 매개변수는 가변 개수의 인수를 허용하지 않아야 하며, 기본값이 없어야 한다.
//ex
infix fun <A, B> A.to(that: B): Pair<A, B> = Pair(this, that)

 

operator

자바에서도 코틀린에서도 많은 연산자를 통해 사용 할 수 있는 기능들이 있다.

+a, -a, !a, a++, a--, a+b, a-b, a*b, a/b, ...

코틀린에서는 위 연산자도 오버라이딩이 가능하며, 적용범위또한 클래스등 여러범위에 사용 할 수 있다.

//ex
data class Point(val x: Int, val y: Int)

operator fun Point.unaryMinus() = Point(-x, -y)

val point = Point(10, 20)

fun main() {
   println(-point)  // prints "Point(x=-10, y=-20)"
}

 

해당되는 식 및 해당 함수는 아래와 같다.

 
 
Expression
Func
+a a.unaryPlus()
-a a.unaryMinus()
!a a.not(
a++ a.inc()
a-- a.dec()
a + b a.plus(b)
a - b a.minus(b)
a * b a.times(b)
a / b a.div(b)
a % b a.rem(b)
a..b a.rangeTo(b)
a in b b.contains(a)
a !in b !b.contains(a)
a[i] a.get(i)
a[i, j] a.get(i, j)
a[i_1, ..., i_n] a.get(i_1, ..., i_n)
a[i] = b a.set(i, b)
a[i, j] = b a.set(i, j, b)
a[i_1, ..., i_n] = b a.set(i_1, ..., i_n, b)
a() a.invoke()
a(i) a.invoke(i)
a(i, j) a.invoke(i, j)
a(i_1, ..., i_n) a.invoke(i_1, ..., i_n)
a += b a.plusAssign(b)
a -= b a.minusAssign(b)
a *= b a.timesAssign(b)
a /= b a.divAssign(b)
a %= b a.remAssign(b)
a == b a?.equals(b) ?: (b === null)
a != b !(a?.equals(b) ?: (b === null))
a > b a.compareTo(b) > 0
a < b a.compareTo(b) < 0
a >= b a.compareTo(b) >= 0
a <= b a.compareTo(b) <= 0

예외적으로 주소값 자체를 비교해주는 ===, !===는 확장구현 할 수 없다.

반응형
Comments