본문 바로가기
프로그래밍 놀이터/Kotlin, Coroutine

[Kotlin Tutorial] 클래스, objects, 그리고 인터페이스 #1 - Chap4. Classes, objects, and interfaces

by 돼지왕 왕돼지 2017. 8. 11.
반응형

 [Kotlin Tutorial] 클래스, objects, 그리고 인터페이스 #1 - Chap4. Classes, objects, and interfaces


참조 : Kotlin in Action

@jvmfield, Abstract, abstract method, abstract modifiers, abstract property, accessor, accessor visibility, and interfaces, android studio module, annotation argument, backing field, class body, class member, const, constructor, constructor keyword, default keyword, default method duplicate, default value, delegated properties, extends, extension function, file, file 안 가시성, Final, final by default, final keyword, fragile base class, Get, getter, getter field, getter setter, getter settter backing field, gradle project, implements, init keyword, initializer block, inner, inner class, inner modifier, Interface, Internal, internal 함수, Java, java default method, java8 default method, jvmfield, Kotlin, kotlin basics, kotlin class, kotlin extends, kotlin implements, kotlin interface, kotlin internal, kotlin object, kotlin override, kotlin package-private, kotlin private constructor, kotlin protected, kotlin tutorial, kotlin 기초, kotlin 기초 강좌, kotlin 상속, lateinit, lateinit modifier, lazy init property, mangled, module, module 개념, name space, nested by default, Nested Class, non-static nedted, nonnull property, object, objects, Open, open modifier, override property, override val, package, package-private, package-private compile, primary, primary constructor, Private, private constructor, private get, private inner class, private set, properties, property, Protected, Public, public by default, Seal, sealed class, sealed modifier, secondary constructor, setter field, Singleton, state, static nested class, Super, This, this@Outer, top-level, top-level declaration, top-level function, util class, visibility modifier, without accessor, [Kotlin Tutorial] 클래스, 그리고 인터페이스 - Chap4. Classes, 난도질, 난독화, 문법, 상속 가이드, 의도적 명시, 자동 private constructor, 코틀린 강좌


4.1. Defining class hierarchies


4.1.1. Interface in Kotlin


-

Kotlin 의 interface 는 Java8 과 비슷하다.

abstract method 를 가질수도 있고, Java8의 default method 도 가질 수 있다. ( Java8 과는 다르게 default keyword 는 필요없다 )

단, state 는 여전히 가질 수 없다. ( 실제 variable )



-

Kotlin 에서 interface 정의는 아래와 같다.

interface Clickable{

    fun click()

}



-

Kotlin 에서는 extends 와 implements 대신 : (콜론) 을 써주면 된다.

@Override 대신 override modifier 를 사용한다. 굳 포인트는 자바와 달리 override 는 “필수” 이다.

class Button : Clickable{

    override fun click() = println(“I was clicked”)

}



-

Default method 가 생기면서 문제가 될 수 있는 것은…

여러개의 interface 가 동시에 똑같은 이름의 default method 를 가졌을 때 어떤 녀석이 불릴까인데..

Kotlin 참 잘 설계한 것 같다.


Compile 에러가 난다.

The class ‘TestClass’ must override public open fun testDefaultMethod() because it inherits many implementations of it.


class Button : Clickable, Focusable{

    override fun click() = println(“I was clicked”)

    override fun showOff(){

        super<Clickable>.showOff() // Java 에서는 Clickable.super.showOff() 로 불렀어야 한다

        super<Focusable>.showOff()

    }


    // 한개의 super 만 호출하고 싶다면 아래와 같이 하면 된다

    // override fun showOff() = super<Clickable>.showOff()

}



-

Kotlin 은 Java6 를 support 한다. 그래서 사실상 호환시에는 default method 를 지원할 수 없다.

그래서 compiler 는 interface 의 default method 를 일반 interface 와 static method 를 가진 class 를 생성한다.

그래서 Java 에서 default method 를 가진 Kotlin class 를 implement 하게 되면 기본 interface 함수뿐만 아니라  default method 로 정의된 것도 impl 해주어야 한다.

