본문 바로가기

Programming/Kotlin

[Kotlin]코틀린을 이용한 안드로이드 프로그래밍 실습 04

퀴즈 : 

1. 탑레벨의 고차 함수 TimeLogCheck 를 만든다. 

2. TimeLogCheck 는 String 타입의 tag 와 함수타입 (tag:String) -> List<String>  을 가지는 f 를 인자로 갖는다. 

3. TimeLogCheck 에서 인자로 받은 람다를 실행하기 전에    “현재시간 : tag -> 시작” 이란 로그를 출력하고 람다를 실행한 후에 “현재시간 : tag -> 종료(elapsed time : 종료 – 시작)” 을 출력한다. 

4. TimeLogCheck 는 람다함수가 리턴한 값을 리턴한다.

5. 순서대로 1~1000 까지 가지는 mutableList<Int> 타입의 initList 를 생성한다. 

6. 

  A : 리스트의 filter 로 짝수를 걸러내고, map 에서 Int 를 String 으로 바꾼 String 리스트를 만든다. 

  B : 리스트를 asSequence 를 통해 sequence 로 바꾸고 filter 로 짝수를 걸러내고, map 에서 Int 를 String 으로 바꾼 후 toList 이란 종단 함수를 통해 String 리스트를 만든다. 

8. A와 B 를 timeLogCheck 를 통해 람다로 실행하여 실행시간 로그가 나오도록 한다.  (마지막 인자가 람다일때 () 밖으로 람다를 빼는 방법으로 작성한다)

* 시간 관련 코드는 아래를 참조한다. 

var endTime = GregorianCalendar()
println("${endTime.toZonedDateTime()} : $tag -> End (elapsed time : ${endTime.timeInMillis - startTime.timeInMillis})")

- 출력

2018-07-05T14:16:56.161+09:00[Asia/Seoul] : collection -> Start

2018-07-05T14:16:56.249+09:00[Asia/Seoul] : collection -> End (elapsed time : 88)

2018-07-05T14:16:56.253+09:00[Asia/Seoul] : stream -> Start

2018-07-05T14:16:56.275+09:00[Asia/Seoul] : stream -> End (elapsed time : 22)



# Kotlin - 제너릭 프로그래밍

- 제너릭(C++ template) 프로그래밍

  * 타입을 미리 정의하지 않고 하는 프로그래밍

  * 인스턴스화 하는 시점에 타입 파라미터<>를 통해 타입 지정

  * 타입 파라미터를 통해 여러 타입으로 사용할 수 있는 객체, 함수 생성 : 하나의 코드로 다양한 타입을 처리할 수 있다.

  * 여러 타입을 처리해야할 때 Any (Java Object 클래스) 타입의 문제

  1. 캐스팅이 필요했지만 제너릭은 불필요

  2. 실수로 다른 타입을 전달했을 경우 오류 찾는것이 힘들었지만 제너릭은 사전에 오류를 발생시킴



# Kotlin - 제너릭 클래스

class ClassName<T, S> (var v1: T?  = null, var v2: S? = null)

 * 클래스 타입 = 클래스이름 : 원시타입 + T,S 제너릭 타입

- <> 안에 타입 파라미터 정의

- 타입 파라미터가 하나 이상일 때 콤마(,)로 구분하여 나열

- 타입추론이 가능할 경우 생략 가능

 * listOf<String>("a","b","c") -> listOf("a", "b", "c")

 * 추론이 안될 경우 명시적으로 선언해야함.

 * 원시타입만으로 사용 불가 (Java는 하위 호환을 위해 가능)

- 인스턴스화 시킬 때는 타입파라미터(제너릭)를 넣어줌으로써 된다!

var a = 클래스이름<Int, String> // 타입인자 : <Int, String>

data class TData<T, S> (var v1: T? = null, var v2: S? = null)

val d1 = TData("delay", 23) // <String, Int>
println("d1 : $d1")

val d2 = TData("delay2", 23+10) // <String, Int>
println("d2 : $d2")

val d3 = TData(20, d2) // <Int, TData<String, Int>>
println("d3 : $d3")

- 출력

d1 : TData(v1=delay, v2=23)

d2 : TData(v1=delay2, v2=33)

d3 : TData(v1=20, v2=TData(v1=delay2, v2=33))



