[Kotlin Tutorial] 클래스, objects, 그리고 인터페이스 #1 - Chap4. Classes, objects, and interfaces |
참조 : Kotlin in Action
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 로 사용할 수 있게 한다.
( 지금은 무슨 말인지 사실 하나도 모를꺼당. ㅋㅋㅋ 나중에 공부 다 하고 다시 보시라~ )
'프로그래밍 놀이터 > Kotlin, Coroutine' 카테고리의 다른 글
[Kotlin Tutorial] 람다로 프로그래밍 하기 - Chap5. Programming with Lambdas (2) | 2017.08.16 |
---|---|
[Kotlin Tutorial] 클래스, objects, 그리고 인터페이스 #2 (0) | 2017.08.14 |
[Kotlin Tutorial] 함수 정의하고 호출하기 #2 (0) | 2017.08.03 |
[Kotlin Tutorial] 함수 정의하고 호출하기 #1 - Chap 3. Defining and calling functions (0) | 2017.08.03 |
[Kotlin Tutorial] Kotlin 기초 #2 - Chap2. Kotlin basics (0) | 2017.07.25 |
댓글