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() {
       }
      }
  • 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() {
          }
      }

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();
        }
  • 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();
        }

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 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

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 프로퍼티만 사용 가능
  • 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의 프로퍼티를 직접 변경할 수 없다.

+ Recent posts