# Kotlin - 타입파라미터 : 명명 관례

- 타입 파라미터 이름은 마음대로 지정할 수 있지만 관례가 있음.

- 관례

 * T : Type(기본 값)

 * S, U, V etc. : 2nd, 3rd, 4th types (T 외에도 타입 파라미터가 여러개 필요할 때)

 * E : element - 자바 컬렉션 프레임워크에서 많이 사용

 * K : Key - Map 선언할 때

 * V : Value - Map 선언할 때

 * N : Number



# Kotlin - 제너릭 함수/프로퍼티

- 제너릭 함수

fun <T> newList(vararg ts: T):ArrayList<T>

 * <T> : 타입 파라미터

- 함수를 사용하는 시점에서 특정 타입으로 지정

newList<Int>(1,2,3,4,5)
newList("a","b","c") // 타입 추론

- 제너릭 프로퍼티

 * 확장된 프로퍼티만 제너릭 설정 가능

val <T> List<T>.penultimate: T

- 근데 위에꺼 쳐보면 not allowed 뭐시기 나온다. 그래서 코틀린 공식 홈페이지 문서 참조.

 * 일반 프로퍼티는 제너릭 불가 : val <T> x:T? = null // 불가



# Kotlin - 서브타입, 슈퍼타입

- class subClass : superClass

 * subClass 타입

 * superClass 타입도 가능

- 상속 관계에 있는 타입

 * 자식 입장에서 부모 : 슈퍼타입

 * 부모 입장에서 자식 : 서브타입

- 인터페이스나 abstract도 자식입장에서는 슈퍼타임

- 상속관계에서 서브타입의 모든 슈퍼타입은 자신의 타입



# Kotlin - 원시타입, 제너릭타입

- class ClassName<T>

ClassName :  원시 타입

T : 제너릭 타입

ClassName<T> : 클래스 타입 

- 두 클래스 타입이 서브타입이 되려면 

제너릭 타입이 같아야함

원시타입이 서브타입 관계여야 함.

예외 ) 변성

- ArrayList<Int> 는 List<Int> 의 서브 타입이다. 

제너릭 타입이 Int 로 같고, ArrayList 는 List 를 상속

- ArrayList<Number> 와 List<Int> 는 서브타입이 아니다. 

제너릭 타입인 Int 가 Number 를 상속하고 있지만 서로 다른 타입이다.

제너릭 타입이 같지 않으면 ArrayList 와 List 관계는 무의미


List<Int> != List<Number> //제너릭타입이 달라서 다름

List<Int> = ArrayList<Int> // 할당 가능. 부모로 자식을 가리킬 수 있지만, 자식으로 부모를 가리킬 수 없다.



# Kotlin - 타입 파라미터 제한

- 기본적으로 타입 인자는 모든 타입 사용 가능

- <T: Number> 로 타 입력 파라미터에 Upper Bound 제한 가능

  * String 못들어감

  * Number 를 포함한 서브타입만 인자로 사용 가능

  * 내부에서 제한한 타입의 멤버 접근 가능



# Kotlin - 타입 소거

- 제너릭으로 지정된 타입의 매개변수 정보는 바이트 코드에 수록하지 않고 소거

 * 소거 : List<Int>, List<String> -> List<Object> (자바는 런타임 때 이렇게 바뀜)

- 따라서 타입 매개변수가 지정된 클래스의 객체 타입을 런타임중 검사할 수 없음

 * 해당 타입 파라미터들을 파라미터로 갖는 함수 오버라이드 불가

 * 제너릭 타입은 해당 타입의 is 연산 불가. 타입이 소거되었기 때문(이게 가장 큰 단점. 현재는 편법으로 해결)

- 예외적으로 함수의 경우 inline 과 함께 타입 파라미터에 reified 키워드로 실체화 가능

 * is 연산이 가능해짐

 * 해당 함수는 자바에서 사용 불가



# Kotlin - 변성(variance)

- ArrayList<Int> vs List<Int>

  * ArrayList<Int> 는 List<Int > 의 서브타입

- List<Int> vs List<Number>

  * 제너릭 타입 Int 가 제너릭 타입 Number 의 서브타입이지만 List<Int> 는 List<Number> 의 서브타입 아님. 제너릭이 다르기 때문!

  * 하지만 List<Int> 는 List<Number> 의 서브타입임.

  * 리스트는 타입매개변수의 서브타입을 인자로 받게 정의되어 있음

