29 Aug 2019
|
Kotlin
내부(inner) 클래스와 중첩(nested) 클래스
코틀린 공식문서와 코틀린 인 액션
책을 참고하여 작성하였습니다.
자바에서는 A 클래스 안에 B 클래스를 정의하면 B 클래스는 자동으로 내부 클래스가 되었습니다. 하지만 코틀린에서는 반대입니다. 한 클래스안에 다른 클래스를 정의하면 기본적으로는 중첩 클래스가 되고, 내부 클래스로 만들고 싶다면 inner
키워드로 클래스를 선언해야 합니다. 내부 클래스는 기본적으로 외부 클래스를 참조하게되지만 중첩 클래스는 그렇지 않습니다. 아래의 예시를 보겠습니다.
// nested class 중첩 클래스
class Outer {
private val bar: Int = 1
class Nested {
fun foo() = 2
}
}
val demo = Outer.Nested().foo() // == 2
위와 같이 중첩 클래스에서는 외부 클래스를 참조하지 않기 때문에 Outer.Nested().foo()의 값이 2가 됩니다.
// inner class 내부 클래스
class Outer {
private val bar: Int = 1
inner class Inner {
fun foo() = bar
}
}
val demo = Outer().Inner().foo() // == 1
반면, 내부 클래스에서는 외부 클래스를 항상 참조하고 있기 때문에 Outer().Inner().foo()의 값이 1이 됩니다. 객체를 항상 참조하고 있다는 것은 어떤 의미를 가질까요? (자바에서) 객체가 삭제되는 시점은 객체가 더 이상 사용되지 않을 때입니다. 그런데 내부 클래스를 사용하면 항상 외부 클래스의 객체를 참조하기때문에 객체가 적절한 시점에 삭제되지 못하고 메모리 누수가 발생합니다.
이러한 누수가 위험한 이유는 명시적(컴파일 시 오류가 발생하는 등 명식적으로 알 수 있는 것)인 것이 아니라 암묵적(프로그래머가 발견하기 전까지는 알 수 없는 것)인 것이기 때문입니다. 따라서 특별한 경우가 아니라면 내부 클래스 사용을 지양하고 중첩 클래스를 사용하는 것이 권장된다고 합니다.
기존의 자바와 다르게 inner 키워드를 선언해야만 내부 클래스를 사용할 수 있도록 설계한 이유와도 관련이 있을것 같다는 생각이 드는데.. 아시는 분은 알려주시면 감사하겠습니다.
참고 문헌
28 Aug 2019
|
Kotlin
인터페이스(Interfaces)
이 글은 코틀린 공식문서를 공부하며 번역한 글입니다. 틀린 부분이나 어색한 부분을 댓글로 알려주시면 감사하겠습니다.
코틀린 인터페이스에서는 추상메소드와 구현이 있는 메소드 모두 선언할 수 있습니다. 인터페이스와 추상클래스와 다른 점은 인터페이스는 상태(필드)를 저장할 수 없다
는 것입니다. 인터페이스는 추상적이거나 accessor implementations를 제공하는 프로퍼티만을 가질 수 있습니다.
interface MyInterface{
fun bar()
fun foo(){
// optional body
}
}
인터페이스 구현하기(Implementing Interfaces)
인터페이스를 사용하는 방법은 자바보다 간편합니다. 자바에서는 [class] implements [interface]
의 형식으로 구현했지만 코틀린에서는 아래와 같이 클래스 이름 뒤에 콜론(:)을 붙인 뒤 인터페이스 이름을 적으면 됩니다.
class Child : MyInterface{
override fun foo(){
// body
}
}
인터페이스의 프로퍼티(Properties in Interfaces)
인터페이스에서 프로퍼티를 사용할 수 있습니다. 인터페이스에서 선언된 프로퍼티는 추상 프로퍼티이거나 접근자로 구현할 수 있어야 합니다. 인터페이스에 선언된 프로퍼티는 backing fields를 가질 수 없으므로 접근자가 참조할 수 없습니다.
interface MyInterface{
val prop: Int // abstract
val propertyWithImplementation: String
get() = "foo"
fun foo() {
print(prop)
}
}
class Child : MyInterface{
// 코틀린에서는 override 변경자를 꼭 사용해야합니다.
override val prop: Int = 29
}
인터페이스 상속(Interface Inheritance)
인터페이스는 다른 인터페이스에서 상속받을 수 있으며, 상속 받은 인터페이스에서 멤버 구현과 새로운 함수 및 프로퍼티 선언을 할 수 있습니다. 당연히 인터페이스를 구현한 클래스에서는 구현되지 않은 부분만 구현하면 됩니다.
interface Named {
val name: String
}
interface Person : Named {
val firstName: String
val lastName: String
override val name: String get() = "$firstName $lastName"
}
data class Employee(
// implementing 'name' is not required
override val firstName: String,
override val lastName: String,
val position: Position
) : Person
오버라이딩 충돌 해결하기(Resolving overriding conflicts)
상위 타입(supertype)을 많이 선언하면 같은 메소드를 한 번 이상 상속 받는 것처럼 보입니다.
interface A {
fun foo() { print("A")}
fun bar()
}
interface B {
fun foo() { print("B")}
fun bar() { print("bar")}
}
class C : A {
override fun bar() { print("bar")}
}
class D : A, B {
override fun foo() {
super<A>.foo()
super<B>.foo()
}
override fun bar() {
super<B>.bar()
}
}
인터페이스 A와 B 모두 foo()와 bar() 메소드를 선언하고 있습니다. foo() 함수는 A와 B 인터페이스 모두 구현하고 있지만 bar() 함수는 B 인터페이스에서만 구현하고 있습니다( bar() 함수가 인터페이스 A에서 추상메소드로 선언되어 있지 않은 이유는, 함수의 바디가 없다면 이것이 인터페이스의 기본 형태이기 때문입니다). 만약 C 클래스에서 A 인터페이스를 구현한다면, bar() 메소드를 오버라이드하여 구현해야만 합니다.
하지만 만약 D 클래스에서 A와 B 인터페이스를 구현한다면 A와 B 인터페이스에 있는 모든 메소드를 구현해야만 합니다. 또한 D 클래스가 함수들을 어떻게 구현할 것인지 구체적으로 작성해야 합니다. 이 규칙은 단일 구현(single implementation)을 한 bar() 메소드와 다중 구현(multiple implementations)을 한 foo() 메소드 모두에 적용됩니다.
참고 문헌
26 Aug 2019
|
TIL
Kotlin
Today I Learned
Kotlin in Action 3장
3.3.4 확장 함수는 오버라이드 할 수 없다
코틀린은 확장 함수를 정적으로 결정하기 떄문에 확장 함수를 오버라이드 할 수 없다. 만약 어떤 클래스의 확장 함수와, 해당 클래스의 멤버 함수의 이름이 같다면 확장 함수가 아니라 멤버 함수가 호출된다.
3.4.2 가변 인자 함수: 인자의 개수가 달라질 수 있는 함수 정의
코틀린에서는 아래와 같이 파라미터 앞에 vararg
변경자를 붙이면 가변 인자를 사용할 수 있다(자바에서는 타입 뒤에 ...
를 붙였다)
fun listOf<T> (vararg values: T) List<T>{ ... }
3.4.3 값의 쌍 다루기: 중위 호출과 구조 분해 선언
코틀린에는 중위 호출(infix call)
이라는 특별한 호출 방식이 있다. 중위 호출 시에는 수신 객체와 유일한 메소드 인자 사이에 메소드 이름을 넣는다(이 떄 객체, 메소드 이름, 유일한 인자 사이에는 공백이 들어가야 한다). 중위 호출은 인자가 하나인 일반 메소드나, 인자가 하나인 확장 함수에 사용할 수 있다.
1.to("one") // "to" 메소드를 일반적인 방식으로 호출함
1 to "one" // "to" 메소드를 중위 호출 방식으로 호출함
함수를 중위호출에 사용하게 허용하고 싶으면 infix 변경자를 함수 선언 앞에 추가해야 한다.
구조 분해 선언(destructuring declaration)
부분이 이해가 안됨. 다시 정리해보고 여쭤보기.
3.5 문자열과 정규식 다루기
3.5.1 문자열 나누기
코틀린에서는 아래와 같이 하나 이상의 문자를 인자로 받는 split 함수를 사용할 수 있다.
>>>println("12.345-6.A".split(".", "-"))
(12, 345, 6, A)