Page tree
Skip to end of metadata
Go to start of metadata

 1. Generics


    1) 타입 파라미터 

  • 기본적으로 Java와 유사한 사용법.

  • Java와 달리 코틀린은 처음부터 Generics를 도입했기 때문에 raw type을 지원하지 않기 때문에 타입 인자를 명시해야 함.

    Generics 사용
    // 타입추론 가능 -> String
    val authors = listOf(“Dmitry”, “Svetlana”) 
    
    //타입을 명시적으로 선언
    val readers : MutableList<String> = mutableListOf() 
    val readers = mutableListOf<String>()
  • 확장 프로퍼티에는 Generic을 사용할 수 있지만 확장이 아닌 일반 프로퍼티는 사용할 수 없다.

    Generics 사용
    val <T> List<T>.penultimate: T
        get() = this[size - 2]
    
    println( listOf(1,2,3,4).penultimate ) //결과 : 3
    
    val <T> x:T = TODO //ERROR : type parameter of aproperty must be used in its receiver type
  • 파라미터 제약 

    - 상한 제한 : 특정 타입의 하위의 타입만 허용

    Generics 사용
    //상한 제한(upper bound)
    //Kotlin
    fun <T : Number> List<T>.sum() : T {   }
    //Java
    <T extends Number> T sum(List<T> list){   }
    
    
    //여러 제약조건 'where'
    fun <T> ensureTrailingPeriod(seq: T) where T : CharSequence, T : Appendable {
       if (!seq.endsWith('.')){
          seq.append('.')
       }
    }
    
    //타입 파라미터의 NULL 여부
    //Null이 허용되는 'Any?'의 하위 타입
    class Processor<T> {}
    //Null이 허용되지 않는 'Any'의 하위 타입
    class Processor<T: Any> {}

     2) 실행 시 동작

  • Generics 사용 시 Java는 타입을 지워 컴파일한다. (type erasure)
  • 코틀린은 inline을 사용하여 type erasure를 막을 수 있다. → reified
  • as 나 as? 로 generic collection을 casting하면 unchecked cast warning이 출력되고 컴파일 시 오류는 발생하지 않지만 runtime에서 ClassCastException이 발생할 수 있다.

    실행 시점의 Generics
    fun printSum(c: Collection<*>) {     //Collection<*>은 Java의 Collection<?>와 유사
        val intList = c as? List<Int>
                ?: throw IllegalArgumentException("List is expected")
        println(intList.sum())
    }
    
    fun main(args: Array<String>) {
        printSum(listOf(1, 2, "3"))
    }
    //3번째 요소 때문에 실행 시 Exception
    //java.lang.ClassCastException: java.lang.String cannot be cast to java.lang.Number
  • 위의 ClassCastException을 피하기 위해 inline function(reified)을 사용. → runtime 시점에 tyep argument를 알 수 있다.
  • 예) _Collection.kt 클래스의 filterIsInstance function → generic으로 받은 타입의 인스턴스만 필터링 하여 반환

  • reified type parameter를 가진 inline function은 Java에서 호출할 수 없다.

    실행 시점의 Generics
    inline fun <reified T> isA(value: Any) = value is T
    
    fun main(args: Array<String>) {
        println(isA<String>("abc"))  //true
        println(isA<String>(123))    //false
    }
  • reified type parameter 사용가능한 부분
    - type check 의 is, !is, as, as?
    - Kotlin reflection API 에서 클래스에 접근 → ::class
    - Java class에 접근 → ::class.java
    - 다른 함수 호출을 위한 type argument
  • reified type parameter 사용 불가능한 부분
    - type parameter를 통해 새로운 인스턴스 생성
    - type parameter class의 companion object 호출
    - non-reified type parameter를 reified type parameter를 가진 함수의 파라미터로 사용
    - class의 type parameter, property, non-inline function


     3) 공변성

  • Any?는 Any의 super type이다. (Nullable이 상위 개념)
  • 공변성이란 상/하위 개념의 클래스가 type parameter에도 적용이 되는 것을 말함.

공변성반공변성무공변성
List<out T>List<in T>MutableList<T>
하위 타입 관계 유지하위 타입 관계 역전하위 타입 관계 성립 X
List<A>는 List<B>의 상위List<B>는 List<A>의 상위아무관계 X
T를 out 위치에 사용T를 in 위치에 사용T를 아무 위치에서나 사용
List<? extends T>List<? super T>
in, out 위치
//P는 in의 위치(파라미터로 소비됨), R은 out위치(return 타입으로 제공됨)
interface Function<in P, out R> {
    operator fun invoke(p: P) : R
}


