Function
Top Level Function & Property
코틀린은 자바와 달리 클래스 내부가 밖에 최상위 함수와 프로퍼티를 생성할 수 있다. 이를 정적인 유틸리티 클래스 대신 사용하면 유용하다. 이런 최상위 함수와 프로퍼티는 실제로는 클래스 파일 이름에 Kt 라는 접미사를 붙힌 클래스(FilenameKt
)의 정적 메소드, 변수로 변환되어 사용된다.
// String.kt
val prop1: String = "Immutable Top Level Property"
var prop2: String = "Mutable Top Level Property"
fun topLevelFunc(): Unit {
...
}
// String.java
public class StringKt {
public static final String Prop = "Immutable Top Level Property";
public static String prop2 = "Mutable Top Level Property";
public static void topLevelFunc() {
...
}
}
Extension Function & Property
코틀린에서는 최상위 함수와 프로퍼티를 활용해 다른 클래스를 확장할 수 있다. 이를 확장 함수, 확장 프로퍼티라 한다. 자바 또는 코틀린으로 작성된 클래스, 외부 라이브러리를 확장하고 싶다면 확장 함수와 프로퍼티를 유용하게 사용된다.
확장 함수, 프로퍼티를 정의하는 방식은 심플하다. 함수, 프로퍼티 이름에 확장하려는 클래스 이름을 접두사로 붙여주면 된다. 이 때 확장하려는 클래스를 수신 객체 타입
이라 하고 함수 내부에서 사용되는 인스턴스를 수신 객체
라 한다. 수신 객체는 this
를 통해 접근하거나 this
를 생략하고 접근하는 것도 가능하다
// ExtFunc.kt
class ExtFunc {
val prop = "string"
}
// 확장 함수
fun ExtFunc.addPrefix(): String {
return "$prop!"
}
fun main() {
val inst = ExtFunc()
println(inst.addPrefix())
}
위와 같이 인스턴스의 메소드처럼 확장 함수를 사용할 수 있다.
유의점 - 접근 범위
하지만 외부에 게터 또는 세터가 정의되어 있지 않은 private, protected 변수와 메소드에는 접근하거나 조작할 수 없다. 그 이유는 확장 함수, 프로퍼티가 만들어지는 방식에 있다. 확장 함수는 결국 최상위 함수의 일종이다. 실제 클래스 내부에 코드가 삽입되는 것이 아니라 최상위 클래스에 담긴 함수의 인자로 인스턴스를 전달하는 방식으로 구현되기 때문이다. 즉 같은 클래스에 존재하는 것이 아니기 때문에 접근할 수 없다는 것에 유의.
// decompiled file
public class ExtFuncKt {
public static String addPrefix(ExtFunc inst) { // 인자로 전달된 인스턴스
return inst.getProp() + '!'; // 게터와 세터를 통해 접근한다
}
}
유의점 - import
한 클래스의 메소드처럼 보이나 실제로는 다른 클래스에 코드가 존재하기 때문에 확장 함수를 사용하기 위해서는 인스턴스의 클래스 외에 확장 함수를 import 해야 한다는 것에 유의.
가변 인자 함수
자바에서 ...
를 사용해 가변 인자를 사용한 것처럼 코틀린에서는 인자 이름 앞에 vararg
변경자를 사용해 가변 인자를 다룬다. 자바와의 차이점이라면 자바에서는 배열을 가변 인자로 넘겼으나 코틀린에서는 스프레드 연산자 *
를 사용해 넘겨주어야 한다는 점이다.
Infix Call
코틀린에서는 유일한 인자를 가지는 메소드 또는 확장 함수는 중위 호출
을 사용할 수 있다. 말로는 잘 이해가 되지 않으니 예제를 살펴보자
data class Person(var name: String, var age: Int)
infix fun String.lived(age: Int) = Person(this, age)
fun main() {
println("Taekg" lived 26)
} // Person(name="Taekg", age=26)
infix 변경자를 함수 선언 앞에 추가하면 수신객체 함수이름 인자
형식으로 호출 할 수 있다. 즉 중위 호출은 함수 이름을 중간에 표기하는 중위 표기로 함수를 호출 하는 것이다.
Class
Constructor
Primary Constructor
코틀린은 생성자를 만들 때 constructor
와 init
키워드를 사용한다. 주 생성자는 클래스 선언과 동시에 주 생성자를 선언한다.
// 주 생성자의 원래 모습
class User constructor(_nickname: String) {
val nickname: String
init {
nickname = _nickname
}
}
// 축약된 주 생성자
class User(val nickname: String)
// 디폴트 파라미터도 지정 가능
class User(val nickname: String = "default")
주 생성자로 프로퍼티 선언과 초기화를 동시에 수행하고 init
초기화 블록을 통해 필요한 코드를 실행한다.
Secondary Constructor
코틀린에서 여러개의 생성자가 필요한 경우는 거의 없으나 프레임워크나 자바와의 상호 운용성 때문에 생성자가 여러개 필요한 경우가 있다. 이런 상황에서 부 생성자를 사용하면 된다. 부 생성자는 클래스 내부에서 constructor
키워드를 사용하여 만들 수 있다. 또 다른 생성자에게 초기화를 위임할 수 있는데 this()
, super()
를 통해 위임한다
Default Constructor
모든 프로퍼티에 디폴트 파라미터를 지정하거나 별도로 생성자를 정의하지 않으면 코틀린 컴파일러가 빈 기본 생성자를 만들어준다.
Inner & Nest Class
코틀린에서 클래스 내부에 클래스를 선언하면 기본적으로 중첩 클래스이다. 자바에서 static class로 선언한 것과 같으며 외부 클래스에 접근할 수 없다. 외부 클래스에 접근하고 싶다면 inner 변경자를 붙여 선언하고 this@Outer
를 사용해야한다.
Sealed Class
코틀린의 봉인된 클래스 열거형의 특징을 가지는 클래스이다. 기본적으로 몇가지 특성을 가진다.
- 추상 클래스
- private 생성자
- 봉인된 클래스를 상속하는 클래스는 중첩 클래스 또는 같은 파일 안에만 존재해야한다
- 열거형과 같이 when 절에서 else 분기 처리가 필요없다
열거형과 같이 사용하되 클래스의 이점을 필요로한다면 봉인된 클래스를 사용하는 것을 고려해볼수 있다.
Class Delegation
상속을 허용하지 않는 클래스에 새로운 동작을 추가해야 할 때가 있다. 확장하려는 클래스가 인터페이스를 구현해야한다면 데코레이터 패턴을 구현하기 위해 데코레이터 클래스에 인터페이스를 모두 구현해야 한다. 코드로 살펴보자
class DelegatingCollection<T> : Collection<T> {
private val innerList = arrayListOf<T>()
override val size: Int
get() = innerList.size
override fun isEmpty(): Boolean = innerList.isEmpty()
...
}
인터페이스의 모든 메소드를 구현해야하기에 많은 코드가 필요하다. 코틀린의 클래스 위임을 사용하면 인터페이스 구현을 다른 객체에 위임할 수 있다. 클래스 위임을 사용한 코드는 아래와 같다
class DelegatingCollection<T> {
private val innerList = arrayListOf<T>()
} : Collection<T> by innerList()
object Keyword
자바에서는 private 생성자를 사용해 싱글톤 클래스를 구현한다. 코틀린은 object
키워드를 사용해 클래스를 생성하면 간단하게 싱글톤 클래스로 만들 수 있다.
object Payroll {
val allEmployees = arrayListOf<Person>()
fun calculateSalary() {
...
}
}
fun main() {
val salary = Payroll.cacluateSalary()
}
class
대신 object
로 클래스를 생성해 싱글톤 클래스로 만들었다. 자바처럼 getInstance()
정적 메소드로 인스턴스를 받을 필요없이 정적 메소드 처럼 싱글톤 객체를 사용하면 된다. 유의할 점은 object
클래스는 생성자를 사용할 수 없다는 점이다.
무명 클래스
object
키워드는 무명 클래스를 정의하는 데도 사용한다. 이 때는 싱글톤으로 객체를 생성하는 것이 아닌 매 호출 마다 새 객체를 생성한다. 자바와 마찬가지로 무명 클래스는 무명 클래스가 정의된 함수의 변수에 접근할 수 있는 특징을 가지고 있다.
companin object
코틀린은 정적 메소드, 변수가 존재하지 않는다. 정적 메소드와 변수를 대신하는 최상위 함수와 프로퍼티가 존재하지만 비공개 필드나 메소드에 접근할 수 없다는 문제가 있다. 클래스 내부에서 companion object
블록 내에 프로퍼티와 메소드를 정의하면 자바에서 정적 메소드와 변수를 사용하는 것 처럼 사용할 수 있고 내부 비공개 필드나 메소드에 접근해 팩토리 메소드를 만드는데 유용하다.
@JvmStatic
동반 객체가 정적 필드, 메소드처럼 동작하지만 자바 코드로 변환 시 실제로 정적 메소드와 변수로 변환되는게 아니다. Companion
이라는 중첩 클래스를 만들고 Companion
이라는 정적 변수에 저장하고 이 정적 변수를 통해 동반 객체에 접근해 로직을 수행한다.
자바 기반 프레임워크, 라이브러리를 사용할 때 실제 정적 메소드, 필드가 필요한 경우가 있다. @JvmStatic
어노테이션을 사용하면 자바 코드로 변환 시 정적 메소드, 필드도 같이 만들어준다.
'Language > Kotlin' 카테고리의 다른 글
[Kotlin] 코루틴의 이해와 사용 (0) | 2022.12.14 |
---|---|
[Kotlin] 코루틴과 비동기 (0) | 2022.12.10 |
[Kotlin] BigDecimal (0) | 2022.12.02 |
[Kotlin] Lambda & High-Order Function (0) | 2022.11.21 |