규턴의 개발블로그
article thumbnail

현재 들어간 회사에서는 코프링이 주 핵심 기술스택이기에 해당 기술스택을 배우고자 코틀린 인 액션 스터디를 진행한다.

  • 아래의 내용은 코틀린 인 액션을 저의 방식으로 정리한 글입니다.

인터페이스

  • 코틀린에서의 override는 자바와 달리 @Override annotaion을 사용하지 않음
  • 인터페이스에 프로퍼티 선언이 가능하다.
  • 자바와 동일하게 하나의 클래스에 대해서만 extends가 가능하며, 반대로 여러개의 인터페이스 가능
  • 코틀린에서는 ":" 으로 상속, 인터페이스화  둘다 가능하다
  • kotlin에서의 default 메서드 구현은 자바와 좀 다르다 
    • 추가로 아래의 코드와 같이 두개의 같은 default method를 상속한다면, 반드시 override가 필수적이다
interface Clickable {
    fun click()
    
    fun showOff() = println("default method")
}
interface Clickable {
    fun click()

    fun showOff() = println("default method")
}

interface Focusable {
    fun showOff() = println("default method2")
}

class Button : Clickable, Focusable{
    override fun click() = println(" i was clicked")
    override fun showOff() { //동일한 메서드를 가진 interface를 2개 인터페이스화 하는경우 override 필수
        super<Clickable>.showOff()  //super<~> 를 통해 상위 클래스 지정 가능
        super<Focusable>.showOff()
    }
}

클래스의 상속

  •  기본적으로 코틀린에서 클래스는 final이 붙는다
    • 즉, 일반적으로 상속을 하게되면 에러가 날 수 있다. (반대로 자바는 상속에 기본적으로 열려 있음)
    • 해당 컴파일에러를 고치기 위해선 상위 클래스에서 'open' 변경자를 붙어야 한다.
    • 하지만 추상클래스에서는 'open' 변경자가 필요없다. 추상클래스 자체로는 인스턴스화가 불가능하기 때문
abstract class Animated {
    abstract fun animate() // 추상함수

    open fun stopAnimating(){} // 비추상함수이지만 open으로 override 가능

    fun animateTwice(){} // override 불가능(비추상함수)


}

변경자 표

변경자 설명
final 오버라이드 불가능, 기본변경자
open 오버라이드 가능, open이 있어야 override가능
abstract 반드시 오버라이드 해야함
override 오바리이드는 기본적으로 열려 있음, 원한다면 final로 금지를 시켜야함 

내부 클래스, 중첩클래스 => 기본적으로 중첩 클래스

  • 자바에서는 중첩 클래스를 작성할때 static class A 와 같이 static이 붙게 된다, 반대로 코틀린은 class A로 가능하다.
  • 코틀린에서 일반적으로 중첩클래스는 Class A, 외부 클래스에 참조를 하고자 하는경우에는 inner class A로 지정한다.
  • 코틀린에서 outer class에 접근하려면 this@를 붙어야 한다.
클래스 B안에 정의된 A클래스 자바 코틀린
중첩클래스(바깥쪽 클래스에 대한 참조를 저장하지 않음) static class A class A
내부클래스(바깥쪽 클래스에 대한 참조를 저장) class A inner Class A
//kotlin
class Outer{  
    inner class Inner{  
    	fun test():Outer= this@Outer  
    }  
}

//java
public class OuterJava {

    class Inner{
        public OuterJava getOuterReference(){
            return OuterJava.this;
        }
    }
}

Sealed Class

sealed class Expr {
    class Num(val value:Int) : Expr()
    class Sum(val left:Expr, val right:Expr): Expr()
}

fun eval(e: Expr): Int =
        when (e) {
            is Expr.Num -> e.value
            is Expr.Sum -> eval(e.left) + eval(e.right)
            // else-> throw Exception() 필요없음
        }

뻔하지 않은 생성자와 프로퍼티를 갖는 클래스 선언

  • 클래스 생성자는 아래의 코드와 같이 생성된다.
