[TECHIT] 코틀린 7

hegleB·2023년 5월 26일
0
post-thumbnail

Reflection

fun main(){
  
    val a1: KClass<String> = String::class
    println("String의 코틀린에서의 타입 : $a1")
  
    val a2:Class<String> = String::class.java
    println("String의 자바에서의 타입 : $a2")

    // 변수를 통해 접근할 수 있는 객체의 클래스 타입을 파악한다.
    val str1 = "안녕하세요"
    val a3:KClass<out String> = str1::class
    println("str1을 통해 접근할 수 있는 객체의 클래스 타입(코틀린) : $a3")

    val a4:Class<out String> = str1::class.java
    println("str1을 통해 접근할 수 있는 객체의 클래스 타입(자바) : $a4 ")

    val test1 = TestClass()

    val a7 = test1::class
    val a8 = test1::class.java
    println("test1의 클래스 타입(코틀린) : $a7")
    println("test1의 클래스 타입(자바) : $a8")

    // 클래스 정보 분석
    println("추상 클래스 인가 : ${test1::class.isAbstract}")
    println("Companion 클래스 인가 : ${test1::class.isCompanion}")
    println("Data 클래스 인가 : ${test1::class.isData}")
    println("final 클래스 인가 : ${test1::class.isFinal}")
    println("open 클래스 인가 : ${test1::class.isOpen}")
    println("중첩 클래스 인가 : ${test1::class.isInner}")
    println("Sealed 클래스 인가 : ${test1::class.isSealed}")
}

프로그램 실행 중 객체에 대한 다양한 정보를 파악하기 위한 개념으로 클래스, 인터페이스, 메서드, 프로퍼티 등의 정보를 알아 낼 수 있다.
:: 연산자를 사용하여 참조하는 위치와 메서드 또는 매개변수 형식 및 반환 형식에 따라 참조하는 타입이 결정된다.
KClass<클래스타입> : 지정된 클래스의 타입을 파악한다(코틀린 클래스)
Class<클래스타입> : 지정된 클래스의 타입을 파악한다(자바 클래스)

람다함수

val lambda1 : (Int, Int) -> Int = {a1:Int, a2:Int -> a1 + a2}
// 타입 부분을 생략할 수 있다.
val lambda2 = {a1:Int, a2:Int -> a1 + a2}
// 뒷 부분의 타입을 생략할 수 있다.
val lambda3 : (Int, Int) -> Int = {a1, a2 -> a1 + a2}
// 제일 마지막에 작성한 값이 반환 값이 된다.
val lambda4 = {a1:Int, a2:Int ->
    val r1 = a1 + a2
    var r2 = a1 - a2
    r1 * r2
}

람다식을 작성하여 변수에 담아두면 그 것을 이용해 람다식을 계산하고 그 결과를 받아 사용할 수 있다.

익명 함수

// 익명함수
val test3 = fun(){
    println("나는야 익명함수")
}

변수에 이름없는 함수로 함수를 정의하면서 동시에 사용할 수 있는 함수이다.

인라인 함수

fun main(){
    testFunction1()
    testFunction1()

    testFunction2()
    testFunction2()
}

fun testFunction1(){
    println("----------------------------")
    println("testFunction1")
    println("----------------------------")
}

inline fun testFunction2(){
    println("----------------------------")
    println("testFunction2")
    println("----------------------------")
}

public final class MainKt {
   public static final void main() {
      testFunction1();
      testFunction1();
      int $i$f$testFunction2 = false;
      String var1 = "----------------------------";
      System.out.println(var1);
      var1 = "testFunction2";
      System.out.println(var1);
      var1 = "----------------------------";
      System.out.println(var1);
      $i$f$testFunction2 = false;
      var1 = "----------------------------";
      System.out.println(var1);
      var1 = "testFunction2";
      System.out.println(var1);
      var1 = "----------------------------";
      System.out.println(var1);
   }

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

   public static final void testFunction1() {
      String var0 = "----------------------------";
      System.out.println(var0);
      var0 = "testFunction1";
      System.out.println(var0);
      var0 = "----------------------------";
      System.out.println(var0);
   }

   public static final void testFunction2() {
      int $i$f$testFunction2 = 0;
      String var1 = "----------------------------";
      System.out.println(var1);
      var1 = "testFunction2";
      System.out.println(var1);
      var1 = "----------------------------";
      System.out.println(var1);
   }
}

자바 코드로 변경될 때 inline 함수를 호출하는 부분들은 함수 내부의 코드로 변경된다. 코드가 다소 늘어나지만 함수 호출과 관련된 작업을 하지 않는 장점이 있다. 딱 한번만 사용하는 함수가 있을 때 사용하는 경우가 종종 있다.

확장 함수

fun main(){
    val str1 = "abcd"

    // 추가한 메서드 호출
    println(str1.getUpperString())
    str1.printString()
}

// 확장함수 정의
// 클래스명.추가할 메서드
fun String.getUpperString() : String {
    return this.uppercase(Locale.getDefault())
}