( 이건 꾸리구먼... )




4.1.2. Open, final, and abstract modifiers: final by default


-

상속은 super class 의 구현 변화에 따른 변동 영향을 받기가 쉽다. (fragile base class 라 부른다.)

그래서 정확하게 상속 가이드를 주지 않으면 모든 sub-class 를 현실적으로 조사할 수 없기 떄문에 이상한 동작하기 쉽상이다.


Kotlin 은 그래서 상속을 위해 설계된 함수가 아니면 override 하지 못하도록 모든 class, method 가 final 이다.

그럼 상속을 시키려면 어떻게 하느냐? open 이라는 modifier 를 추가해주면 된다.



-

만약 super 의 녀석 또는 interface 의 녀석을 override 했는데, 나의 subclass 는 override 하지 않게 하려면 명시적으로 final keyword 를 써주면 된다.



-

Kotlin 도 Java 처럼 abstract 를 붙이면 의도한데로 동작한다.

abstract method 들은 자동으로 open 이므로 명시적으로 open 을 쓰지 않아도 된다.



-

Kotlin 에서 사용되는 modifier 들


final / override 할 수 없음 / class member 함수들은 기본적으로 final 이다

open / override 할 수 있음 / final 의 반대로 Java 와는 반대로 오히려 상속시키려면 open 시켜야 한다

abstract / 반드시 override 해야함 / abstract class 에만 사용하면 된다. abstract class 안의 abstract method 들은 구현을 가질 수 없다.

override / superclass 나 interface 의 것을 override 한다




4.1.3. Visibility modifiers: public by default


-

Java 처럼 public, protected, private modifier 가 있다.

기본 visibility 가 다르다. 아무것도 안 쓰면 public 이다

package-private 은 Kotlin 에 없다.

( name space 구성으로 visibility package-private 을 만들 수 있다 )



-

internal 이라는 modifier 가 있다. 이 녀석은 module 안에서만 visible 하다는 의미이다.

module 이라 하면 함께 compile 되는 file 전체를 이야기한다.

IntelliJ IDEA 와 Android Studio 의 module, Eclipse 의 project, Maven 과 Gradle 의 project 단위로 보면 된다.


internal 은 module 단위의 “real” encapsulation 을 지원한다.

Java 에서는 내 module 에서 같은 package 이름에 같은 class 를 정의하면 package-private 에 접근할 수 있었다.. (신박!)



-

Kotlin 은 top-level declaration 에도 private 을 쓸 수 있다.

top-level declaration 에 private 을 쓰면 file 안에서만 가시성을 갖는다.



-

Kotlin 에서 사용하는 visibility modifier 다


public(default) / 어디서든 보인다

internal / module 안에서만 보인다

protected / subclass 에서만 보인다 ( 같은 package 에서 못 본다 )

private / class 내에서만 보인다.



-

extension function 에서는 public 과 internal 만 볼 수 있다.



-

Java 와의 호환을 생각해보면… 나머지는 동일 로직 적용

private class 는 package-private 로 compile 된다.

internal 은 bytecode 에서는 public 이 된다.


Kotlin 코드에서는 접근 못 하는 것을 Java 에서 접근할 수 있당…. (네..?)


예제를 못 돌려봐서 정확히는 모르겠지만..

Java 에서 호출할 때 internal 함수의 이름은 “난도질되었다(mangled)” 라고 한다.

그래서 실수로 부르거나 override 하는 것을 방지한다고 하는데.. 아마 난독화 이상의 수준으로 함수 이름이 변경될 것 같다.



-

Kotlin 의 또 다른 점은

private inner class 의 내부를 outer class 가 볼 수 잆다는 것! ( 좋아! )




4.1.4. Inner and nested classes: nested by default


-

Kotlin 역시 inner class 를 가질 수 있다.

Java 와의 차이점은 outer class 를 의도적으로 명시하지 않으면 접근할 수 없다는 것이다. ( leak 이 줄겠군 )