//무공변성
fun <T: R, R> copyData(source: MutableList<T>,
                       destination: MutableList<R>) {
    for (item in source) {
        destination.add(item)
    }
}


//공변성
fun <T> copyData(source: MutableList<out T>,
                 destination: MutableList<T>) {
    for (item in source) {
        destination.add(item)
    }
}


//반공변성
fun <T> copyData(source: MutableList<T>,
                 destination: MutableList<in T>) {
    for (item in source) {
        destination.add(item)
    }
}


fun main(args: Array<String>) {
    val ints = mutableListOf(1, 2, 3)
    val anyItems = mutableListOf<Any>()
    copyData(ints, anyItems)
    println(anyItems)
}


  • star projection : 제네릭 인자에 대한 정보 없음 → List<*>
  • List<Any?>는 어떠한 타입이든 담을 수 있지만, List<*>는 한가지 타입만 담을 수 있다.
start projection
import kotlin.reflect.KClass

interface FieldValidator<in T> {
    fun validate(input: T): Boolean
}

object DefaultStringValidator : FieldValidator<String> {
    override fun validate(input: String) = input.isNotEmpty()
}

object DefaultIntValidator : FieldValidator<Int> {
    override fun validate(input: Int) = input >= 0
}

object Validators {
    private val validators =
            mutableMapOf<KClass<*>, FieldValidator<*>>()

    fun <T: Any> registerValidator(
            kClass: KClass<T>, fieldValidator: FieldValidator<T>) {
        validators[kClass] = fieldValidator
    }

    @Suppress("UNCHECKED_CAST")
    operator fun <T: Any> get(kClass: KClass<T>): FieldValidator<T> =
        validators[kClass] as? FieldValidator<T>
                ?: throw IllegalArgumentException(
                "No validator for ${kClass.simpleName}")
}

fun main(args: Array<String>) {
    Validators.registerValidator(String::class, DefaultStringValidator)
    Validators.registerValidator(Int::class, DefaultIntValidator)
    println(Validators[String::class].validate("Kotlin"))
    println(Validators[Int::class].validate(42))
}

 2. Annotation

  • 기본적으로 Java와 유사
  • 사용 지점 대상을 선언할 수 있다.
Annotation
//필드, getter, annotation에 선언 가능
@Target(AnnotationTarget.FIELD, AnnotationTarget.PROPERTY_GETTER, AnnotationTarget.ANNOTATION_CLASS)
annotation class MyAnno

@MyAnno
annotation class YourAnno

