Kotlin Study 2

Kotlin Study 2주차

Extension

kotlin은 확장이라는 기능을 이용해 간단한게 객체의 함수나 프로머티를 임의로 확장 정의 할수 있다.

  • 유틸리티 클래스 등을 별도로 지정하지 않고, 직접적인 객체 확장의 방법 제공

  • 함수와 프로퍼티 양측에 대한 확장을 지원

  • Generic을 통해 객체의 타입을 처리 가능

  • Extension이 적용될 범위를 지정가능

Extension function의 예시

kotlin

// Some code
fun String.hello() : String{
    return "Hello, $this"
}


fun main() {
    val whom = "songmyeongjin"
    println(whom.hello())
}

Java

// Some code
public final class MainKt {
   @NotNull
   public static final String hello(@NotNull String $this$hello) {
      Intrinsics.checkNotNullParameter($this$hello, "$this$hello");
      return "Hello, " + $this$hello;
   }

   public static final void main() {
      String whom = "songmyeongjin";
      String var1 = hello(whom);
      boolean var2 = false;
      System.out.println(var1);
   }

   // $FF: synthetic method
   public static void main(String[] var0) {
      main();
   }
}

Extension은 정적으로 처리된다.

확장 대상인 String을 인자로 받는 static final로 메서드가 생성됨. 이는 클래스 자체가 확장된 것이 아닌, 정적인 메소드 형태로 코드가 생성되었으므로, 객체 멤버 접근에 제한 접근이 존재할수 있음

실제로 클래스를 상속/수정 하는 것이 아닌, 클래스에 새 멤버를 삽입하지 않고 단순히 해당 타입의 변수에 .을 기반으로 호출가능한 함수를 생성

// Some code

open class C

class D: C()

fun C.foo() = "c"
fun D.foo() = "d"

fun printFoo(c:C){
    println(c.foo())
}

fun main() {
    printFoo(D())
}

// Some code

public final class MainKt {
   @NotNull
   public static final String foo(@NotNull C $this$foo) {
      Intrinsics.checkNotNullParameter($this$foo, "$this$foo");
      return "c";
   }

   @NotNull
   public static final String foo(@NotNull D $this$foo) {
      Intrinsics.checkNotNullParameter($this$foo, "$this$foo");
      return "d";
   }

   public static final void printFoo(@NotNull C c) {
      Intrinsics.checkNotNullParameter(c, "c");
      String var1 = foo(c);
      boolean var2 = false;
      System.out.println(var1);
   }

   public static final void main() {
      printFoo((C)(new D()));
   }

   // $FF: synthetic method
   public static void main(String[] var0) {
      main();
   }E

Extension은 멤버가 우선이다.

동일한 시그니처를 가지는 멤버 함수가 존재한다면 언제나 멤버가 호출된다. 멤버가 Extensions에 대해 항상 우선권을 가진다.

// Some code
class Person{
    fun hello() {
        println("Hello!")
    }
}

fun Person.hello(){
    println("world!")
}

fun main() {
    Person().hello()
}

class Person{
    fun hell() {
        println("Hello!")
    }
}

fun Person.hello(){
    println("world!")
}

fun main() {
    Person().hello()
}
// Some code
public final class MainKt {
   public static final void hello(@NotNull Person $this$hello) {
      Intrinsics.checkNotNullParameter($this$hello, "$this$hello");
      String var1 = "world!";
      boolean var2 = false;
      System.out.println(var1);
   }

   public static final void main() {
      (new Person()).hello();
   }

   // $FF: synthetic method
   public static void main(String[] var0) {
      main();
   }
}

멤버가 Extension에 대해 항상 우선권을 가지기에 Complie시 이를 반영해서 컴파일된다. 별도로 에러가 발생하지 않기 때문에 조심해야함 (인텔리제이 상에선 우선순위가 있는것이 색깔이 들어옴)

Extension 역시 범위를 갖는다.

Extension을 클래스 멤버로 선언하면 해당 클래스 내에서만 범위가 결정. 클래스 멤버로 선언된 Extension은 당연히 자신이 선언된 클래스 멤버에 자유롭게 접근가능. 아래 예시에선 C클래스 내에 선언된 D.foo() extension의 경우 C클래스 내에서만 유효하다. (D().foo()는 따로 선언하면 안됨)

// Some code

class D{
    fun bar() {
        println("D.bar()")
    }
}

class C{
    fun baz(){
        println("C.bar()")
    }
    fun D.foo(){
        bar() // calls D.bar
        baz() // calls C.baz
    }

    fun caller(d:D){
        d.foo()
    }
}


fun main() {
    C().caller(D())
}



Extensions 정의하기

fun 뒤에 Receiver type을 정의하고, Nullable이 필요하다면 ?을 추가한다. 그리고 Receiver type과 함수의 이름 사이에는 .으로 구분한다. Receiver type으로 받은 object는 this로 접근해 사용할수 있다.

inline fun String?.isNullOrBlank(): Int {
    if(this == null || this.isBlank()) return 1;
	return 0;
}

fun main() {
    val temp = ""
    println(temp.isNullOrBlank())
}

Higer Order functions에서도 동일하게 사용한다.

// Some code
    val isNotEmpty:String ?.() -> Boolean ={
        this != null && this.isNotEmpty()
    }