즉 Java 의 static inner class 로 작동한다는 의미이다.


그럼 non-static inner class 는 어떻게 만드느냐? inner 를 써주면 된다.



-

Kotlin 의 inner inner class 에서 outer class 의 참조를 얻기 위해서는 this@Outer 를 통해 접근해야 한다.

class Outer{

inner class Inner{

fun getOuterReference(): Outer = this@Outer

}

}




4.1.5. Sealed classes: defining restricted  class hierarchies


-

interface Expr

class Num(val value: Int) : Expr

class Sum(val left: Expr, val right: Expr) : Expr


fun eval(e: Expr): Int =

    when(e){

        is Num -> e.value

        is Sum -> eval(e.right) + eval(e.left)

        else -> throw IllegalArgumentException(“Unknown expression”)

    }

else 로 default branch 를 주는 것은 에러를 양산하기 좋다. ( 추후 Num, Sum 외에 하나를 더 추가한다고 생각해보자 )

그렇다고 default branch 를 안 주면 또 에러가 나는 구조이다!!!



-

sealed 를 사용하면 된다!!!

sealed class Expr{

    class Num(val values: Int) : Expr : 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)

    }

sealed 가 되면, Expr 안에 새로운 class 를 추가하고, eval function 의 when 에서 처리 안 해주면 compile error



-

sealed modifier 가 있으면 class 는 자동으로 open 이다

그리고 sealed modifier 가 있는 class 는 자동으로 private constructor 를 갖는다.



-

현재 (2017.07.22) Kotlin 은 1.1.2 버전이 나와 있는 상태.

1.0 에서는 sealed class 안에 subclass 들이 nested 되어있어야 했지만, 지금은 바깥에 있어도 된다.


1.0 에서는 subclass 는 data class 로 만들 수 없다. ( 이녀석도 개선 예정인가보다 )


cf) reference 문서를 보면..

sealed class Expr

data class Const(val number: Double): Expr()

data class Sum(val e1: Expr, val e2: Expr) : Expr()

object NotANumber : Expr()

sealed class 의 subclass 들은 한 파일에 들어 있어야 한다.

그러나 sealed class 를 상속한 녀석을 또 상속한 녀석은 같은 파일에 있지 않아도 된다.






4.2. Declaring a class with nontrivial constructors or properties


-

Kotlin 에는 primary constructor 와 secondary constructor 가 있다.

primary 는 말 그대로 보통 main constructor 이며 class body 바깥쪽에 선언된다.

secondary 는 class body 안에 정의된다.




4.2.1. Initializing classes: primary constructor and initializer blocks


-

class User(val nickname: String)


위의 코드에서 괄호 ( ) 와 함께 정의된 것이 primary constructor 이다.

이는 2가지를 의미하는데, constructor parameter 를 정의하고, property 를 저 param 으로 채우겠다는 의미이다.


이는 아래와 같이 풀어 쓸 수도 있다.

class User constructor(_nickname: String){

    val nickname: String


    init{

        nickname = _nickname

    }

}



-

constructor keyword 는 primary 나 secondary constructor 를 정의할 때 쓰인다.

init keyword 다음에는 initializer block 을 써준다.


primary constructor 는 그 문법상 코드가 들어가지 않기 때문에 constructor 가 불리면 class 를 생성하면서 init 으로 정의한 initializer block 이 불린다.

( initializer block 은 한 class 에 여러개 정의할 수도 있다. )



-

constructor 에 default value 를 적용할 수도 있다.

모든 param 이 default 라면 param 없는 default constructor 도 생성된다.



-

만약 subclass 를 만드는 거라면 다음과 같이 해서 super class 의 constructor 호출까지 할 수 있다.

open class User(val nickname: String) { … }


class TwitterUser(nickname : String) : User(nickname) { … }



-

open class Button // default constructor 만 생성


class RadioButton: Button() // Button 의 default constructor 호출



-

private constructor 를 만들려면 다음과 같이..

class Secretive private constructor(){ }

