```
// reified 예약어를 사용해 실체화한 파라미터 선언.
inline fun <reified T> printType() {
println(T::class.simpleName)
}
fun main() {
printType<Int>() // Int
printType<String>() // String
printType<List<Boolean>>() // List
}
// 자바 와일드카드를 이용한 사용자 지정 변성
// Number 클래스의 하위 클래스 타입인 Integer와 Double을 모두 인자로 전달 가능
import java.util.*;
public class Example {
public static void main(String[] args) {
List<Integer> integers = Arrays.asList(1, 2, 3);
List<Double> doubles = Arrays.asList(1.0, 2.0, 3.0);
printList(integers);
printList(doubles);
}
public static void printList(List<? extends Number> list) {
for (Number n : list) {
System.out.println(n);
}
}
}
// 코틀린 선언 지점 변성 을 사용한 타입 인자 제한.
//opy 함수는 from 파라미터가 T 타입의 하위 타입을 받도록 out 키워드를 사용하여 선언
fun <T> copy(from: MutableList<out T>, to: MutableList<T>) {
for (item in from) {
to.add(item)
}
}
fun main() {
val dogs: MutableList<Dog> = mutableListOf(Dog("Rex"), Dog("Fido"))
val animals: MutableList<Animal> = mutableListOf()
copy(dogs, animals)
println(animals) // Output: [Dog(name=Rex), Dog(name=Fido)]
}
// out 키워드를 사용한 예시 코드
interface AnimalShelter<out T> {
fun adoptAnimal(): T?
}
class DogShelter : AnimalShelter<Dog> {
private val dogs: MutableList<Dog> = mutableListOf()
override fun adoptAnimal(): Dog? {
if (dogs.isNotEmpty()) {
return dogs.removeAt(0)
}
return null
}
}
class Animal(val name: String)
open class Dog(name: String) : Animal(name)
class Cat(name: String) : Animal(name)
fun main() {
val dogShelter: AnimalShelter<Dog> = DogShelter()
val animalShelter: AnimalShelter<Animal> = dogShelter
println(animalShelter.adoptAnimal()?.name) // Output: Rex
}
```
일반적으로 제네릭 타입은 컴파일 시점에서 타입 소거가 일어나.
실행시점에 제네릭타입 정보를 알수 없음, 그래서 실체화한 타입파라미터 사용.
타입 정보유지.
선언 지점 변성으로 타입인자 제한해서.. 특정 인자만 사용 가능하도록 제한.
코드 안정성 높임
코드 재사용성 증대
val authors = listOf("Dmitry", "Svetlana")
코틀린 컴파일러는 보통 타입 인자 추론 가능 .
코틀린에서 제네릭 사용시 타입추론이나 타입을 명시해 줘야됨
왜? 타입의 안정성 보장 및 raw 타입 지원하지 않아서.
// List<T> 제네릭 타입을 사용한 예시
List intList = Arrays.asList(1, 2, 3);
List stringList = Arrays.asList("a", "b", "c");
// Map<K, V> 제네릭 타입을 사용한 예시
Map intToStringMap = new HashMap();
intToStringMap.put(1, "one");
intToStringMap.put(2, "two");
intToStringMap.put(3, "three");
Map stringToIntMap = new HashMap();
stringToIntMap.put("one", 1);
stringToIntMap.put("two", 2);
stringToIntMap.put("three", 3);
자바에서 예시 자바는 raw타입 지원해서 타입을 따로 생략해도 된다.
Raw 타입은 제네릭 타입에서 타입 인자를 생략한 것을 의미
원래는 제네릭이 없었기 때문에 타입인자가 없는 제네릭 사용하려면 Raw타입이 필요했다.
if(vlaue is List<*>)
타입 파라미터가 2개 이상이라면 모든 타입 파라미터에 * 포함 시켜야 한다.
인자를 알 수 없는 제네릭 타입으로 표현할때 (자바에선 ? )
쓴다..
와일드 카드를 대체하는 코틀린의 기능 ?
알수 없는 유형 매개변수가 있는 일반 유형 처리할때 유용
fun printContents(box: Box<*>) {
val contents = box.contents
println("Box contains $contents")
}
val list: List<*> = listOf("Hello", 42, true)
와닿지는 않지만 모든 유형을 유연하게 사용가능하는거같다.
fun printSum(c: Collection<*>) {
val intList = c as? List<Int> ?: throw IllegalArgumentException("List is expected")
println(intList.sum())
}
printSum(listOf(1, 2, 3)) // prints 6
printSum(setOf(1, 2, 3)) // throw error
printSum(listOf("a", "b", "c")) //type cast error
이런식으로 예제..
뭐지 ??
타입소거때문에 두번째는 예외가 발생. 실행시점에 타입이 지워졌기때문. 그래서 as는 위험.
as를 사용할땐 조심해야 한다. 잘못된 유형으로 캐스트 할 가능성이 있따.
fun printSum (c: Collection<Int>) (
if (c is List<Int>) { printin (c.sum ())
)
Is 예약어는 컴파일 시점에 타입이 뭔지 명시한다.
is 연산자는 컴파일 시간에 유형 검사를 수행하는 데 사용됨
일반적으로 is 연산자를 사용하여 유형 검사를 수행하는 것이 더 안전
변수나 표현식을 특정 유형으로 캐스트해야 하고 캐스트가 안전하다고 확신하는 경우 'as' 연산자를 사용
아 다시 풀어서 정리하면 Is예약어는 타입을 확인할때 as는 그 타입이 확실하다할때 쓴다.
fun isA(value: Any) = value is T
위 식처럼 제네릭 함수의 타입 인자도 호출시 타입 인자를 알수 없다.
제약을 피하려면?
여기서 다시 Inline 키워드를 붙이면 컴파일러는 함수를 호출한 식을 함수 본문으로 바꿈.
함수가 람다를 인자로 사용하는 경우 그 함수를 인라인으로 만들면?
뭐 성능좋은얘기만 나오고..
inline fun <reified T> isA(value: Any) = value is T
println(isA<String>("abc")) // Output: true
println(isA<String>(123)) // Output: false.
refied 예약 어는 런타임시 유형 매개변수의 런타임 시 유형 매개변수의 유형 정보를 보존하는 데 사용
매개변수가 사용되는 함수 또는 클래스의 범위 내에서 런타임 시 매개변수의 유형 정보를 보존
유형 정보를 보존하기위해 인라인을 해야되는 작업이 필요하다.
인라인으로 표시하면 컴파일러는 유형 매개변수가 런타임시 유형 정보 보존하도록 추가 바이트코드 생성하기 때문에
reified” 키워드는이타입파라미터가 실행시점에 지워지지 않음을 표시한다. (책 참조)
val items = listOf("one", 2, "three")
println(items.filterIsInstance<String>()) // Output: [one, three]
inline fun <reified T> Iterable<*>.filterIsInstance(): List<T> {
val destination = mutableListOf<T>()
for (element in this) {
if (element is T) {
destination.add(element)
}
}
return destination
}
위 정리한 내용에 참고할만한 글
왜 실체화된 파라미터를 사용하지.
인스턴스화된 유형 매개변수를 사용하여 유형 안전성을 강화하고 코드 재사용성을 촉진하며 가독성을 높이고 Java와의 상호 운용성을 유지
사용할 수 있는경우
타입 검사 캐스팅, 리플렉션, 코틀린 타입에 대응하는 java.lang.class얻기, 다른함수 호출할때 타입 인자 사용등.. ?
class Example {
companion object {
fun <T> create(): T {
// This will not compile because we cannot create an instance of a type parameter directly
// return T()
// Instead, we can use reflection to create an instance of the type parameter class
@Suppress("UNCHECKED_CAST")
val clazz = T::class.java as Class<T>
return clazz.newInstance()
}
}
inline fun <reified T> callFunction() {
// We can pass the non-materialized type parameter T to a function that requires a materialized type parameter
val instance = Example.create<T>()
println(instance)
// We can also call a companion object method of a type parameter class
val result = T.someCompanionObjectMethod()
println(result)
}
}
class MyClass {
companion object {
fun someCompanionObjectMethod(): String {
return "Hello from companion object method!"
}
}
}
fun main() {
val example = Example()
example.callFunction<MyClass>()
}
interface Producer<out T> {
fun produce(): T
}
class StringProducer : Producer<String> {
override fun produce(): String {
return "Hello, world!"
}
}
out 예약어를 사용함으로서 타입이 공변적임을 표시. 없으면 무공변성으로 표시.
공변 유형 매개변수를 선언하는 데 사용
out 선언이 없으면 컴파일러가 T가 누구의 하위 클래스인지 여부를 알 수 없기 때문
그러나 읽기는 가능하나 쓰기 변경은 안됌
class Herd<out T : Animal> {
// T is now covariant.
fun takeCareOfCats(cats: Herd<Cat>) {
for (i in 0 until cats.size) {
cats[i].cleanLitter()
feedAll(cats)
}
}
//...
}
// Error: inferred type is Herd<Cat>, but Herd<Animal> was expected.
// fun feedAll(animals: Herd<Animal>) {
// ...
// }
// The Herd class provides an API similar to List, and you cannot add animals to the class or change animals in the herd to other animals.
// So, make Herd a covariant class, and you can change the calling code appropriately.
fun feedAll(animals: Herd<out Animal>) {
//
}
in 위치와 out 위치
in은 소비 out은 생산.
(함수의반환타입>는아웃위치에서만 사용가능 (out)
List는 읽기 전용 타입을 추가하거나 기존 값 변경메서드가없다
이는 List는 T에 대해 공변적이다 라고 한다 책에서.
MutableList를 타입파라미터에 대해 공변적인 클래스로 선언할수는 없 다 는 점 유의.
class Herd<T: Animal> (var leadAnimal: T, vararg animals: T) (. . . )
leadAnimal 프로퍼티가인위치에있기때문에꼬를out으로표시할 수 없다.
변성규칙은클래스외부의사용자가클래스를잘못사용하는일을막기
위한것이므로클래스내부구현에는적용되지않는다.
공변성을 거울에 비친 상
반공변 클래스 하위타팁 관계는 공변 클래스 경우와 반대
책에서는 B는 A의 하위 타입인경우 Consume가 Consume의 하위 타입인 관계가 성립하면 제네릭 클래스 Consume는 타입인자 T에 반공변 이라한다.
A 와 B의 위치가 바뀌었다 . 하위타입의 위치가 바뀌었다.
// 사용 지점 변성예 out 키워드를 사용함으로서 해당 타입은 in 위치에 사용하지 않는다 .
//이때 타입프로젝션이 일어나, source는 일반적인 mutableList가 아니라
// MutableList를 프로젝션한 (제약을가한 ) 타입이 됨.
// 결론적으로 타입파라미터를 아웃위치에서 사용하는 메소드만 호출할수있다
fun <T> copyData(source: MutableList<out T>, destination: MutableList<T>) {
for (item in source) {
destination.add(item)
}
}
///
사용 지점 변성 : 사용하는 위치에 파라미터에 대한 제약 명시
선언 지점: 기저 객체에 사용때 지정 ..
super type token 조사
제네릭을 쓸때 슈퍼타입토큰을 쓴다? 그래서 타입이 지정된다.