Class User(val nickname:String , val tf : Boolean = true //디폴트 파라미터도 가능)  
or  
Class User(\_nickname:String){  
    val nickname: String  
    init{  
    	nickname = \_nickname  
    }  
}
  • init 블록은 객체가 만들어질때(인스턴스화 될떄) init블럭내의 코드가 실행됨.
  • 첫번째 방법과 같이 작성한다면 컴파일러가 해당 파라미터에 맞게 생성자를 만들어준다. ( 내가 듣기론 이러한 이유 때문에 kotlin에서는 빌더 패턴이 필요없다고 들었다.)
  • Class User(val nickname:String)에서 val은 해당 파라미터에 상응하는 프로퍼티가 생성된다는 의미이다.
  • 참고로 ' class SecretClass prviate constructor(){}' 와 같은 클래스를 지정하면 해당 생성자만 비공개(private)지정할 수 있다. 이는 추후 유틸함수나 companion object에 해당 기능을 사용하면 좋을 수 있다.(이유는 이후 장에 나옴)

인터페이스에 선언된 프로퍼티 구현

interface User {
    val nickname:String
}



class PrivateUser(override val nickname: String) :User {
}


class SubscribingUser(val email: String):User {
    override val nickname: String
        get() = email.substringBefore("@")

}

class FackebookUser(val accountId:Int):User{
	override val nickname=getFacebookName(accountId)
}
  • 인터페이스에도 프로퍼트 선언이 가능하다
  • 클래스에서 User인터페이스를 상속하려면  인터페이스의 프로퍼티 때문에 override가 필요하다
  • 커스텀 getter를 사용해서도 가능하다.(PrivateUser 참고)
    • 커스텀getter같은 경우는 nickname을 호출할때 마다 customgetter의 로직을 수행하여 Nickname을 가져온다.
    • 반대로 FacebookUser의 nickname같은 경우는 처음 객체가 지정될때 nickname을 저장하고 이후 다음에 nickname을 호출한다면, 이미 저장된 nickname을 호출한다.

 

모든 클래스가 정의해야 하는 메서드

class Client(val name: String, val postalCode: Int) {
    override fun toString(): String {
        return "Cleint(name=$name, postalCode=$postalCode)"
    }

    override fun equals(other: Any?): Boolean {
        if(other == null || other !is Client)
            return false
        return name==other.name && postalCode==other.postalCode
    }

    override fun hashCode(): Int {
        return name.hashCode()*31+postalCode
    }
}
  • 코틀린에서 equals메서드는 자동으로 '=='비교를 할 시에 객체간의 비교인경우 equals가 호출된다
  • 코틀린에서의 is는 자바에서의 instanceof와 같은 역할을 한다. ( a is User == a instanceof User)
  • 원래는 JVMd에서 equals만 정의해서는 의도하지 않는 false값이 나올 수 있다. 실제로는 hashCode도 구현을 해줘야 한다. 하지만 코틀린 컴파일러(자바도 되는지는 정확하지 않음) 해당 메서드를 자동으로 생성해줄수 있다.

Data Class

  • 코틀린의 data클래스는 아래의 3가지 기능을 모두 포함한다.
    • 인스턴스 간 비교를 위한 equals
    • Hashmap과 같이 해시 기반 컨테이너에서 키로 사용할 수 있는 hashCode
    • 클래스의 각 필드를 순서대로 나열해 줄 수 있는 toString

BY

interface IWindow {
    fun getWidth() : Int
    fun getHeight() : Int
}

open class TransparentWindow : IWindow {
    override fun getWidth(): Int {
        return 100
    }

    override fun getHeight() : Int{
        return 150
    }
}

class UI(window: IWindow) : IWindow by window{}


 val window: IWindow = TransparentWindow()
  val ui = UI(window)
  System.out.println("Width : ${ui.getWidth()}, height: ${ui.getHeight()}") // by 키워드를 통해 사용가능

 

object keyword

  • 싱글턴 생성
  • object Payrool { val allEmployees = arrayListOf<Person>() .... }
  • 팩터리 메서드와 정적멤버
    • 아래의 코드는 정적팩터리 메서드(A클래스의 동반객체를 companion 이라는 keyword로 생성가능)
    • 위와 같은 이점은 동반객체는 자신을 둘러싼 클래스의 모든 Private 멤버에 접근할 수 있다. 즉 동반객체는 바깥쪽 클래스의 private 생성자를 호출 할 수 있음
class A{
    companion object {
        fun bar(){
            print("foo hi")
        }
    }
}
  • 동반 객체 object를 사용할때도 interface를 구현할 수 있다.
interface JsonFactory<T>{
    fun fromJson(jsonText:String):T
}

class Persoon (val name:String) {
    companion object : JsonFactory<Persoon> {
        override fun fromJson(jsonText: String): Persoon {
            TODO("Not yet implemented")
        }
    }
}
profile

규턴의 개발블로그

@규턴이

포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!