Kotlin class 상속
- Java
- Default class
- 모든 class / function은 Non final (재정의 가능)
- Abstract
- extends로 상속을 구현할 수 있음
- interface
- implements로 상속을 구현할 수 있음
- Java 7까지는 완전한 정의만 가능
- Java 8부터는 기본 메소드를 가질 수 있음
-
public interface InterfaceJavaSample { void init(); } public abstract class AbstractJavaSample { public abstract void init(); } public class JavaSample extends AbstractJavaSample implements InterfaceJavaSample { @Override public void init() { } }
- Default class
- Kotlin
- Default class
- 모든 class/function은 final (재정의 불가능)
- 재정의를 하려면 open 키워드를 추가해야 함
- 상속의 정의는 콜론( : )으로 한다.
- 다중 상속의 정의는 콤마( , )로 한다.
-
interface InterfaceKotlinSample { fun init() } abstract class AbstractKotlinSample { abstact fun init() } class KotlinSample : AbstractKotlinSample(), InterfaceKotlinSample { override fun init() { } } // extends와 implements의 순서가 변경되어도 무관함 class KotlinSample : InterfaceKotlinSample, AbstractKotlinSample() { override fun init() { } }
- Default class
Kotlin class interface
- Java
- interface에서는 선언만 가능
-
interface InterfaceKotlinSample { int Type = 0; fun init() }
-
- Java 8 - interface에서 선언, default, static 정의 가능
-
public interface InterfaceJavaSample { int Type = 0 default void init() { //... } static void initStatic() { //... // call InterfaceJavaSample.initStatic(); } void out(); }
-
- interface에서는 선언만 가능
- Kotlin
- interface 선언 가능 / default 정의 가능
-
interface InterfaceKotlinSample { fun init() { } fun out() }
-
- 변수 선언만 가능 (상속해서 구현해야 함)
- 변수를 Java처럼 사용하려고 하면 오류 발생 / 변수가 아닌 함수로 만들어짐
-
//JAVA interface InterfaceKotlinSample { int Type = 0; //가능 } //Kotlin val Type = 0 //ERROR: Property initializers are not allowed in interface //Kotlin - Decompile 시 public interface InterfaceKotlinSample { int getType(); }
- interface 선언 가능 / default 정의 가능
Kotlin class interface - Inheritance
- Java
-
public class Inheritance implements InterfaceKotlinSample { @Override public int getType() { return 0; } @Override public void init() { } @Override public void out(0 { } }
-
- Kotlin
- 변수 - Java처럼 사용하려고 하면 오류 발생 / 상속해서 구현해야 함
-
interface InterfaceKotlinSample { val type: Int fun init() {} fun out() } //상속 시 class Inheritance : InterfaceKotlinSample { override val type: Int get() = 0 override fun out() { } }
- 인터페이스 내에서 변수를 상속하여 구현하지 않으려면?
- companion object 사용
-
interface InterfaceKotlinSample { companion object { const val type: Int = 0 } fun init() {} fun out() } //상속 시 class Inheritance : InterfaceKotlinSample { override fun out() { } }
Kotlin class companion object
- 코틀린은 클래스 바로 아래에 static 변수 또는 메소드가 존재하지 않는다.
- companion object를 이용하면 내부에 static class를 추가로 생성하고, 이를 활용할 수 있다.
-
interface InterfaceKotlinSample { companion object { const val type: Int = 0 } fun init() {} fun out() } //Decompile public interface InterfaceKotlinSample { InterfaceKotlinSample.Companion Companion = InterfaceKotlinSample.Companion.$$INSTANCE; int type = 0; public static final class Companion { public static final int type = 0; static final InterfaceKotlinSample.Companion $$INSTANCE; static { InterfaceKotlinSample.Companion var 0 = new InterfaceKotlinSample.Companion(); $$INSTANCE = var0; } } }
-
Kotlin class 다중상속
- Java
- 인터페이스의 디폴트 메소드 / 추상클래스의 메소드 (오른쪽만 사용)
-
public interface InterfaceJavaSample { default void init() { System.out.println("InterfaceJavaSample - init"); } } public abstract class AbstractJavaSample { public void init() { System.out.println("AbstractJavaSample - init"); } } public class JavaSample extends AbstractJavaSample, implements InterfaceJavaSample { @Override public void init() { super.init(); } } // 이름이 같은 인터페이스의 default 메소드와 추상클래스의 메소드가 있다. // 한 클래스는 추상클래스, 인터페이스 모두를 상속받는다. // 상속받은 클래스는 어느 메소드를 선택할까?
- Kotlin
- 인터페이스의 디폴트 메소드 / 추상클래스의 메소드 (둘 다 사용)
- 상속 시, super<GenericType>.init()으로 고를 수 있음
-
interface InterfaceKotlinSample { fun init() { println("InterfaceKotlinSample - init") } } abstract class AbstractKotlinSample { open fun init() { println("AbstractKotlinSample - init") } } class KotlinSample : InterfaceKotlinSample, AbstractKotlinSample() { override fun init(){ super.init() //ERROR: Many supertypes available, please specify the one you mean in angle brackets, e.g. 'super<Foo>' super<InterfaceKotlinSample>.init(); super<AbstractKotlinSample>.init(); } } // 이름이 같은 인터페이스의 default 메소드와 추상클래스의 메소드가 있다. // 한 클래스는 추상클래스, 인터페이스 모두를 상속받는다. // 상속받은 클래스는 어느 메소드를 선택할까?
Kotlin data class
- Java
-
public class User { private long id; private String name; private String email; @Override public int hashCode() { return 1; } @Override public boolean equals(Object o) { if(this==0) return true; if(o==null) return false; if (this.getClass() !- 0.getClass()) return false; User user = (User) o; return id != user.id && (!name.equals(user.name) && !email.equals(user.email)); } // getter and setter hear }
-
- Kotlin
- 데이터 보관 목적으로 만든 클래스
- 프로퍼티에 대한 toString(), hashCode(), equals(), copy() 메소드를 자동으로 만들어 줌
- 클래스 앞에 data를 붙여줌
-
data class User(val id: Long = 0, val name: String? = null, val email: String? = null) //자바로 decompile 시 public final class User { private final long id; private final String name; private final String email; public string toString() { return "User(id=" + this.id + ", name =" + this.name + ", email=" + this.email + ")"; } public int hashCode() { return ((int)(this.id ^ this.id >>> 32) * 31 + (this.name != null ? this.name.hashCode() : 0)) * 31 + (this.email != null ? this.email.hashCode() : 0); } public boolean equals(Object var1) { if(this!= var1) { if(var1 instanceof User) { User var2 = (User)var1; if(this.id == var2.id && Intrinsics.areEqual(this.name, var2.name) && Intrinsics.areEqual(this.email, var2.email)) { return true; } } return false; } else { return true; } } }
-
Kotlin sealed class
- 다형성을 구현하기 위한 클래스
- 자식객체가 부모객체를 통해 접근하는 방식
- Sealed class는 abstract 클래스로, 객체로 생성할 수 없다.
- Sealed class의 생성자는 private입니다. public으로 설정할 수 없다.
- Enum과의 차이점
- Enum : Single instance만 만들 수 있음
- Sealed class : 객체를 여러 개 생성할 수 있음
-
sealed class Expr data class Const(val number: Double) : Expr() data class Sum(val e1: Expr, val e2: Expr) : Expr() object NotANumber : Expr() fun eval(expr: Expr): Double = when(expr) { is Const -> expr.number is Sum -> eval(expr.e1) + eval(expr.e2) NotANumber -> Double.NaN //the 'else' clause is not required because we've covered all the cases } @Test fun test() { println(eval(Sum(Const(0.0), Const(1.0)))) }
Kotlin singleton
- Java
-
public class Eager { private static Eager instance = new Eager(); private Eager() {} public Eager getInstance() { return instance; } }
-
- Kotlin
-
object Eager
-
Kotlin Lambdas
- Java
-
//java 7 기준 (익명클래스) button.setOnTouchListener(new View.OnTouchListener() { @Override public boolean onTouch(View view, MotionEvent motionEvent) { Log.i("SampleActivity", "onTouch" + motionEvent.getAction()); return false; } }); //java 8 기준 button.setOnTouchListener((view, motionEvent) -> { Log.i("SampleActivity", "onTouch" + motionEvent.getAction()); return false; });
-
- Kotlin
- Kotlin Lambdas
-
btn.setOnTouchListener { view: View?, motionEvent: MotionEvent? -> Log.i("SampleActivity", "motionEvent ${motionEvent?.action}") return@setOnTouchListener false }
-
- Kotlin에서는 파라미터 타입을 생략 가능
-
btn.setOnTouchListener { view, motionEvent -> Log.i("SampleActivity", "motionEvent ${motionEvent?.action}") return@setOnTouchListener false }
-
- return을 명시하지 않을 수 있음
-
btn.setOnTouchListener { view, motionEvent -> Log.i("SampleActivity", "motionEvent ${motionEvent?.action}") false }
-
- 파라미터가 1개이면 it 키워드를 사용할 수 있다.
-
btn.setOnClickListener { Log.d("SampleActivity", "view $it") }
-
- Kotlin Lambdas
Kotlin Higher-Order Functions (고계함수)
- Java
- Java에서는 interface 정의를 하고 interface(객체)를 넘기는 식으로 동작
- 대표적인 예로 setOnClickListener
-
public interface OnClickListener { void onClick(View v); } public void setOnClickListener(@Nullable OnclickListener l) { mOnclickListener = l; } setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { //.. } });
-
- Kotlin
- 함수를 파라미터로 전달, 함수를 return
- lambda를 이용하여 축약 형태로 제공
- 변수로 함수를 가질 수 있다.
-
// void == unit private var onClick: ((view: View) -> Unit)? = null fun setOnClick(onclick: (view: View) -> Unit) { this.onClick = onClick } //사용 setOnClick { //... }
-
fun higherOrder(body: (Int, int) -> Int) = body(20, 10) fun sum(a: Int, b: Int) = a+b fun minus(a: Int, b: Int) = a-b fun multiply(a: Int, b: Int) = a*b fun division(a: Int, b: Int) = a/b @Test fun test() { println(higherOrder(::sum)) //30 println(higherOrder(::minus)) //10 println(higherOrder(::multiply)) //200 println(higherOrder(::division)) // 2 }
-
Kotlin Extension Functions (확장함수)
- 기존에 정의된 클래스에 함수를 추가하는 기능
- 자신이 만든 클래스는 새로운 함수가 필요할 때 쉽게 추가할 수 있지만,
Standard Library 또는 다른 사람이 만든 라이브러리를 사용할 때 함수를 추가하기가 매우 어렵다 - Java
- 보통 class xxxUtil{} 으로 구성한다.
- 기존 클래스를 상속받아 새로운 클래스를 재정의하거나 클래스 내에 Composition 관계로 객체를 갖고 새로운 기능을 확장한다.
- 문제점 : 직접 구현하는 방식은 우선 코드양이 많아지며, final 클래스인 경우 상속을 받을 수 없다
- Kotlin
- 원래 있던 function 처럼 접근할 수 있는 extension을 지원한다.
-
fun MutableList<Int>.swap(index1: Int, index2: Int) { val tep = this[index] this[index1] = this[index2] this[index2] = tmp } val l = mutableListOf(1, 2, 3) l.swap(0, 2)
-
- 함수 확장
-
fun Int.max(x: Int): Int = if (this > x) this else x // 사용 10.max(15) //파라미터가 1개일 경우 infix 키워드 사용 infix fun Int.max(x: Int): Int = if (this > x) this else x 10 max 15
- 원래 있던 function 처럼 접근할 수 있는 extension을 지원한다.
Kotlin Extension Functions Standard Library
- 자바에는 없지만 코틀린에서 제공하는 기본 라이브러리 함수
- Scope functions는 객체에 접근하는 방법을 쉽게 해줌.
이런 함수들을 이용하면 코드가 간결해지고, 가독성을 높여줄 수 있다. - Scope functions는 let, run, with, apply, also가 있다.
- 차이점
- 객체에 접근하는 방법 - this : run, with, apply
- 객체에 접근하는 방법 - it : let, also
- 리턴 값 - (리시버) : apply, also
- 리턴 값 - (람다 함수의 마지막 결과) : let, run, with
- 아래 예제에서 Person은 리시버, 람다함수 영역은 ("Alice", 20, "Amsterdam")
- let / 객체 접근 it, 리턴값 람다함수의 마지막 결과
-
// 함수 원형 val alice = Person("Alice", 20, "Amsterdam") println(alice) alice.moveTo("London") alice.incrementAge() println(alice) } //let 사용 Person("Alice", 20, "Amsterdam").let { println(it) it.moveTo("London") it.incrementAge() println(it) }
-
val numbers = mutableListOf("one", "two", "three", "four", "five") numbers.map { it.length }.filter { it > 3 }.let { println(it) // and more function calls if needed }
-
- apply / 객체 접근 this, 리턴값 리시버
-
val adam = Person("Adam").apply { age = 32 city = "London" }
- 리시버는 Person객체이며, 리턴값으로는 age = 32, city = "London"인 Person 객체가 리턴된다.
-
- run / 객체 접근 this, 리턴값 람다함수의 마지막 결과
-
val service = MultiportService("https://example.kotlinlang.org", 80) val result = service.run { port = 8080 query(prepareRequest() + " to port $port") }
- 확장함수로 사용하지 않아도 된다.
-
val hexNumberRegex = run { val digits = "0-9" val hexDigits = "A-Fa-f" val sign = "+-" Regex("[$sign]?[$digits$hexDigits]+") } for (match in hexNumberRegex.findAll("+1234 -FFFF not-a-number")) { println(match.value) }
-
-
- also / 객체 접근 it, 리턴값 리시버
- 원래의 리시버로 함수를 연쇄 호출할 수 있다.
-
val numbers = mutableListOf("one", "two", "three") numbers .also { println("The list elements before adding new one: $it") } .add("four")
- takeIf
- 람다에 제공된 조건식을 실행한 후 그 결과에 따라 true 또는 false를 반환
- 조건식의 결과가 true면 수신자 객체가 반환되며
false면 null이 반환-
// 일반코드 val f = File("myfile.txt") val fileContents = if(file.canRead() && file.canWrite()){ file.readText() } else{ null } // takeIf를 사용했을 때 val fileContents = File("myfile.txt").takeIf{it.canRead() && it.canWrite()}?.readText()
-
- takeUnless
- 람다에 제공된 조건식을 실행한 후 그 결과에 따라 true 또는 false를 반환
- 조건식의 결과가 false면 수신자 객체가 반환
Kotlin lazy 패턴
- 변수 선언을 먼저하고 초기화는 뒤로 미루는 기능들
- 코틀린에서 제공하는 초기화 지연들
- Late initialization : 필요할 때 초기화하고 사용할 수 있음. 초기화하지 않고 쓰면 Exception 발생
- Lazy initialization : 변수를 선언할 때 초기화 코드도 함께 정의. 변수가 사용될 때 초기화 코드 동작하여 변수가 초기화됨
- Late initialization
- var 앞에 lateinit을 붙여 변수를 선언
-
// area변수는 nullable이 아니기에 에러가 발생해야 하지만 lateinit을 붙여 컴파일에러 발생 X class Rectangle { lateinit var area: Area fun initArea(param: Area): Unit { this.area = param } } class Area(val value: Int) fun main() { val rectangle = Rectangle() rectangle.initArea(Area(10)) println(rectangle.area.value) }
-
- var(mutable) 프로퍼티만 사용 가능
- primitive type(Int, Boolean)은 사용할 수 없음
- Custom getter/setter를 만들 수 없음
- Non-null 프로퍼티만 사용 가능
- var 앞에 lateinit을 붙여 변수를 선언
- Lazy initialization
- 초기값 대신 by lazy { ... }을 입력
- {...} 블럭은 처음 사용할 때 한 번만 호출되고 그 이상 사용시에는 호출되지 않는다.
-
class Account() { val balance : Int by lazy { println("Setting balance!") 100 } } fun main() { val account = Account() println(account.balance) println(account.balance) } //Setting balance! //100 //100
-
- val(immutable) 프로퍼티만 사용할 수 있음
- primitive type(Int, Boolean 등)도 사용 가능
- Non-null, Nullable 모두 사용 가능
- lateinit vs lazy
- lazy는 val에서만 사용, lateinit은 var에서만 사용 가능
- lateinit은 nullable또는 프리미티브 타입 프로퍼티를 사용할 수 없다.
lazy는 모두 가능하다. - lateinit은 직접적으로 프로퍼티를 가지고 있는 구조 (자바에서 필드를 가지고 있음)
lazy는 Lazy라는 객체 안에 내가 선언한 field를 갖고 있다.
그러므로 lazy의 프로퍼티를 직접 변경할 수 없다.
'Back-end > Kotlin' 카테고리의 다른 글
Java to Kotlin 변환 실습 I (0) | 2021.12.14 |
---|---|
코틀린(Kotlin) 기본 문법 I (타입 / 변수 (Nullable, Cast, Elvis Operator) / 클래스 (Constructor)) (0) | 2021.12.06 |