- 타입 매개변수와 타입 인자 간의 서브 타입 관계 설정을 변성(변형) 이라 함

  * 제너릭 타입의 변환이 생김

- 변성은 클래스나 인터페이스의 타입 매개변수에만 설정 가능 

  * 함수 매개변수나 반환타입에는 지정 불가



# Kotlin - 변성(variance) : 불변성/무공변성

- 타입 매개변수에 아무것도 하지 않으면 불변성(무공변성)

 * default는 불변성(무공변성)

- 제너릭 타입이 일치해야한다. 

- 제너릭 타입의 상속관계 허용 안됨.

interface IMyClazz<T>

open class MyClazzImpl<T>(val v: T) : IMyClazz<T>
data class MySubClazzImpl<T>(val w: T) : MyClazzImpl<T>(w)

fun printMyClazz(my: MyClazzImpl<Number>) {
println(my)
}

val m1 = MyClazzImpl<Int>(1)
val m4 = MyClazzImpl<Number>(222)
val m5 = MySubClazzImpl<Number>(222)
println(m1)
printMyClazz(m4)
//printMyClazz(m1)
printMyClazz(m5)



# Kotlin - 변성(variance) : 공변성(공변형)

- 타입 매개변수에 out 변형 제한자를 지정

 * 자바의 super 변형 제한자와 같다 : 제너릭 타입이 상속관계에 있으면 파라미터로 받아준다.

public interface List<out E> : Collection<E>

- E 타입을 포함하여 E 의 서브 타입도 타입인자가 가능

- List<Int> 는 List<Number> 의 서브

 * 부모(Number)한테는 받아줄 수 있는 것



# Kotlin - 변성(variance) : 불변성/무공변성

- 타입 매개변수에 in 변형 제한자를 지정

 * 자바의 extends 변형 제한자와 같다

 * 공변성의 반대 

public interface Comparable<in T>

- T 타입을 포함하여 T 의 수퍼 타입도 타입인자가 가능

 * 잘 사용되지 않음. 

class InVariance<T>
class CoVariance<out T>
class ContraVariance<in T>

val in1 = InVariance<Int>()
// val in2:InVariance<Number> = in1
val co1 = CoVariance<Int>()
val co2: CoVariance<Number> = co1

val ct1 = ContraVariance<Int>()
// val ct2 : ContraVariance<Number> = ct1
val ct3 = ContraVariance<Number>()
val ct4:ContraVariance<Int> = ct3



# Kotlin - 변성(variance) : 사용지점 변성

- 불변형의 경우 사용할때 in/out 을 강제 지정해서 사용 가능

 * Interface 나 class 에 지정하는것 : 선언 지점 변성(변형)

 * 사용할때 지정 : 사용되는 지점에서 변성(변형)

- 공변형이나 반공변형으로 선언된 경우 불가 (충돌)

class InVariance<T>
class CoVariance<out T>
class ContraVariance<in T>

val in1 = InVariance<Int>()
// val in2:InVariance<Number> = in1
val in3:InVariance<out Number> = in1 // 사용지점변성



# Kotlin - 변성(variance) : 스타 프로젝션

- 원시타입만 사용 불가(자바는 가능)

 * List<Number> : 가능

 * List : 불가능

- 스타 프로젝션 

 * List<*> 

 * 타입 인자를 알 수 없음을 의미

 * List<Any?> 와 다름

  + Any 타입을 받겠습니다!

  + <out Any> : Any가 가지고 있는 속성을 쓸 수 있다. 하지만 *는 쓸 수 없다!

 *타입이 중요하지 않은 함수에 유용

class Star<T>
val st1:Star<Any> = Star<Any>()
// val st2:Star<Any> = Star<Int>()

val st5:Star<out Any> = Star<Int>()

val st6:Star<*> = Star<Any>()
val st7:Star<*> = Star<Int>()


fun printListItem(list : List<*>) {
println("갯수 : ${list.size}")
println("내역 : $list")
// list.add(100) // list…여야만 쓸 수 있어서
}

val ls1 = listOf(1,2,3)
val ls2 = listOf("a", "b", "c")
printListItem(ls1)
printListItem(ls2)


# 출처 : 백지훈 모베란 대표이사님