data class Test(
        @get:MyAnno
        val age : Int?,

        @MyAnno
        val name : String?
)
  • 사용 지점 지원 대상 리스트
     - property : 프로퍼티 전체 ( 자바에서 사용X )
     - field : 프로퍼티에 의해 생성되는 필드
     - get : 프로퍼티의 게터
     - set : 프로퍼티의 세터
     - receiver : 확장함수나 수신 객체 파라미터
     - param : 생성자 파라미터
     - setparam : 세터 파라미터
     - delegate : 위임 프로퍼티의 위임 인스턴스에 담아둔 필드
     - file : 파일 안에 선언 된 최상위 함수, 프로퍼티를 담아두는 클래스

  • Kotlin 코드를 decomplile하면 자주 보이는 Annotation들
     - @Volatile, @Strictfp : 자바와 동일
     - @JvmName : Kotlin에서 정의한 Java 메소드나 필드의 이름 변경
     - @JvmStatic : object, companion object 선언 시 Java에서 static method로 노출
     - @JvmOverloads : 디폴트 파라미터 값이 있는 함수를 자동으로 overload하여 생성
     - @JvmField : property를 getter, setter 없이 Java의 public 필드로 노출


 3. Reflection

  • 코틀린은  runtime 라이브러리의 크기를 줄이기 위해, kotlin-reflect.jar로 별도의 패키징 → 사용을 위해 dependency 추가 필요

   1) 코틀린 리플렉션 API

  • KClass 
     - Java의 java.lang.Class에 해당
     - MyClass::class 의 형태로 얻을 수 있다.
     - 실행 시점에는 Object.javaClass.kotlin 의 형태로 얻는다 → Java의 java.lang.Object.getClass() 형태

    KClass
    class Person(val name: String, val age: Int)
    
    
    fun getKClass() {
        val person = Person("Slipp", 5)
        val clazz = person.javaClass //java.lang.Class
        val kClass = clazz.kotlin  //KClass<Person>
        val kProperties = kClass.memberProperties //Collection<KProperty<Person, *>>
    	kProperties.forEach { println("""${it.name} , ${it.get(person)}""") }
    }
  • KCallable
     - KFunction과 KProperty의 상위 인터페이스이다.

    KCallable
    public interface KCallable<out R> : kotlin.reflect.KAnnotatedElement {
        @kotlin.SinceKotlin public abstract val isAbstract: kotlin.Boolean
    
        @kotlin.SinceKotlin public abstract val isFinal: kotlin.Boolean
    
        @kotlin.SinceKotlin public abstract val isOpen: kotlin.Boolean
    
        public abstract val name: kotlin.String
    
        public abstract val parameters: kotlin.collections.List<kotlin.reflect.KParameter>
    
        public abstract val returnType: kotlin.reflect.KType
    
        @kotlin.SinceKotlin public abstract val typeParameters: kotlin.collections.List<kotlin.reflect.KTypeParameter>
    
        @kotlin.SinceKotlin public abstract val visibility: kotlin.reflect.KVisibility?
    
        public abstract fun call(vararg args: kotlin.Any?): R
    
        public abstract fun callBy(args: kotlin.collections.Map<kotlin.reflect.KParameter, kotlin.Any?>): R
    }
  • KFunction
     - Java 의 메소드 개념
     - KCallable의 하위 클래스이다.
     - KFunction0 는 파라미터가 없는, KFunction1은 파라미터가 1개, KFunctionN은 파라미터가 N개인 함수이며 N의 수는 컴파일러가 생성한 합성 타입을 사용하기 때문에 원하는 수 만큼의 KFunction이 사용 가능하다.

KFunction
fun foo(x: Int) = println(x)

fun main(args: Array<String>) {
    val kFunction = ::foo //KFunction1 타입
    kFunction.call(42)
}
  • KProperty
     - Java의 필드 개념
     - KCallable의 하위 클래스이다.

     - Kproperty의 call함수는 getter를 호출한다.

    KProperty
    var counter = 0
    class Person(val name: String, val age: Int)
    
    fun main(args: Array<String>) {
        val kProperty = ::counter
        kProperty.setter.call(21)
        println(kProperty.get())
    
    
    	val person = Person("Alice", 29)
    	val memberProperty = Person::age
    	println(memberProperty.get(person))
    }

 2) 코틀린 리플렉션 인터페이스 계층도


 4. 실습 - Html tag Escaper 만들기

 Input 파라미터 중 문자열로 들어오는 파라미터 들에 대해 html tag를 escape 처리를 하는 유틸 작성한다.

  1) 조건

    - 모든 Input은 불특정 data class를 통해 들어온다.

    - 프로퍼티 중 String 타입이며 특정 Annotation이 있는 프로퍼티들에 대해 Escape 처리를 한다.

    - Escape 처리를 위한 로직은 Apache commons text 라이브러리를 사용한다. → StringEscapeUtils

  2) 의존성

   - Apache commons text : compile group: 'org.apache.commons', name: 'commons-text', version: '1.3'

   - Kotlin reflection : compile group: 'org.jetbrains.kotlin', name: 'kotlin-reflect', version: '1.2.41'

  3) 추가 조건

   - data class의 프로퍼티가 다른 data class 타입일 경우, collection 타입일 경우 내부의 문자열을 escape 처리하기 위한 방법


HtmlTagEscaper
@Target(AnnotationTarget.PROPERTY)
annotation class Escape

data class Job(val name: String, val phone: Int?)
data class Person(
        @property:Escape
        val name: String,
        val nickName: String?,
        val age: Int?,
        val job: Job?)

fun main(args: Array<String>) {
    val person = Person("<script>이름</script>", "nick", 10, Job("dev", 1234))
    val props = person::class.declaredMemberProperties

    props
    .filter {
        (it.returnType.javaType == String::class.javaObjectType) &&
        it.findAnnotation<Escape>() != null
    }
    .forEach {
        it.javaField?.let {
            it.isAccessible = true
            it.set(person, StringEscapeUtils.escapeHtml4(it.get(person).toString()))
        }
        println(it.getter.call(person))
    }
}


 참고자료



  • No labels