제네릭(generic)
- 자료형의 객체들을 다루는 메서드나 클래스에서 컴파일 시간에 자료형을 검사해 적당한 자료형을 선택할 수 있도록 하기 위해 사용
- 앵글 브래킷(<>) 사이에 형식 매개변수를 사용해 선언
- 형식 매개변수는 자료형을 대표하는 용어로 T와 같이 특정 영문의 대문자로 사용
형식 매개변수의 이름
- 강제사항은 없으나 일종의 규칙
- E : 요소(element)
- K : 키(key)
- N : 숫자(number)
- T : 형식(type)
- V : 값(value)
- S, U, V etc. : 두번째, 세번째, 네 번째 형식(2nd, 3rd, 4th types)
- 자료형이 추론 가능한 경우, 앵글 브라켓을 생략할 수 있다.
val box3 = Box(1) // 1은 Int형이므로 Box<Int>로 유추함
val box4 = Box("Hello") // "Hello"은 String형이므로 Box<String>로 유추함
형식 매개변수를 한 개 이상 받는 클래스
- 인스턴스를 생성하는 시점에서 클래스의 자료형을 정함.
- 제네릭 클래스 내에 메서드에도 다음과 같이 형식 매개변수를 사용
class MyClass<T> { // 한 개의 형식 매개변수를 가지는 클래스
fun myMethod(a: T) { // 메서드의 매개변수 자료형에 사용됨
...
}
}
- 프로퍼티에 지정하는 경우
주 생성자나 부 생성자에 형식 매개 변수를 지정해 사용
주 생성자의 형식 매개변수
class MyClass<T>(val myProp: T) { } // 주 생성자의 프로퍼티
부 생성자의 형식 매개변수
class MyClass<T> {
val myProp: T // 프로퍼티
constructor(myProp: T) { // 부 생성자 이용
this.myProp = myProp
}
}
package com.example.pp243
class Box<T>(t: T) { // 제네릭을 사용해 형식 매개변수를 받아 name에 저장
var name = t
}
fun main() {
val box1: Box<Int> = Box<Int>(1)
val box2: Box<String> = Box<String>("Hello")
println(box1.name)
println(box2.name)
}
제네릭 예제
package com.example.pp248
open class Parent
class Child: Parent()
class Cup<T>
fun main() {
val obj1: Parent = Child() // Parent형식의 obj1은 Child로 형 변환될 수 있음
//val obj2: Child = Parent() // 에러 하위 형식인 Child의 객체 obj2는 Parent로 변환되지 않음
//val obj3: Cup<Parent> = Cup<Child>() // 에러 자료형 형식이 일치하지 않음
//val obj4: Cup<Child> = Cup<Parent>() // 에러 자료형 형식이 일치하지 않음
val obj5 = Cup<Child>() // obj5는 Cup<Child>형식이 된다.
val obj6: Cup<Child> = obj5 // 형식이 일치하므로 괜춘
}
제네릭 클래스의 자료형 변환하기 예제
open되어 있어 Parent클래스를 상속받을 수 있음
Child클래스가 Parent()클래스를 상속받음.
가변성을 주기위해 in, out등을 설정해야 한다.
package com.example.pp249
class GenericNull<T> { // 기본적으로 null이 허용되는 형식 매개변수
fun EqualityFunc(arg1: T, arg2: T) {
println(arg1?.equals(arg2))
}
}
fun main(args: Array<String>) {
val obj = GenericNull<String>() // non-null로 선언됨
obj.EqualityFunc("Hello", "World") // null이 허용되지 않음
val obj2 = GenericNull<Int?>() // null이 가능한 형식으로 선언
obj2.EqualityFunc(null, 10) // null 사용
}
형식 매개변수의 null 제어 예제
제네릭의 형식 매개변수는 기본적으로 null 가능한 형태로 선언
null을 허용하지 않으려면? ==> 특정 자료형으로 제한한다. <T: Any>
제네릭 함수 혹은 메서드
- 해당 함수나 메서드 앞쪽에 <T>와 같이 지정
- 자료형의 결정은 함수가 호출될 때 컴파일러가 자료형 추론
- 이 자료형은 반환 자료형과 매개변수 자료형에 사용 가능
fun <형식 매개변수[,...]> 함수명(매개변수: <매개변수 자료형>[, ...]): <반환 자료형>
// 예시
fun <T> genericFunc(arg: T): T? { ... } // 매개변수와 반환 자료형에 형식 매개변수 T가 사용됨
fun <K, V> put(key: K, value: V): Unit { ... } // 형식 매개변수가 여러 개인 경우
제네릭과 람다식
- 형식 매개변수로 선언된 함수의 매개변수를 연산
fun <T> add(a: T, b: T): T{
return a + b // 오류! 자료형을 결정할 수 없음
}
==> 람다식으로 변경해주어야한다.
fun <T> add(a: T, b: T, op: (T, T) -> T): T {
return op(a, b)
}
fun main() {
val result = add(2, 3, {a, b -> a + b})
// val result = add(2, 3) {a, b -> a + b}와 같이 표현 가능
println(result)
}
** 람다식은 매개변수와 처리부(바디)로만 이루어진 것.
(람다식은 함수명과 반환형이 없다. ==> 중문으로 람다식임을 알려줘야한다.)
*** 함수에는 1. 함수명 2. 매개변수 3. 바디(처리부) 4. 반환형 이 필요하다.
package com.example.pp251
fun <T> find(a: Array<T>, Target: T): Int {
for(i in a.indices) {
if(a[i] == Target) return i
}
return -1
}
fun main() {
val arr1: Array<String> = arrayOf("Apple", "Banana", "Cherry", "Durian")
val arr2: Array<Int> = arrayOf(1, 2, 3, 4)
println("arr.indices ${arr1.indices}") // indices는 배열의 유효 범위 반환
println(find<String>(arr1, "Cherry")) // 요소 C의 인덱스 찾아내기
println(find(arr2, 2)) // 요소 2의 인덱스 찾아내기
}
배열의 인덱스 찾아내기
package com.example.pp252
fun <T> add(a: T, b: T, op: (T, T) -> T): T {
return op(a, b)
}
fun main() {
val result = add(2, 3, {a, b -> a + b})
// val result = add(2, 3) {a, b -> a + b}와 같이 표현 가능
println(result)
}
람다식으로 연산식 작성 예제
자료형 제한하기
형식 매개변수를 특정한 자료형으로 제한
- 자료형의 사용범위를 좁히기 위해 자료형을 제한
- 코틀린은 콜론(:)과 자료형을 기입하면 형식 매개변수 T의 자료형이 기입한 자료형으로 제한
(자바에서는 extends나 super를 사용해 자료형을 제한)
package com.example.pp254
class Calc4<T: Number> { // 클래스의 형식 매개변수 제한
fun plus(arg1: T, arg2: T): Double {
return arg1.toDouble() + arg2.toDouble()
}
}
fun main(args: Array<String>) {
val calc = Calc4<Int>()
println(calc.plus(10,20))
val calc2 = Calc4<Double>()
val calc3 = Calc4<Long>()
//val calc4 = Calc4<String>() // 제한된 자료형으로 오류
println(calc2.plus(2.5, 3.5))
println(calc3.plus(5L, 10L))
}
자료형을 숫자로 제한하는 예제
** String은 Number의 자식이 아니다! 그래서 오류
package com.example.pp255
fun <T: Number> addLimit(a: T, b: T, op: (T, T) -> T): T {
return op(a, b)
}
fun main() {
//val result = addLimit("abc", "def", {a, b -> a + b}) // 제한된 자료형으로 오류
}
함수의 자료형 제한 예제
상, 하위 형식의 가변성
- 가변성(variance) : 형식 매개변수가 클래스 계층에 어떤 영향을 미치는지 정의
형식 A의 값이 필요한 모든 장소에 형식 B의 값을 넣어도 아무 문제가 없다면 B는 A의 하위 형식(subtype)
(Int는 Number의 하위 클래스)
- String : 클래스O, 자료형O
- String? : 클래스X, 자료형O
- List : 클래스O, 자료형O
- List<String> : 클래스X, 자료형O
** null을 허용하는 자료형은 자료형x , 객체형으로 본다. 따라서 힙영역에 저장됨.
- 하위클래스는 상위클래스가 수용
하위 자료형은 상위 자료형으로 자연스럽게 형 변환이 이루어짐
val integer: Int = 1
val number: Number = integer // 하위 자료형 Int를 Number가 수용
val integer: Int = 1;
val nullableInteger: Int? = integer;
println($integer)
println(nullableInteger)
가변성의 3가지 유형
- 공변성(covariance) : T'가 T의 하위 자료형이면, C<T'>는 C<T>의 하위 자료형이다. 생산자 입장의 out 성질
- 반공변성(contravariance) : T'가 T의 하위 자료형이면, C<T'>는 C<T>의 하위 자료형이다. 소비자 입장의 in 성질
- 무변성(invariance) : C<T>와 C<T'>는 아무 관계가 없다. 생산자 + 소비자
무변성
- 자료형 사이의 하위 자료형 관계가 성립하지 않음
- 코틀린에서는 따로 지정해주지 않으면 기본적으로 무변성
package com.example.pp259
// 무변성(Invariance) 선언
class Box<T>(val size: Int)
fun main(args: Array<String>) {
val anys: Box<Any> = Box<Int>(10) // 자료형 불일치 오류
val nothings: Box<Nothing> = Box<Int>(20) // 자료형 불일치 오류
}
공변성
- 형식 매개변수 사이의 하위 자료형 관계가 성립
- 하위 자료형 관계가 그대로 인스턴스 자료형 사이의 관계로 이루어지는 경우
- out 키워드를 사용해 정의
package com.example.pp260
// 공변성(Covariance) 선언
class Box<out T>(val size: Int)
fun main(args: Array<String>) {
val anys: Box<Any> = Box<Int>(10) // 관게 성립으로 객체 생성 가능
//val nothings: Box<Nothing> = Box<Int>(20) // 자료형 불일치 오류
println(anys.size)
}
반공변성
- 자료형의 상하 관계가 반대
- 하위 클래스의 자료형을 상위 클래스의 자료형이 허용
package com.example.pp261
// 반공변성(Contravariance) 선언
class Box<in T>(val size: Int)
fun main(args: Array<String>) {
// val anys: Box<Any> = Box<Int>(10) // 자료형 불일치 오류
val nothings: Box<Nothing> = Box<Int>(20) // 관계 성립으로 객체 생성 가능
println(nothings.size)
}
package com.example.pp262
open class Animal(val size: Int) {
fun feed() = println("Feeding...")
}
class Cat(val jump: Int): Animal(50)
class Spider(val poison: Boolean): Animal(1)
// 형식 매개변수를 Animal로 제한
class Box<out T: Animal>(val element: T) { // 주 생성자에서 val만 허용
fun getAnimal(): T = element // out은 반환 자료형에만 사용 가능
/*fun set(new: T) { // 오류 T는 in 위치에 사용할 수 없다.
element = new
}*/
}
fun main() {
// 일반적인 객체 선언
val c1: Cat = Cat(10)
val s1: Spider = Spider(true)
// 클래스의 상위 자료형 반환은 아무런 문제가 없다.
var a1: Animal = c1
a1 = s1 // a1은 Spider의 객체가 된다.
println("a1.size = ${a1.size}")
// 공변성 - Cat은 Animal의 하위 자료형
val c2: Box<Animal> = Box<Cat>(Cat(10))
println("c2.element.size = ${c2.element.size}")
// 반대의 경우는 가능하지 않다.
// val c3: Box<Cat> = Box<Animal>(10) // 오류
// 자료형이 제한되어 Animal과 하위 클래스 이외에는 사용할 수 없다.
// val c4: Box<Any> = Box<Int>(10) // 오류류
}
자료형을 제한하는 제네릭 클래스
배열
- arrayOf()나 Array() 생성자를 사용해 배열 생성
- arrayOfNulls()은 빈배열
val numbers = arrayOf(2, 5, 2, 3) // 정수형으로 초기화된 배열
val animals = arrayOf("abc", "def", "ghi") // 문자열형으로 초기화된 배열
for (element in numbers) { // 정수형으로 초기화된 배열 출력하기
println(element)
}
다양한 자료형의 혼합 가능
val mixArr = arrayOf(4, 5, 7, 3, "Click", false) // 정수, 문자열, Boolean 혼합
특정 자료형을 제한할 경우
- arrayOf<자료형>() 혹은 자료형이름 + ArrayOf()
- charArrayOf(), booleanArrayOf(), longArrayOf(), shortArrayOf(), byteArrayOf(), intArrayOf()
- ubyteArrayOf(), ushortArrayOf(), uintArrayOf(), ulongArray()
** intArrayOf()는 내부적으로 int[] 형으로 변환
연산자를 통한 접근
- arr.get(index) -> value = arr[index]
- arr.set(index) -> arr[index] = value
package com.example.test
import java.util.Arrays
fun main(args: Array<String>) {
val arr = intArrayOf(1, 2, 3, 4, 5, 8)
println("arr: ${Arrays.toString(arr)}")
println("size: ${arr.size}")
println("sum(): ${arr.sum()}")
// 게터에 의한 접근과 대괄호 연산자 표기법
println(arr.get(2))
println(arr[2])
// 세터에 의한 값의 설정
arr.set(1, 5)
arr[0] = 3
println("size: ${arr.size} arr[0]: ${arr[0]}, arr[2], ${arr[2]}")
// 루프를 통한 배열 요소의 접근
for(i in 0..arr.size -1) {
println("arr[$i] = ${arr[i]}")
}
}
Array Test 예제
package com.example.pp270
fun main() {
val array1 = arrayOf(1, 2, 3)
val array2 = arrayOf(4, 5, 6)
val array3 = arrayOf(7, 8, 9)
val arr2d = arrayOf(array1, array2, array3)
val arr2d2 = arrayOf(arrayOf(1, 2, 3), arrayOf(4, 5, 6), arrayOf(7, 8, 9))
for(e1 in arr2d) {
for(e2 in e1) {
print(e2)
}
println()
}
println()
for(e1 in arr2d2) {
for(e2 in e1) {
print(e2)
}
println()
}
}
다차원 배열 예제
'Kotlin' 카테고리의 다른 글
Kotlin #10 : 내부 클래스, 연산자 230223 (0) | 2023.02.23 |
---|---|
Kotlin #9 : 객체, 데이터 클래스, Companion 객체, object, singleton, 추상 클래스, 인터페이스 230222 (0) | 2023.02.23 |
Kotlin #8 : 코틀린의 프로퍼티, 자바의 필드, 지연 초기화, lazy, 위임 230220 (0) | 2023.02.22 |
Kotlin #7 : 정보 은닉 캡슐화 (연관, 의존, 집합, 구성) 230220 (0) | 2023.02.20 |
Kotlin #6 : 상속과 클래스 230217 (0) | 2023.02.20 |
댓글