| 클래스 혹은 메서드를 사용할 때 타입을 명시하는 기법
 | // 제네릭 클래스
 |  | class MyClass<T> {     var info: T? = null; }   val obj1: MyClass<String> = MyClass<String>(); obj1.info = "test";   val obj2: MyClass<Int> = MyClass<Int>(); obj2.info = 111; | cs | 
 | 
 
 | // 타입 유추에 의한 제네릭
 |  | class MyClass<T>(no: T) {     var info: T? = null; }   val obj1: MyClass<Int> = MyClass<Int>(10); obj1.info = 111;     val obj2 = MyClass("hello"); obj2.info = "world"; | cs | 
 | 
 obj2 프로퍼티에 MyClass인스턴스 생성시 타입지정을 하지 않고 생성자에 바로 문자열 파라메터를 전달해도 에러가 나지 않고 타입 유추로 인해 정상 컴파일&실행 된다. | // 여러 개의 형식 타입 선언
 |  | class MyClass<T, A> {     var info: T? = null;     var data: A? = null; }   val obj1: MyClass<String, Number> = MyClass<String, Number>(); obj1.info = "test"; obj1.data = 111;  // Int타입 데이터 obj1.data = 111.10f;  //Float타입 데이터 obj1.data = 111.110;  // Double타입 데이터 | cs | 
 | 
 
 | // 타입 제약 조건 설정
 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | interface IMyInterface1 interface IMyInterface2   class MyHandler1 : IMyInterface1, IMyInterface2 {} class MyHandler2 : IMyInterface1 {} open class MyHandler3 {} class MyHandler4 : MyHandler3() {}   // IMyInterface1 && IMyInterface2인터페이스를 상속 받은 객체 타입만 가능 class MyClass1<T> where T : IMyInterface1, T: IMyInterface2 {     // }   // MyHandler3 타입 또는 MyHandler3을 상속 받은 객체 타입만 가능 class MyClass2<T> where T : MyHandler3 {     // }   val obj1: MyClass1<MyHandler1> = MyClass1<MyHandler1>(); val obj2: MyClass2<MyHandler4> = MyClass2<MyHandler4>(); | cs | 
 | 
 제네릭의 형식 타입은 기본으로 Null을 허용한다.따라서 만일
 위와 같이 선언하였다면 이는 로 선언한 것과 같다. Any클래스는 코틀린에서 모든 객체의 최상위 객체이므로Any?로 선언하므로 실사용 때 어떤 타입도 지정할 수 있고 Null도
 허용한다는 의미이다.
 | | class MyClass<T> {     fun myFun(arg1: T, arg2: T) {         // arg1파라메터는 null허용이기에 '?'로 사용해야 한다.         println(arg1?.equals(arg2));     } }   val obj1: MyClass<String?> = MyClass<String?>(); obj1.myFun("test", "test");   val obj2: MyClass<String?> = MyClass<String?>(); obj2.myFun(null, "test"); | cs | 
실행결과true
 null
 | 
 Null을 허용하고 싶지 않을때는 <T: Any?>가 아닌 Any타입으로 명시하면 된다. | | // null 허용하지 않는 제네릭 타입 class MyClass<T: Any> {     fun myFun(arg1: T, arg2: T) {         println(arg1.equals(arg2));     } } val obj: MyClass<String> = MyClass<String>(); obj.myFun(null, "test");  // 오류 | cs | 
 | 
 
 제네릭의 타입 선언시 ‘out’을 붙이면 해당 제네릭 파라메터는 read만 가능하고 상속받은 자식 타입을 부모 타입에 대입하여 사용 가능하게 할 수 있다.
 | | class MyClass<T>(private val data: T) {     fun myFun(arg: T): T {         return data;     } } fun some(arg: MyClass<Number>) {     arg.myFun(20); } this.some(MyClass<Number>(10)); this.some(MyClass<Int>(10));  // 오류 | cs | 
 | 
 제네릭 형식이 Number타입이라 Int타입 사용은 오류 발생 하지만 제네릭 타입에 ‘out’을 붙이면 사용 가능하다. | | class MyClass<T>(private val data: T) {     fun myFun(arg: T): T {         return data;     } }   fun some(arg: MyClass<out Number>) {     arg.myFun(20);  <- 오류 }   this.some(MyClass<Number>(10)); this.some(MyClass<Int>(10)); | cs | 
 | 
 제네릭 타입에 out을 붙여 Int타입 제네릭으로 사용 가능하지만 out은 read만 가능하므로해당 제네릭의 파라메터에서 관련 타입 맴버는 접근할 수 없기에 오류가 발생한다.
 ‘out’과 반대로 write만 가능하고 부모 타입을 자식 타입에 대입하여 사용 가능하게 할 수 있다. | | class MyClass<T>(private val data: T) {     fun myFun(arg: T): T {         return data;     } }   fun some(arg: MyClass<in Int>) {     arg.myFun(20); }     this.some(MyClass<Number>(10)); this.some(MyClass<Int>(10)); | cs | 
 | 
 제네릭 형식이 Int타입이면서 ‘in’을 붙였기에 해당 제네릭 타입에 Int클래스의 부모인 Number클래스 사용이 가능하다.in과 out의 또 다른 예제 이다.
 | | interface IOutput<T> {     fun isArgument(argument: T): Boolean; }   class MyClass1: IOutput<String> {     override fun isArgument(argument: String): Boolean = if (argument.equals("test")) true else false; }   class MyClass2: IOutput<String> {     override fun isArgument(argument: String): Boolean = if (argument.equals("aaa")) true else false; }   fun printAll(items: ArrayList<IOutput<String>>) {     val obj2 : MyClass2 = MyClass2();     items.add(obj2);     items.indices         .filter { items[it].isArgument("aaa") }         .forEach { println("item : ${items[it].isArgument("aaa")}"); }; }   val items = ArrayList<IOutput<String>>(); val obj1 : MyClass1 = MyClass1(); val obj2 : MyClass2 = MyClass2();   items.add(obj1); items.add(obj2);   this.printAll(items); | cs | 
실행 결과item : true
 item : true
 | 
 printAll메서드의 제네릭 파라메터 items의 제네릭 타입 IOutput은 어노테이션이 붙지 않아 자유롭게 해당 배열에 아이템을 추가하고, 읽을 수 있다.하지만 ‘out’를 붙이면 read만 가능하기에 items.add구문에서 오류가 발생한다.
 | | interface IOutput<T> {     fun isArgument(argument: T): Boolean; }   class MyClass1: IOutput<String> {     override fun isArgument(argument: String): Boolean = if (argument.equals("test")) true else false; }   class MyClass2: IOutput<String> {     override fun isArgument(argument: String): Boolean = if (argument.equals("aaa")) true else false; }   fun printAll(items: ArrayList<out IOutput<String>>) {     val obj2 : MyClass2 = MyClass2();     items.add(obj2);  // 오류     items.indices         .filter { items[it].isArgument("aaa") }         .forEach { println("item : ${items[it].isArgument("aaa")}"); }; }   val items = ArrayList<IOutput<String>>(); val obj1 : MyClass1 = MyClass1(); val obj2 : MyClass2 = MyClass2();   items.add(obj1); items.add(obj2);   this.printAll(items); | cs | 
 | 
 ‘in’을 사용했을 때 는 write만 가능하기에 반대로 items.add구문은 오류가 없지만 배열을 읽는 부분은 오류가 발생한다. | | interface IOutput<T> {     fun isArgument(argument: T): Boolean; }   class MyClass1: IOutput<String> {     override fun isArgument(argument: String): Boolean = if (argument.equals("test")) true else false; }   class MyClass2: IOutput<String> {     override fun isArgument(argument: String): Boolean = if (argument.equals("aaa")) true else false; }   fun printAll(items: ArrayList<in IOutput<String>>) {     val obj2 : MyClass2 = MyClass2();     items.add(obj2);     items.indices         .filter { items[it].isArgument("aaa") }  <- 오류         .forEach { println("item : ${items[it].isArgument("aaa")}"); };  <- 오류 }   val items = ArrayList<IOutput<String>>(); val obj1 : MyClass1 = MyClass1(); val obj2 : MyClass2 = MyClass2();   items.add(obj1); items.add(obj2);   this.printAll(items); | cs | 
 | 
 
 
 스타 프로젝션(Projection)은 제네릭 타입을 <*>로 표현하는 것을 의미한다.스타 프로젝션은 제네릭 타입을 모른다는 의미이다. 나중에 정확한 타입으로 이용되기는
 하지만 지금은 어떤 제네릭 타입이 지정될지 모른다는 의미로 사용된다. 스타 프로젝션은 제네릭의 선언
 측에서는 사용할 수 없고 이용축에서만 사용할 수 있다.
 | | class MyClass<*>  <- 오류 class MyClass2<T>   fun myFun(arg: MyClass2<*>) { }  // 이렇게 제네릭을 이용하는 축에서 사용 | cs | 
 | 
 _<Any?>와 _<*>의 차이 | | val list1: MutableList<Any?> = mutableListOf<Any>(10, 10.0, "test"); <- 오류 val list2: MutableList<*> = mutableListOf<Any>(10, 10.0, "test"); | cs | 
 | 
 list1은 Any? 제네릭타입에 Any타입 대입으로 오류가 발생하지만list2는 제네릭 타입이 <*>이므로 Any타입을 발생해도 오류가 발생하지 않는다.
 제네릭 타입을 실행 시점에 알아내기 위해서는 reified키워드를 이용해야 한다. Reified는 인라인 메서드에서만 사용할 수 있다.
 | | fun <T>some(arg: Any) {     if(arg is T) {         println("true");     }     else {         println("false");     } } | cs | 
 | 
 위 코드는 “Cannot check for instance of erased type: T”오류가 발생한다.제네릭 타입 대상으로 as와 is 연산자를 사용헐려면 reified키워드를 이용한다.
 | | inline fun <reified T>some(arg: Any) {     if(arg is T) {         println("true");     }     else {         println("false");     } } | cs | 
 | 
 
 |