코틀린 고급2 (실드클래스, 확장함수, 제네릭, 지연초기화)

김소희·2023년 12월 11일
1

실드 클래스

  • shield 클래스는 하나의 상위 클래스나 인터페이스에서 하위 클래스에 대한 정의를 제한할 수 있는 방법이다.
  • sealed class의 하위클래스들은 같은 패키지, 같은 모듈에 있어야 한다.(1.6버전기준)
  • Developer 추상클래스와 하위클래스로 backend, frontend가 있을때 object pool에 객체를 담거나 꺼내는 용도로 사용할 수 있다.
  • Developer가 abstract class일경우 when식에 else가 필요하지만 sealed class일 경우 컴파일 시점에서 하위클래스를 확인하기 때문에 else를 제외할 수 있다.
// Developer, backend, frontend 코드 생략
object DeveloperPool {
	fun add(developer: Developer) = when(developer) {
    is backend -> pool[developer.name] = developer
    is frontend -> pool[developer.name] = developer
    //else -> {
    //	println("지원하지 않는 개발자입니다.")
    //}
    
    fun get(name: String) = pool[name]
}

fun main() {
	val backend = Backend(name="토니")
    DeveloperPool.add(backend)
    
    val frontend = Frontend(name="엘리")
    DeveloperPool.add(frontend)
    
    println(DeveloperPool.get("토니"))
    println(DeveloperPool.get("엘리"))
}

확장함수

코틀린은 클래스에 상속하거나 디자인 패턴을 사용하지 않고 새로운 기능으로 클래스를 확장 할 수 있는 기능을 제공하는데 이것이 확장(extension)이라는 선언을 통해 이루어진다.
이때 추가적인 메소드를 구현하면 이를 "확장 함수"라고 하고 추가적인 프로퍼티를 구현하면 "확장 프로퍼티"라고 한다.

주의할 점은 기존에 존재하는 맴버함수와 동일한 이름(+동일한 시그니처)의 확장함수가 있을 경우에는 컴파일 오류가 발생하지 않고, 맴버함수가 우선적으로 호출되므로 확장 함수는 실행되지 않는다. 흔치않지만 본의아니게 코틀린 라이브러리에 쓰이는 함수명을 사용할 경우에 문제가 생길 수 있다.

  • 클래스 확장방법 : 확장이 되는 클래스명에 .을 붙여 새로운 함수를 만들면 된다.
  • 확장함수에 사용되는 this 키워드는 receiver(수신자 객체)라고 부른다.
fun String.first() : Char {
	return this[0];
}

fun String.addfirst(char: Char) : String {
	return char + this.substring(0)
}

fun main() {
	println("ABCD".first())        //결과 A
    println("ABCD".addFirst('Z'))  //결과 ZABCD
}

제네릭(generic)

<>안에 type parameter를 추가하고 ()에 타입 파라미터를 받는 변수를 추가한 클래스를 만들고,
제네릭으로 만든 클래스의 인스턴스는 type argument를 제공해주면 된다.

class MyGenerics<T>(val t: T) {
}

fun main() {
	// 제네릭을 사용한 클래스의 인스턴스를 만들려면 타입아규먼트를 제공
    // val generics = MyGenerics<String>("테스트")
    
    //타입생략가능
    val generics = MyGenerics("테스트")
    
    //변수의 타입에 제네릭을 사용한 경우
    val list1: MutableList<String> = mutableListOf()
    
    // 타입아규먼트를 생성자에서 추가
    val list2 = mutableListOf<String>()
    
    // 정확한 타입을 모르는 경우에도 제네릭을 안전하게 사용가능
    val list3 : List<*> = list<String>("테스트")
    val list4 : List<*> = list<Int>(1,2,3,4)
}

제네릭에서 파라미터화 된 타입이 어떤 관계에 있는지 설명하는 개념을 변성이라고 한다.
변성은 공변성, 반공변성, 무공변성 세종류가 있다.

공변성은 자바 제네릭의 Extends, 코틀린에선 out,
반공변성은 자바 제네릭의 Super, 코틀린에선 in을
서로 관계가 없으면 무공변성으로 아무것도 쓰지 않는다.

class MyGenerics<out T>(val t: T){
}

제네릭 상세설명 블로그 참고

지연초기화

지연초기화는 대상에 대한 초기화를 미뤘다가 실제 사용시점에 초기화하는 기법으로 초기화 과정에서 자원이 많이 쓰이거나 오버헤드가 발생할 경우 지연초기화를 사용하는 것이 유리할 수 있다.
많이 쓰이는 예시로는 웹페이지에서 특정 스크롤에 도달했을때 컨텐츠를 보여주는 무한 스크롤이나
싱글톤 패턴에서 LazyHolder방식의 지연초기화, JPA의 엔티티를 사용하는 시점에 로딩하는 LazyLoading기능이 있다.

코틀린에선 늦은 초기화, 초기화 지연을 할 수 있는 lateinit과 lazy 프로퍼티를 제공한다.

  • by lazy를 사용하면 불변변수를 유지하면서 코드를 사용하는 시점에 1회 초기화를 할 수 있으며 티 쓰레드 환경에서도 안전하게 동작하게 설계되어 있다.
    만약 초기화를 여러번 수행하고 싶거나 동기화를 끄고싶다면 by lazy(LazyThreadSafetyMode.NONE)처럼 설정을 바꾸어 사용할 수도 있다.

  • lateinit는 가변프로퍼티에 대하여 지연초기화가 필요한 경우에 사용한다.
    예를들어 스프링에 @Authwired를 사용하여 DI를 적용할 때나 @Setup으로 초기화할때 쓰인다.

profile
백엔드 자바 개발자 소희의 노트

0개의 댓글