본 포스팅은 DroidKaigi 2017 ~ 解剖Kotlin ~バイトコードを読み解く 을 기본으로 번역하여 작성했습니다
제 일본어 실력으로 인하여 오역이나 오타가 발생할 수 있습니다.
DroidKaigi 2017 03.09
Room3 15:10~
Toshihiro Yagi
tokubai
슈퍼 게재 점포수 No.1
100% Kotlin
즐거운 장보기를 하자
Kotlin 하고 있나요?
Java에 비해 편리한 것은 알겠지만, 그래도 뭐 별로 Java를 사용하고 크리티컬로 곤란한 것도 아니고 ~ 학습 비용이나 팀에 도입 비용을 생각하면 거기까지 좋다고 느껴지지도 않고 ~
최종적으로 아셨으면 하는 것
음, Java로 하면 이런 느낌이구나
Kotlin 간단하고 편리하네
Kotlin 해볼까~
Tools > Kotlin > Show Kotlin Bytecode
nullable 타입
nullable 타입은 파라매터, 변수, 반환값이 null이 될 수 있는지 여부를 명시적으로 선언하는 기능
val name:String? = null
? : 타입 끝에 ?를 붙이는 것으로 선언할 수 있다
한편으로 비 null 타입은 null을 대입할 수 없다. 변수, 파라매터, 반환값이 비 null 타입이라면, 값이 반드시 비 null이라는 것을 보증한다
val name:String = "Hello" // OK
val name:String = null // Error
fun toString() :String // 반환값이 비 null이라는 것을 보증한다
str.length // 컴파일 에러가 된다
// null이라면 무시한다
str?.length
// null의 경우 NullPointerException
str!!.length
fun semitransparent(view: View) {
view.alpha = 0.5f
}
View: 함수내에서 view가 null이 아니라는 것을 보증한다
val text: View? = findViewById(R.id.text)
semitransparent(text)
Type missmatch로 컴파일 에러
위험한 호출은 위험하므로 고쳐야 한다라는 것을 알 수 있다
val text: View? = findViewById(R.id.text)
semitransparent(text!!) // 위험한 호출. 개선 신호
위험한 호출은 위험하므로 고쳐야 한다라는 것을 알 수 있다
val text: View? = findViewById(R.id.text)
text?.let { semitransparent(it) } // null이 아니면 실행
nullable 타입은 Java에서 어떻게 실현되어 있는가?
val nonNull: String = ”nonNull”
nonNull.length
↓ Java로 되는것뿐
String nonNull = “nonNull";
nonNull.length();
val nullable: String? = null
nullable?.length
↓
String nullable = (String)null;
if(nullable != null) {
nullable.length();
}
null 체크가 추가된다
nullable!!.length
↓
if(nullable == null) {
Intrinsics.throwNpe();
}
nullable.length()
null이라면 예외를 throw
fun length(text: String) = text.length
↓
public final int length(@NotNull String text) {
Intrinsics.checkParameterIsNotNull(text, “text");
return text.length();
}
파라매터가 null이라면 예외를 throw
?.
라면 null 체크를 추가한다!!
라면 null 체크와 예외 throw를 추가한다함수 타입, 람다식
val onClick: (View) -> Unit // 인수에 View를 받고, 반환값이 없는 함수를 나타낸다
fun calc(a: Int, b: Int, op: (Int, Int) -> Int): Int {
return op(a, b)
}
calc(1, 2, { a, b -> a + b })
-> (Int, Int) -> Int 타입을 그대로 작성할 수 있다
listOf(1, 2, 3)
.filter { it % 2 == 0 }
.map { it * 2 } // 4
함수 타입과 람다식은 Java로 어떻게 실현되고 있는지?
val onClick: (View) -> Unit
↓
@NotNull
private final Function1 onClick; // 정확하게는 Function1<View, Unit>
public interface Function1<in P1, out R> : Function<R> {
public operator fun invoke(p1: P1): R
}
Function1은 단순한 인터페이스
함수 타입은 인수의 수에 따라 FunctionN으로 변환된다
calc(1, 2, { a, b -> a + b })
↓
calc(1, 2, (Function2)null.INSTANCE);
컴파일러가 람다식을 정적이라고 판단한 경우는 싱글톤 클래스를 생성한다
final class call$1 extends kotlin.jvm.internal.Lambda implements Function1 { // Lambda를 상속
public call$1() {
super(2); // 인수의 갯수를 부모 클래스에 전달
}
public final int invoke(int a, int b) { // Function2를 구현
return a + b;
}
public final static $call$1 INSTANCE = new call$1(); // 싱글톤
}
수동으로 고치면 이런 느낌
val seed = 10
calc(1, 2, { a, b -> a + b + seed }
↓
final byte seed = 10;
calc(1, 2, (Function2)(new Function2(2) {
public final int invoke(int a, int b) {
return a + b + seed;
}
}));
클로저의 경우는 무명 클래스를 생성한다
var seed = 10
calc(1, 2, { a, b -> seed++; a + b })
↓
final IntRef seed = new IntRef();
seed.element = 10;
calc(1, 2, (Function2)(new Function2(2) {
public final int invoke(int a, int b) {
int var3 = seed.element++;
return a + b;
}
}));
캡쳐한 변수를 변경하는 경우는 Ref 클래스가 사용된다
확장 함수
fun Int.reversed(): Int {
return toString().reversed().toInt()
}
334017.reversed() // 710433
context.getSystemService(Context.WINDOW_SERVICE) as WindowManager
↓
context.getWindowService()
확장 함수는 Java로 어떻게 실현되고 있는지?
fun Context.getWindowService() = getSystemService(Context.WINDOW_SERVICE) as WindowManager
↓
@NotNull
public static final WindowManager getWindowService(@NotNull Context $receiver) {
Intrinsics.checkParameterIsNotNull($receiver, “$receiver");
Object var10000 = $receiver.getSystemService(“window");
if(var10000 == null) {
throw new TypeCastException("null cannot be cast to non-null type android.view.WindowManager”);
} else {
return (WindowManager)var10000;
}
}
fun Context.getWindowService() = getSystemService(Context.WINDOW_SERVICE) as WindowManager
↓ ※ 싱글톤으로 한 것
public static final WindowManager getWindowService(Context $receiver) {
Object var10000 = $receiver.getSystemService(“window");
return (WindowManager)var10000;
}
대상 클래스를 첫 인수로 받는 정적 함수가 생성된다
context.getWindowService()
↓
ExtensionsKt.getWindowService(context)
여기는 확장 함수를 정의한 클래스에 의존한다
프로퍼티
class Product(val id: Int, var name: String)
val product = Product(1, “tomato")
val id = product.id
product.name = "meat"
class Product(val id: Int) {
var name: String = ""
get() = field
set(value) {
field = value
}
}
프로퍼티 accessor는 반드시 accessor를 거친다. accessor 내에서만 접근가능한 Backing Field 라는 실체가 있다.
프로퍼티는 Java로 어떻게 실현되고 있는지?
class Product(val id: Int, var name: String)
↓
private final int id;
@NotNull // private인 field가 선언된다
private String name;
public final int getId() { // val의 경우 getter만 생성된다
return this.id;
}
@NotNull
public final String getName() { // var의 경우 getter/setter가 생성된다
return this.name;
}
public final void setName(@NotNull String var1) {
Intrinsics.checkParameterIsNotNull(var1, “<set-?>");
this.name = var1;
}
val id = product.id
product.name = "meat"
↓
int id = product.getId();
product.setName("meat");
여기까지 정리
설계에 미치는 영향
nullable 타입
null 체크를 추가하고 처리를 건너뛰거나 예외를 throw 하는 것뿐인 간단한 구조이지만 단지 그것만으로 설계에 영향이 있는것일까?
@Nullable, @NonNull을 활용하거나 null 체크를 제대로 구현하면 되지 않나??
fun semitransparent(view: View) {
view.alpha = 0.5f
}
함수내에 view가 비 null이라는 것을 보증한다
val text: View? = findViewById(R.id.text)
semitransparent(text) // Type missmatch로 컴파일 에러
fun semitransparent(view: View) {
view.alpha = 0.5f
}
val text: View? = findViewById(R.id.text)
semitransparent(text!!) // 위험한 호출. 개선 신호
fun semitransparent(view: View) {
view.alpha = 0.5f
}
val text: View? = findViewById(R.id.text)
text?.let { semitransparent(it) }
null이 아니면 실행
-> 왠지 장황하고, 애초에 null의 경우는 가정하고 있지 않은가?
-> DataBinding이나 Kotlin Android Extensions 이용을 검토
fun semitransparent(view: View) {
view.alpha = 0.5f
}
semitransparent(binding.text)
nullable 타입이 나타나는 곳을 줄여주는 것으로, null 대해 안전한 범위가 퍼져간다
확장 함수
각각의 함수의 구현 자체는 매번하지 않으면 안되고, 결국 정적 함수 호출을 바꿀 뿐이고 의미있어??
정적 함수를 모은 Util 클래스들을 적절하게 운용하면 좋지 않나??
val id = intent.getIntExtra("id", -1)
이 Intent 매개 변수가 필수인지 여부가 여기서는 읽을 수 없다
inline fun <reified T> Intent.getRequired(key: String): T {
extras?.get(key).let {
if (it !is T) {
throw IllegalArgumentException("$key")
}
return it
}
}
key가 없는 경우 예외를 throw하는 getRequired 함수를 Intent에 추가
val id:Int = intent.getRequired("id")
Intent 자체에 필수값을 추출하는 책무를 추가함으로써 의도가 분명한 호출이 가능하다
Let’s enjoy Kotlin!
이 앞은 당신의 눈으로 확인해줘!
THANK YOU
comments powered by Disqus
Subscribe to this blog via RSS.
LazyColumn/Row에서 동일한 Key를 사용하면 크래시가 발생하는 이유
Posted on 30 Nov 2024