private constructor 는 보통 util class 나 singleton 에 많이 쓰이는데, util class 는 top-level func 로 대체 가능하고, singleton 은 object 선언으로 가능하다. 

다시 말해 이 방법은 쓸 일이 거의 없다고 보면 된다.




4.2.2. Secondary constructors: initializing the superclass in different ways


-

multiple constructor 가 필요한 경우는 거의 없다. ( 대부분 default 로 대체 가능하니까.. 란다 )

필요한 경우가 있다면 multiple constructor 를 제공하는 framework class 를 상속하는 경우이다. ( 대부분 Java class 상속할 때가 그러하다 ) 

예를 들면 android 의 View 를 상속할 때..

open class View{

    constructor(ctx: Context){

        // do something

    }


    constructor(ctx: Context, attr:AttributeSet){

        // do something

    }

}


secondary constructor 는 클래스의 { } 안에 constructor 라는 키워드로 시작한다.



-

clsss MyButton : View{

    constructor(ctx: Context) : this(ctx, MY_STYLE){ // this 로 내 다른 constructor 호출 가능

        // do something

    }


    constructor(ctx: Context, attr: AttributeSet) : super(ctx, attr){ // super 의 constructor 호출

        // do something

    }

}




4.2.3. Implementing properties declared in interfaces


-

Kotlin 의 interface 는 abstract property 를 가질 수 있다.

interface User{

    val nickname: String // constructor 에서 set 을 해주거나, getter 를 지정해야 하는 것으로 볼 수 있다.

}


class PrivateUser(override val nickname: String) : User // User 것을 setting 하므로 override 를 써준다


class SubscribingUser(val email: String) : User{

    override val nickname: String

        get() = email.substringBefore(‘@‘)

}


class Facebook(val accountId: Int) : User{

    override val nickname = getFacebookName(accountId)


    fun getFacebookName : String{

        // return something

    }

}



-

interface User{

    val email: String // abstract

    val nickname: String // default function 이라고 이해할 수 있음

        get() = email.substringBefore(‘@‘)

}




4.2.4. Accessing a backing field from a getter or setter


-

class User(val name: String) {

    var address: String = “unspecified”

        set(value: String){

            println(“””address was changed for 

$name:”$field” -> “$value”.”””.trimIndent())

            field = value

        }

}


field 를 통해 위의 예제에서는 address 값을 읽어올 수 있다. ( accessor 에서만 쓸 수 있다. )

이 field 를 backing field 라고 부르는데 accessor 중 하나에서 default 구현을 쓰면 자동 생성된다.

또는 custom accessor 에서 field 를 이용해서 reference 를 호출하면 생성된다.


그럼 그냥 address 를 읽으면 되는데 왜 field 를 쓰냐?

만약 위의 예제에서 address 에 대해 custom getter, setter 를 사용한다면 실제로는 address 라는 변수는 만들어지지 않았다고 보면 된다.




4.2.5. Changing accessor visibility


-

기본적으로 accessor 의 visibility 는 property 의 것과 동일하다.

그러나 visibility modifier 를 붙여주어 조절할 수도 있다.

class LengthCounter{

    var counter: Int = 0

        private set


    fun addWord(word: String){

        counter += word.length

    }

}


cf) class 내부에서 property 접근하는 경우도 무조건 getter, setter 가 이용된다.



-

이것들은 나중에 나오는 내용이지만 이런 특성도 있다는 거 알아두자.


non-null property 에 lateinit modifier 를 써주면 constructor 가 호출된 후에 init 해도 된다. ( 예를 들어 member 로 val 을 가지고 있으면 원래 class init 과 동시에 initialize 를 해줘야 한다. )


lazy init property 는 delegated properties 의 하나이다. 

@JvmField annotation 을 써주면 accessor 없이 public field 를 노출할 수 있다.


const 는 primitive type 이나 String 을 annotation argument 로 사용할 수 있게 한다.


( 지금은 무슨 말인지 사실 하나도 모를꺼당. ㅋㅋㅋ 나중에 공부 다 하고 다시 보시라~ )





반응형

댓글