-
TECHIT 앱 스쿨 2기: Android 19일차 (23.05.22)[THEC!T] 앱 스쿨2기 : Android 2023. 5. 23. 00:45728x90
자료 출처 : 안드로이드 앱스쿨 2기 윤재성 강사님 수업 내용
오늘의 시작은 리플렉션에 대한 설명이였습니다.
리플렉션(Reflection)
프로그램 실행 중인 객체에 대한 다양한 정보를 파악하기 위한 개념입니다.
리플렉션을 사용하면 클래스, 인터페이스, 메서드, 프로퍼티 등의 정보를 동적으로 알아낼 수 있고, 실행 중에 객체를 생성하고 메서드를 호출하며, 프로퍼티 값을 가져오거나 설정할 수 있습니다.
코틀린 리플렉션은 kotlin.reflect 패키지에 정의된 클래스와 함수들을 사용하여 구현됩니다.
:: 연산자를 사용하여 메서드나 프로퍼티를 참조할 때는 해당 메서드 또는 프로퍼티의 시그니처(매개변수 형식 및 반환 형식)와 참조하는 위치에 따라서 참조 타입이 결정됩니다.
하기는 코틀린 클래스와, 자바 클래스를 지정된 클래스의 타입을 파악하는 코드 입니다.
import kotlin.reflect.KClass fun main(){ val a1 : KClass<String> = String::class println("a1 : $a1") val a2 = String::class println("a2 : $a2") val a3 : Class<String> = String::class.java println("a3 : $a3") val a4 = String::class.java println("a4 : $a4") }
출력 결과
a1 : class kotlin.String a2 : class kotlin.String a3 : class java.lang.String a4 : class java.lang.String
변수를 통해 접근 할 수 있는 객체의 클래스 타입을 파악합니다.
fun main(){ val str1 = "안녕?" val a1: KClass<out String> = str1::class println("a1 : $a1") val a2= str1::class println("a2 : $a2") val b1: Class<out String> = str1::class.java println("b1 : $b1") val b2 = str1::class.java println("b2 : $b2") }
출력결과
a1 : class kotlin.String a2 : class kotlin.String b1 : class java.lang.String b2 : class java.lang.String
다음은 생성된 객체의 클래스 타입을 알아내 보겠습니다.
import kotlin.reflect.KClass fun main(){ val t1 = Tc1(100, 300, "오백") val a1: KClass<out Tc1> = t1::class println("a1 : $a1") val a2 = t1::class println("a2 : $a2") val b1 : Class<out Tc1> = t1::class.java println("b1 : $b1") val b2 = t1::class.java println("b2 : $b2") } class Tc1(val one:Int, val two:Int, val three:String)
출력 결과
a1 : class Tc1 a2 : class Tc1 b1 : class Tc1 b2 : class Tc1
클래스의 정보를 분석하는 방법입니다.
import kotlin.reflect.KClass fun main(){ val t1 = Tc1(100, 300, "오백") println("추상클래스 인지 확인 ${t1::class.isAbstract}") println("오픈클래스 인지 확인 ${t1::class.isOpen}") println("Data클래스 인지 확인 ${t1::class.isData}") println("파이널 클래스 인지 확인 ${t1::class.isFinal}") println("중첩클래스 인지 확인 ${t1::class.isInner}") println("실드 클래스 인지 확인 ${t1::class.isSealed}") println("Companion 클래스 인지 확인 ${t1::class.isCompanion}") } class Tc1(val one:Int, val two:Int, val three:String)
출력 결과
추상클래스 인지 확인 false 오픈클래스 인지 확인 false Data클래스 인지 확인 false 파이널 클래스 인지 확인 true 중첩클래스 인지 확인 false 실드 클래스 인지 확인 false Companion 클래스 인지 확인 false
생성자 정보를 가져오는 방법 입니다.
import kotlin.reflect.KClass fun main(){ val t1 = Tc1(100, 300, "오백") val constructor = t1::class.constructors for(con in constructor){ println("생성자 정보 : $con") } } class Tc1(val one:Int, val two:Int, val three:String)
출력 결과
생성자 정보 : fun `<init>`(kotlin.Int, kotlin.Int, kotlin.String): Tc1
생성자의 매개 변수들을 가져와서 출력하는 방법입니다.
import kotlin.reflect.KClass fun main(){ val t1 = Tc1(100, 300, "오백") val constructor = t1::class.constructors for(con in constructor){ for(param in con.parameters){ println("순서 : ${param.index}") println("타입 : ${param.type}") println("이름 : ${param.name}") } } } class Tc1(val one:Int, val two:Int, val three:String)
출력 결과
순서 : 0 타입 : kotlin.Int 이름 : one 순서 : 1 타입 : kotlin.Int 이름 : two 순서 : 2 타입 : kotlin.String 이름 : three
다음으로는 코틀린에서 매개변수로 들어오는 값을 가지고 처리를 하여 처리된 결과를 반환하는 함수에 대해 보다 쉽고 편하게 작성할 수 있는 다양한 문법을 지원하기에 다양한 문법을 제공하고 있어 그에 대한 문법들을 학습하였습니다.
fun main(){ val t1 = test1(10, 20) val t2 = test2(10, 20) val t3 = test3(10, 20) println(t1) println(t2) println(t3) } fun test1(a1:Int, a2:Int) : Int{ return a1 + a2 } fun test2(a1:Int, a2:Int) : Int = a1 + a2 fun test3(a1:Int, a2:Int) = a1 + a2
출력결과
30 30 30
위의 코드는 지금까지 배운 코드들이였으며 다음은 람다함수입니다.
이해하기 편하기 위해 함수명을 한글로 작성해보았습니다.
fun main(){ val t1 = 람다1(100,200) println("t1 : ${t1}") val t2 = 람다2(100,200) println("t2 : ${t2}") val t3 = 람다3(100,200) println("t3 : ${t3}") val t4 = 일반함수(100,200) println("t4 : ${t4}") val t5 = 람다4(100, 200) println("t5 : ${t5}") } val 람다1 : (Int,Int) -> Int = {a1:Int, a2:Int -> a1 + a2} val 람다2 = {a1:Int, a2:Int -> a1 + a2} val 람다3 : (Int,Int) -> Int = {a1, a2 -> a1 + a2} fun 일반함수(a1:Int, a2:Int): Int{ println("일반 a1 : $a1") println("일반 a2 : $a2") return a1 + a2 } val 람다4 = {a1:Int, a2:Int -> println("람다 a1 : $a1") println("람다 a2 : $a2") a1+a2// 가장 마지막에 적은 값이 반환이 된다. }
출력결과
t1 : 300 t2 : 300 t3 : 300 일반 a1 : 100 일반 a2 : 200 t4 : 300 람다 a1 : 100 람다 a2 : 200 t5 : 300
다음은 익명함수에 대해서 학습하였습니다.
코틀린에서는 함수의 이름을 통해 다른 변수에 함수를 넣는 것은 불가능 합니다.
fun main(){ // 에러 //val t1 = test1 // 하기 코드는 저장 가능(일반적으로는 반환값이 있을 경우 사용) // 만약 반환값이 없다면 사용할 이유가 없음.... val t2 = test2() println("t2 : $t2") // 익명함수 사용 test3() } fun test1(){ println("test1") } fun test2():Int{ return 100 } // 익명함수 val test3 = fun(){ println("나는야 익명함수") }
출력 결과
t2 : 100 나는야 익명함수
다음으로는 인라인 함수에 대해서 학습하였습니다.
인라인(Inline) 함수는 자바 코드로 변경될 때 인라인 함수를 호출하는 부분들은 함수 내부의 코드로 변경됩니다.
코드가 다소 늘어나지만 함수 호출과 관련된 작업을 하지 않는다는 장점이 있으며, 딱 한번만 사용하는 함수가 있을 때 사용하는 경우가 종종있습니다.
fun main(){ test1() test2() } inline fun test1(){ println("인라인 함수1") println("인라인 함수2") println("인라인 함수3") } fun test2(){ println("일반 함수1") println("일반 함수2") println("일반 함수3") }
출력결과
인라인 함수1 인라인 함수2 인라인 함수3 일반 함수1 일반 함수2 일반 함수3
디컴파일된 자바 코드(메인 부분)
public final class StudyKt { public static final void main() { int $i$f$test1 = false; String var1 = "인라인 함수1"; System.out.println(var1); var1 = "인라인 함수2"; System.out.println(var1); var1 = "인라인 함수3"; System.out.println(var1); test2(); } // $FF: synthetic method public static void main(String[] var0) { main(); } }
다음 으로는 확장 함수에 대해서 학습하였습니다.
확장 함수는 이미 있는 클래스에 메서드를 추가하는 개념입니다.
추가된 메서드는 같은 프로그램 내에서만 사용이 가능하며, 자바 코드로 변결될 때
객체의 ID를 받아 사용하는 코드로 변경됩니다.
fun main(){ var integer = 1000 var integer2 = 150 println(integer.getAddString()) println(integer.nowValue()) println("---------------------------") println(integer2.getAddString()) println(integer2.nowValue()) } fun Int.getAddString() : String{ return "문자열 추가"+this } fun Int.nowValue() = when(this){ in 0 .. 100 -> "0 이상 100 이하" in 101 .. 200 -> "101이상 200 이하" else -> "200보다 큰수" }
출력 결과
문자열 추가1000 200보다 큰수 --------------------------- 문자열 추가150 101이상 200 이하
다음으로는 인픽스(infix) 함수에 대해서 학습 하였습니다.
인픽스 함수는 함수 호출을 연산자를 호출하듯이 사용할 수 있는 함수 입니다.
형태 : 값1 함수이름 값2
값1 객체를 통해 함수를 호출하고 매개변수로 값2를 전달합니다.
fun main(){ // 인픽스 함수 사용 val t1 = 100 val r1 = t1.customAdd(200) val r2 = t1 customAdd 200 println("r1 : $r1") println("r2 : $r2") // 클래스 내 선언한 인픽스 함수 사용 val t2 = Test() t2.num1 = 200 val t3 = Test() t3.num1 = 500 val result = t2.customAdd2(t3) println("result : ${result.num1}") } infix fun Int.customAdd(a1:Int):Int{ // 여기서 this는 첫 번째 값을 의미함. // 즉 호출한 객체의 값 return this+50+a1 } class Test{ var num1 = 100 infix fun customAdd2(a1:Test):Test{ val r1 = this.num1 + a1.num1 val t1 = Test() t1.num1 = r1 return t1 } }
출력 결과
r1 : 350 r2 : 350 result : 700
다음으로는 고차함수에 대해서 배웠습니다.
고차함수는 함수를 매개변수로 받거나 반환 타입이 함수인 함수입니다.
강사님께서 제가 하기에 작성한 방식보다 더 많이 알려주셨지만 제가 생각했을 때 유용한 것들만 작성하였습니다.
fun main(){ val t1 = test1(100,200,fun(a1:Int,a2:Int):Int{ return a1 + a2 }) println(t1) // 전달하는 메서드는 람다식으로도 작성이 가능하다. val t2 = test1(100,200,{a1:Int,a2:Int -> a1+a2}) println("t2 : $t2") // 기본적으로 함수를 매개변수로 전달할 때 함수는 가장 뒤에 작성한다. // 뒤에 쓸 경우 하기와 같이 작성 할 수 있다. val t3 = test1(200,300){a1:Int, a2:Int -> a1 + a2} println("t3 : $t3") test2 { println("""고차함수를 이용해 다른 곳에서 |호출하는 메서드를 오버라이딩 하는 |작업을 대신 할 수 있다""".trimMargin()) } test2 { for(i in 0 .. 10){ print("i = $i ") } } println() // 반환값으로 람다식을 반한함. val t4 = test3(100) val t5 = t4(200) println("t5 : $t5") } fun test1(a1:Int, a2:Int , m1:(Int, Int) -> Int):Int{ return m1(a1,a2) } fun test2(m1:() -> Unit){ m1() } fun test3(a1:Int) : (Int) -> Int{ return {r2 -> a1 * r2} }
출력결과
300 t2 : 300 t3 : 500 고차함수를 이용해 다른 곳에서 호출하는 메서드를 오버라이딩 하는 작업을 대신 할 수 있다 i = 0 i = 1 i = 2 i = 3 i = 4 i = 5 i = 6 i = 7 i = 8 i = 9 i = 10 t5 : 20000
이제 마지막으로는 let, apply, run, with, also 에 대해서 학습하였습니다.
해당 함수들을 배우면서 개발자의 취향에 따라 쓰는 것만 쓴다고 말씀해주시면서, 일단은 지금 배우는 입장이니 모두 어떻게 동작하는지에 대해서 자세하게 설명 해주셨습니다.
let : 객체에 접근할 때 it 변수를 사용하며, 코드 블럭 마지막에 작성된 값이나 수식을 반환합니다.
let은 객체 생성 후 나중에 프로퍼티에 값을 저장하고자 할 때 사용합니다.
apply : 생성된 객체의 ID 가 apply 쪽으로 전달되어 this(혹은 생략)을 통하여 객체의 프로퍼티에 접근할 수 있습니다.
{ } 블럭 내부의 코드가 수행이 끝나면 생성된 객체의 ID가 변수에 담김니다. 그렇기에 { } 블럭 내부에 객체의 ID 를 반환하는 코드를 작성하지 않습니다.
run : 객체를 생성한 후에 run 코드 블럭 내로 객체가 전달되기 때문에 this(혹은 생략)을 통해 프러퍼티에 접근 할 수 있습니다. run { } 블럭의 수행이 끝나면 제익 마지막에 작성한 값이 반환되어 변수에 담깁니다. 이에 생성된 객체의 ID를 전달하고자 한다면 run { } 블럭 제일 아래에 this를 작성하여 객체의 ID를 반환해야 합니다.
강사님의 경험에서 나온 편한 방법
객체를 생성하면서 바로 프로퍼티의 값을 설정하겠다면 -> apply
객체를 생성하고 나중에 프로퍼티의 값을 설정하겠다면 -> run
with : 생성한 객체를 ( ) 안에 넣어줘야하며 { } 내에는 with에 지정된 객체의 ID가 전달되기 때문에 this(혹은 생략)로
프로퍼티에 접근할 수 있다. 객체를 생성할 때 프로퍼티에 값을 설정하기 보단 run 처럼 객체를 생성한 이후 나중에 프로퍼티의 값을 새롭게 저장하기 위해 사용한다. run 보다 작성하는 코드가 더 많기 때문에 비교적 잘 사용하지는 않는다.
also : 객체를 생성과 함께 프로퍼티의 값을 설정하는 작업을 하면 객체의 ID가 it이라는 변수로 전달되고 그 것을 통해 객체의 프로퍼티에 접근할 수 있다. 하지만 let과 다르게 생성된 객체의 ID가 반환되므로 마지막에 객체의 ID를 반환하지 않아도 된다.
저도 작성하면서 apply와 run이 편하였기에 apply와 run 코드예제만 하기에 적도록 하겠습니다.
fun main(){ val t1 = Test1(100, 200).apply { println("apply 실행") println("프로퍼티 접근할 때 this 혹은 생략") println("a1 : $a1") println("a2 : $a2") println("a3 : $a3") println("a4 : $a4") println("반환하지 않아도 t1에 저장됨") } println("반환값을 적지 않아도 t1 객체id 값이 저장되어 있음 : $t1") val t2 = Test1(500,600).run { println("run 실행") println("프로퍼티 접근할 때 this 혹은 생략") println("a1 : $a1") println("a2 : $a2") println("a3 : $a3") println("a4 : $a4") println("this를 사용하여 ID를 반환하는 코드를 작성하지 않으면 반환을 하지 않음.") } println("반환값을 적지 않은면 t2 객체id 값이 저장되어 있지 않음 : $t2") } class Test1(var a1: Int, var a2:Int){ var a3 = 300 var a4 = 400 }
출력결과
apply 실행 프로퍼티 접근할 때 this 혹은 생략 a1 : 100 a2 : 200 a3 : 300 a4 : 400 반환하지 않아도 t1에 저장됨 반환값을 적지 않아도 t1 객체id 값이 저장되어 있음 : Test1@1d81eb93 run 실행 프로퍼티 접근할 때 this 혹은 생략 a1 : 500 a2 : 600 a3 : 300 a4 : 400 this를 사용하여 ID를 반환하는 코드를 작성하지 않으면 반환을 하지 않음. 반환값을 적지 않은면 t2 객체id 값이 저장되어 있지 않음 : kotlin.Unit
마무리
오늘은 코틀린 내 함수들을 위주로 학습하였습니다.
옛날에 혼자 독학하였을때 run,apply 등 함수들을 책으로만 봤었기에 이해가 안되서 그냥 거의 넘어갔었는데 이번에 배우면서 이해가 잘되어 뭔가가 뚫린듯한 느낌을 받았었습니다.
강의를 들으면서 과거 막혔던 부분들이 뚫려 점점 성장하는 느낌을 받아 뿌듯합니다.
그리고 주말에는 자격증 공부를 하고있는데 강사님께서 강의를 해주시면서 해당 개념에 대해서 이해하기 쉽도록 설명을 해주셨던 부분이 자격증 공부를 하면서 도움이 많이 되고 있습니다. 약간 느낌이 자격증 공부를 하면서 강사님께서 해주셨던 말씀들을 복습 및 심화하여 공부하고 있는 기분이여서 현재까지는 원활하다라는 느낌을 받고 있습니다.
오늘의 마음가짐
초심을 잃지 말고 꾸준히 하여 추상클래스 같은 내 지식들을 채워나가자~!
'[THEC!T] 앱 스쿨2기 : Android' 카테고리의 다른 글
TECHIT 앱 스쿨 2기: Android 21일차 (23.05.24) (0) 2023.05.25 TECHIT 앱 스쿨 2기: Android 20일차 (23.05.23) (0) 2023.05.24 TECHIT 앱 스쿨 2기: Android 18일차 (23.05.18) (0) 2023.05.18 TECHIT 앱 스쿨 2기: Android 17일차 (23.05.17) (0) 2023.05.17 TECHIT 앱 스쿨 2기: Android 16일차 (23.05.16) (1) 2023.05.17