fun String.printString() {
    println("문자열 : $this")
}

이미 있는 클래스에 메서드를 추가하는 개념 추가된 메서드는 같은 프로그램 내에서만 사용이 가능하다. 자바 코드로 변경될 때 객체의 ID를 받아 사용하는 코드로 변경된다.

인픽스 함수

fun main(){
    val v1 = 100
    // 값(또는 객체).함수
    val r1 = v1.add2(50)
    println("r1 : $r1")

    // infix가 있으므로 다음과 같이 호출 할 수도 있다.
    var r2 = v1 add2 50
    println("r2 : $r2")

    val obj1 = TestClass1()
    val obj2 = TestClass1()
    obj1.number1 = 100
    obj2.number1 = 200

    val r3 = obj1 add4 obj2
    println(r3.number1)

    val r4 = obj1.add4(obj2)
    println(r4.number1)
}

// infix fun 값1의 타입.함수이름(값2를 담을 매개변수) : 반환타입
infix fun Int.add2(a1:Int) : Int{
    // 여기에서 this 첫 번째 값을 의미한다.
    return this + a1
}

infix fun Int.minus2(a1:Int) : Int{
    return this - a1
}

// 일반적인 함수는 infix로 정의할 수 없다.
// infix fun add3(a1:Int) : Int {
//
// }

// 클래스에 정의한 멤버 메서드
class TestClass1 {
    var number1 = 0

    infix fun add4(target:TestClass1) : TestClass1{
        val r1 = this.number1 + target.number1
        val t1 = TestClass1()
        t1.number1 = r1
        return t1
    }
}

함수 호출을 연산자 사용하듯이 할 수 있는 함수이다. 값1 함수이름 값2 형태로 호출 한다. 값1 객체를 통해 함수를 호출하고 매개변수로 값2를 전달한다.

고차함수

fun main(){
    // 함수를 호출할 때 전달할 익명 함수
    val t1 = fun(x1:Int, x2:Int) : Int {
        return x1 + x2
    }
    // 고차함수 호출
    testFunc1(100, 200, t1)

    // 익명함수를 직접 작성해 준다.
    testFunc1(100, 200, fun(x1:Int, x2:Int) : Int {
        return x1 - x2
    })

    // 고차함수에 람다 함수를 전달하는 것도 가능하다.
    val lambda1 = {x1:Int, x2:Int -> x1 * x2}
    testFunc1(100, 200, lambda1)

    // 람다식을 직접 작성해 준다.
    testFunc1(100, 200, {x1:Int, x2:Int -> x1 / x2})

    // 함수를 반환하는 함수 사용
    val t2 = testFunc2(100)
    val r2 = t2(200)
    println("r2 : $r2")

    // 람다식을 반환하는 함수 사용
    val t3 = testFun3(100)
    val r3 = t3(200)
    println("r3 : $r3")

    // 함수를 받는 매개변수가 마지막에 있지 않은 함수 호출
    // 1. 익명함수를 변수에 담아 전달한다.
    val f4 = fun(x1:Int, x2:Int) : Int{
        println("x1 : $x1")
        println("x2 : $x2")
        return x1 + x2
    }
    testFunc4(100, f4, 200)

    // 2. 익명함수를 직접 작성한다.
    testFunc4(100, fun(x1:Int, x2:Int) : Int{
        println("x1 : $x1")
        println("x2 : $x2")
        return x1 + x2
    }, 200)

    // 3. 람다를 전달한다.
    val lambda4 = {x1:Int, x2:Int ->
        println("x1 : $x1")
        println("x2 : $x2")
        x1 + x2
    }
    testFunc4(100, lambda4, 200)

    // 4. 람다를 직접 작성한다.
    testFunc4(100, {x1:Int, x2:Int ->
        println("x1 : $x1")
        println("x2 : $x2")
        x1 + x2
    }, 200)

    // 익명함수나 람다식을 받는 매개변수가 마지막에 있는 함수 호출
    // 위의 1 ~ 4 모두 사용가능하다.
    // { } 로 작성한 람다식이 제일 마지막 매개변수로 들어간다.
    testFunc5(100, 200) {x1:Int, x2:Int ->
        println("x1 : $x1")
        println("x2 : $x2")
        x1 + x2
    }

    val ic1 = InterClass1()
    testFunc6(ic1)

    val ic2 = InterClass2()
    testFunc6(ic2)
    
    testFunc6(object:Inter1{
        override fun interMethod() {
            println("익명 중첩 클래스의 메서드 호출")
        }
    })

    
    testFunc7 { 
        println("testFun7 1번")
    }
    
    testFunc7 { 
        println("testFun7 2번")
    }


    testFunc8 { a1:Int ->
        println(a1)
    }
  
    testFunc8 {
        println(it)
    }
}

// 매개변수로 함수를 받는 함수
fun testFunc1(a1:Int, a2:Int, m1:(Int, Int) -> Int){
    val r1 = m1(a1, a2)
    println("testFun1 r1 : $r1")
}

