1. KClass
java 에는 Class 라는 타입(클래스) 가 있듯이,
Kotlin 에는 KClass 라는 타입이 있다.
1. KClass 타입
val stringType = String::class
2. Class 타입
val stringType = String::class.java
KClass 타입도 Class 타입과 마찬가지로, 여러가지 함수를 제공한다.
프로퍼티
사용 | 반환타입 | 설명 |
constructors | Collection<KFunction<T>> | 선언된 생성자의 Collection |
isAbstract | Boolean | 추상 클래스 여부 |
isCompanion | Boolean | Compaion object 클래스 여부 |
isData | Boolean | Data 클래스 여부 |
isFinal | Boolean | 클래스가 final 인지 여부 |
isFun | Boolean | kotlin functional interface 인지 여부 |
isInner | Boolean | inner class 여부 |
isOpen | Boolean | open 인지 여부 |
isSealed | Boolean | sealed 클래스 여부 (이글 2번쨰 주제에 설명) |
사용 | 반환타입 | 설명 |
isValue | Boolean | value (값) 클래스인지 여부 |
members | Collection<KCallable<*>> | 접근 가능한 모든 함수와 프로퍼티. 해당 클래스와 상위 클래스를 포함. 생성자는 미포함. |
nestedClasses | Collection<KClass<*>> | 내부 중첩 클래스의 Collection |
objectInstance | T? | object 클래스라면 해당 객체를 반환 (object 클래스는 싱글톤이므로) |
qualifiedName | String? | FQCN |
sealedSubclasses | List<KClass<out T>> | 클래스의 하위 타입 중 sealed 클래스의 리스트 |
simpleName | String? | 패키지 경로가 포함되지 않은 클래스 이름 |
supertypes | List<KType> | 클래스의 상위 타입의 리스트 |
typeParameters | List<KTypeParameter> | 타입 파라미터의 리스트 (제네릭 클래스 타입 (T, V 같은거)) |
사용 | 반환타입 | 설명 |
visibility | KVisibility? | 가시성 (PUBLIC, PRIVATE ..) |
anotations | List<Annotation> | 클래스에 붙은 에너테이션 리스트 |
외에도, 멤버 함수로 isInstance(value: Any?) 처럼 어떤 객체가 해당 KClass 타입인지 검사 할 수 있는 함수도 있고,
여러 확장 프로퍼티와 확장 함수들이 있다.
https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.reflect/-k-class/
이 중에 createInstance() 메서드로 KClass 객체를 해당 클래스의 인스턴스로 만들 수 있다.
val stringType = String::class
stringType.createInstance() // 객체 생성
생성자가 없거나, 기본값이 있는 생성자가 있는 경우에만 만들어 준다.
기본값이 없고, 생성자가 인수를 받아야 할 때에는 IllegalArgumentException 이 발생한다.
https://www.baeldung.com/kotlin/kclass-new-instance
2. Sealed class
open 클래스를 상속 받는 하위 클래스는 여러 파일에 존재할 수 있다. 따라서 관리하기 힘들다. 컴파일러가 얼마나 많은 하위 클래스가 있는지 알지도 못한다.
sealed 키워드도 해당 클래스를 open 처럼 상속을 할 수 있게 만들어준다.
차이점은, 동일 파일에 정의된 하위 클래스 외에 다른 하위 클래스는 존재하지 않는다는 것을 컴파일러에게 알려준다.
즉, 동일 파일에서만 상속할 수 있는 클래스를 만들어 준다.
코드상으로는 private 생성자를 가진 abstract 클래스로 만들어 준다.
하위 클래스가 될 수 있는 클래스를 제한하여 얻을 수 있는 이점 중 하나는 when 절에서 else 를 사용하지 않아도 된다는 것이다.
반대로, else 가 없기 때문에 when 절에 모든 하위 클래스 타입을 명시하지 않으면 에러가 발생한다.
enum 으로도 else 없는 when 절을 만들 수 있지만, enum 클래스의 상수들은 singleton 객체이지만,
sealed 클래스의 하위 타입들은 필요하면 여러 객체를 만들 수 있다.
https://codechacha.com/ko/kotlin-sealed-classes/
3. 고차 함수: 함수 타입
class a : (String) -> String {
...
위 a 라는 클래스는 (String) -> String 이라는 것을 구현했다.
인터페이스도 아닌 것이 클래스도 아니다.
이는 함수 타입 이다.
이 함수 타입은 컴파일 시 인터페이스로 바뀐다.
구체적으로 Function0<R> (인자가 없는 함수), Function1<P1, R> (인자가 하나만 있는 함수 ) 와 같은 인터페이스 타입으로 바뀐다.
이 FunctionN 인터페이스는 공통적으로 invoke() 라는 하나의 메서드가 정의되어 있다.
(String) -> String 과 같은 함수 타입의 람다 식이 이 invoke() 의 구현부로 들어가게 되고,
invoke() 를 실행하면 함수 타입이 실행되게 되는 것이다.
한마디로,
람다 = invoke() 를 가진 인터페이스 이다.
4. invoke()
invoke() 는 코틀린에서 예약어처럼 정해진 특별한 연산자이다.
연산자라고 했듯이 operator 키워드가 앞에 붙고, 함수의 이름이 invoke() 라면, 특별한 능력을 얻는다.
class A {
operator fun invoke(str: String): String {
return str.toUpperCase()
}
}
fun main() {
val res = A()("tomas")
println(res) // TOMAS(대문자)
}
operator fun invoke 를 선언했다.
그리고 A() 로 객체를 만들고, A().invoke("tomas") 가 아니라 invoke 를 생략한 A()("tomas") 와 같이 실행했다.