    val temp = "111"
    println(temp.isNotEmpty())

람다 표현식

Lamda expression은 람다 표현식. 쉽게 말하면 익명함수. 익명함수란 함수의 이름이 없는 함수를 말함. 보통 한번 사용되고 재사용되지 않는 함수를 만들때 익명함수로 만든다. 굳이 함수를 따로 생성하지 않고, 코드 중간에 익명함수를 만들면, 코드 가독성이 높아진다.

일급 함수란?

  • 일급함수는 함수가 객체로 취급될 수 있습니다.

  • 일급함수는 함수 객체를 인자로 넘길 수 있어야 합니다.

  • 일급함수는 함수 객체를 인자로 넘길 수 있어야 합니다.

일급함수는 함수가 객체로 취급될 수 있습니다.

변수 hello 에 hello world를 리턴하는 함수를 할당할 수 있다.

val hello: () -> String = {"hello world"}

일급함수는 함수 객체를 인자로 넘길 수 있어야된다. (이것은 콜백?)

val hello: () -> String = {"hello world"}
fun printHello(func: ()->String) {
    print("${func()}")
}

일급 함수는 함수 객체를 함수의 결과로 리턴할수 있다.

// Some code

    val hello: () -> String = {"hello world"}
    fun returnHello(): () -> String{
        return hello
    }

    val returned : () -> String = returnHello()
    println("${returned()}")

언어가 일급 함수를 지원한다는 것은 고차함수를 사용할 수있다는 것과 같다.

고차 함수란?

함수의 인자를 함수로 넘기거나 함수를 리턴하는 함수를 말한다.

val hello: () -> String = {"hello world"}

fun returnParamFunc(func: ()->String): () -> String {
    return func
}

fun main(args: Array<String>) {
    val returned = returnParamFunc(hello)
    print("${returned()}")
}

인자 및 리턴 타입

인자로 함수를 넘길때 변수 이름 뒤에 :()-> String 처럼 함수의 타입을 명시해야한다. 예를 들어 Int를 인자로 받고 String을 리턴하는 함수는 (Int) -> String처럼 표현한다. 만약 인자 두개를 넘기면 (Int, Int) -> String으로 표현할수 있다. 만약 리턴값이 없다면, Unit을 사용하면 된다.

fun returnParamFunc(func: ()->String): () -> String {
    return func
}

익명 함수의 생성

익명함수는 이름없이 정의되는 함수

// Some code

    val greeting = fun(){
        println("Hello")
    }
    val greeting2 : () -> Unit = {
        println("Hello")
    }
    greeting2()
// Some code

    val greeting3 = {
        name : String, age: Int
            -> "Hello, My name is $name I'm $age year old"
    }
    println(greeting3("song",10))

인자 타입을 생략하는 익명함수. 만약 익명함수가 할당되는 변수에 인자와 리턴타입이 정의되어있다면, 이를 생략할수 있다.

// Some code

    val greeting4:(String,Int) -> String ={
        name,age -> "Hello, My name is $name I'm $age year old"
    }

    println(greeting4("song",10))

인자가 1개일때 선언을 생략할수 있으며, 인자에 접근하려면 it라는 이름으로 접근해야된다.

val greeting2: (String) -> String = { "Hello. My name is $it."}
val result = greeting2("chacha")

라이브러리에서 사용되는 익명함수

모든것을 생략할수 있었던 것은 변수에 이미 인자와 리턴 타입이 정의되어있기 때문에 가능하다.

// Some code

    val numbers = listOf<Int>(5, 1, 3, 2, 9, 6, 7, 8, 4)
    val sorted = numbers.sortedBy({ it }).filter { it > 5 }
    println(sorted)

특정 기준으로 sorting 예시

// Some code

data class Person(val name: String, val age: Int)
val people = listOf(
    Person("김복남", 1),
    Person("김점례", 2),
    Person("황덕순", 3),
    Person("이흥복", 4),
    Person("이덕출", 250)
)
fun <T> List<T>.toPrint() { this.forEach(::println) }

fun main(){
    people.sortedBy(Person::age).toPrint()
}

두가지 이상 비교 조건 (나이를 내림차순후 점수를 오름차순한다)

// Some code

data class Person(val name: String, val age: Int, val score :Int)
val people = listOf(
    Person("김복남", 1, 50),
    Person("김점례", 2,56),
    Person("황덕순", 3,67),
    Person("이흥복", 4,9),
    Person("이덕출", 250,10)
)
fun <T> List<T>.toPrint() { this.forEach(::println) }

fun main(){
    people.sortedWith(compareByDescending(Person::age).thenByDescending(Person::score)).toPrint()
}

Last updated