// 함수를 반환하는 함수
fun testFunc2(a1:Int) : (Int) -> Int {

    // 반환하는 함수 내부에서 지역변수나 매개변수를 사용할 수 있다.
    return fun(x2:Int) : Int {
        return a1 + x2
    }
}

// 람다식을 반환하는 함수
fun testFun3(a1:Int) : (Int) -> Int {
    return {x2:Int -> a1 - x2}
}

// 함수나 람다식을 받는 매개변수를 제일 마지막에 작성하지 않은 함수
fun testFunc4(a1:Int, m1:(Int, Int) -> Int, a2:Int){
    val r4 = m1(a1, a2)
    println("r4 : $r4")
}

// 함수나 람다식을 받는 매개변수를 제일 마지막에 작성한 함수
fun testFunc5(a1:Int, a2:Int, m1:(Int, Int) -> Int){
    val r5 = m1(a1, a2)
    println("r5 : $r5")
}

interface Inter1{
    fun interMethod()
}

fun testFunc6(inter1:Inter1){
    inter1.interMethod()
}

class InterClass1 : Inter1{
    override fun interMethod() {
        println("InterClass1의 interMethod")
    }
}

class InterClass2 : Inter1{
    override fun interMethod() {
        println("InterClass2의 interMethod")
    }
}

fun testFunc7(m1:()->Unit){
    m1()
}

// 매개변수 하나를 가지고 있는 함수나 람다식을 받는 함수
fun testFunc8(m1:(Int) -> Unit){
    m1(100)
}

함수를 매개변수로 받거나 반환 타입이 함수인 함수이다. 다른곳에서 호출하는 메서드를 오버라이딩하는 작업을 대신할 수 있다. 매개변수 하나인 함수나 람다식을 받는 고차함수를 사용할 때 전달할 함수나 람다식에 매개변수를 정의하지 않고 it 을 사용하면 매개변수로 전달되는 값을 사용할 수 있다.

Scope 함수

let

val t7 = TestClass1(100, 200).let{
        it.a3 = 300
        it.a4 = 400
        // t7 변수에 객체의 ID를 담기 위해 반환해줘야 한다.
        it
    }
println(t7)

객체에 접근할 때 it 변수를 사용해야 한다. 코드 블럭 마지막에 작성된 값이나 수식을 반환해준다. let 객체 생성 후 나중에 프로퍼티에 값을 저장하고자 할 때 사용한다. 만약 아래와 같이 객체 생성시 프로퍼티의 값을 저장하고 객체의 ID를 변수에 담겠다면 람다식 마지막에 객체의 ID(it을 통해)를 반환해야 한다.

apply

val t8 = TestClass1(100, 200).apply{
        a3 = 300
        a4 = 400
    }
println(t8)

생성된 객체의 ID가 apply 쪽으로 전달되어 this(혹은 생략)을 통해 객체의 프로퍼티에 접근할 수 있다. { } 블럭 내부의 코드가 수행이 끝나면 생성된 객체의 ID가 변수에 담긴다. 이에 { } 블럭 내부에서 객체의 ID를 반환하는 코드를 작성하지 않는다.

run

val t9 = TestClass1(100, 200).run{
        a3 = 300
        a4 = 400
        this
    }
println(t9)

객체를 생성한 후에 run 코드 블럭 내로 객체가 전달되기 때문에 this(혹은 생략)을 통해 프러퍼티에 접근할 수 있다. run { } 블럭의 수행이 끝나면 제일 마지막에 작성한 값이 변환되어 변수에 담긴다. 이에 생성된 객체의 ID를 전달하고자 한다면 run { } 블럭 제일 아래에 this를 작성하여 객체의 ID를 반환해야 한다.

with

val t10 = with(TestClass1(100, 200)) {
        a3 = 300
        a4 = 400
        this
    }
println(t10)

생성한 객체를 ( ) 안에 넣어줘야 한다. { } 내에는 with에 지정된 객체의 ID가 전달되기 때문에 this(혹은 생략)로 프로퍼티에 접할 수 있다. 객체를 생성할 때 프로퍼티에 값을 설정하기 보단 run 처럼 객체 생성한 이후 나중에 프로퍼티의 값을 새롭게 저장하기 위해 사용한다. run 보다 작성하는 코드가 더 많기 때문에 잘 사용하지는 않는다.

also

val t11 = TestClass1(100, 200).also {
        it.a3 = 300
        it.a4 = 400
    }
println(t11)

객체를 생성과 함께 프로퍼티의 값을 설정하는 작업을 하면 객체의 ID가 it 이라는 변수로 전달되고 그 것을 통해 객체의 프로퍼티에 접근할 수 있다. 하지만 let과 다르게 생성된 객체의 ID가 반환되므로 마지막에 객체의 ID를 반환하지 않아도 된다.

결론

객체를 생성하면서 바로 프로퍼티의 값을 설정하겠다면 -> apply
객체를 생성하고 나중에 프로퍼티의 값을 설정하겠다면 -> run

profile
성장하는 개발자

0개의 댓글

관련 채용 정보