본문 바로가기

안드로이드/Kotlin

[Kotlin] 개요, Java와의 차이점 1

Kotlin

Jetbrain에서 Java를 대체하기 위해 만든 언어로 아래와 같은 특징이 있다

  • 널 안정성
  • 가변/불변 구분(var, val)
  • 람다식
  • 스트림 API
  • 클래스 프로퍼티에 대해 getter/setter 제공

특이한 점은 Kotlin은 원시 타입을 모두 객체로 표현한다.

예를들어, 아래와 같이 객체(Int)로 선언한다.

val foo : Int = ...

 

이렇게 표현할 경우 비효율적일거 같지만, 컴파일 과정에서 가장 효율적인 타입으로 변화된다.

위의 코드는 아래와 같이 표현된다.

int foo = ... ;

 

Java와의 비교

배열

Java의 경우 아래와 같이 배열 자료형이 별도로 존재한다.

int[] num = new int[]{1,2,3};

하지만, Kotlin의 경우 Array 객체로 표현한다.

val num : Array<Int> = arrayOf(1,2,3)

 

컬렉션(Collections)

Kotlin에선 Java의 컬렉션 클래스들을 그대로 사용할 수 있다. 다만, 컬렉션 내 자료를 수정할 수 있는지 여부에 따라 가변 타입(mutable)과 불변 타입(immutable)로 나뉜다.

자료구조 수정 불가 수정 가능
List List MutableList
Map Map MutableMap
Set Set MutableSet

 

lateinit 

가변타입 변수를 이용할 때 사용 가능한 키워드이다. 해당 키워드를 사용할 경우, 변수 선언 단계에서 초기화를 하지 안하도 된다. 하지만, 이 값이 초기화되지 않고 사용된다면 예외가 발생할 것이다. 이는 컴파일 단계에서 확인되지 않는 문제이므로 lateinit을 사용한 변수는 초기화가 되었는지 확인하는 코드가 필수적이다.

lateinit var address : String?

 

클래스와 생성자

Java에서는 메서드를 정의하는 것과 유사하게 생성자를 정의한다.

public class Foo{
    public Foo(){
    }
}

 

코틀린의 경우 init 블록을 통해 디폴트 생성자를 대체한다. 또한, 인자가 필요한 경우 클래스 우측에 명시한다.

class Foo(a:Int){
	init{
    	Log.d("FOO", "Number:$a")
    }
}

 

만약, 클래스에 프로퍼티를 인자로 받아 바로 할당하는 것을 원한다면 아래와 같이 작성 가능하다.

class Foo(val a:Int, val b:Char){

}

아래는 위와 같은 기능을 하는 Java 코드이다.

public class Foo{
    int a;
    char b;
	public Foo(int a, char b){
    	this.a = a;
        this.b = b;
    }
}

 

메인 생성자 외에 다른 형태의 매개변수를 받는 생성자가 필요한 경우 아래와 같이 정의 가능하다.

class Foo(val a:Int, val b:Char){

    //a의 값만 인자로 받는 생성자
    constructor(a:Int):this(a,0)
    
    //두 인자의 값을 모두 0으로 설정하는 생성자
    constructor():this(0,0)
    
}

위 처럼 새로운 생성자를 사용할 경우 주 생성자 this를 통해 필수적으로 호출해야된다.

 

상속

Java의 경우 final 키워드를 통해 다른 클래스들이 상속을 할 수 없게한다. 하지만, Kotlin의 경우 기본적으로 모든 클래스는 상속 불가능한 상태로 정의되며, 상속을 허용하려면 open 키워드를 사용해야 한다.open 키워드가 붙이 않은 클래스나 함수가 아니라면 클래스나 함수를 재정의할 수 없다.

open class OpenClass{
    //상속한 클래스에서 재정의 가능
    open val openProperty = "foo"
    
    //상속한 클래스에서 재정의 불가 
    val finalProperty ="bar"
    
    
    //상속한 클래스에서 함수를 재정의 가능
    open fun openFunc(){}
    
    //상속한 클래스에서 함수 재정의 불가
    fun finalFunc(){}
}

class FinalClass : OpenClass(){
    override val openProperty = "FOO"
    //override val finalProperty ="BAR" - 불가
    
    //함수를 재정의 가능
    open fun openFunc(){Log.d("Log","openFunc()")}
    
    //fun finalFunc(){Log.d("Log","finalFunc()")} - 불가
}
    

 

this

this는 가장 가까운 범위의 클래스를 의미한다. 보통, this만 사용할 경우 키워드를 사용하는 위치에 따라 this가 의미하는 클래스가 달라지기 때문에 Java에서는 클래스명.this를 사용한다. Kotlin에서는 대신 this@클래스명 형태로 표기한다.

 

companion object

Kotlin의 경우 Java와 달리 정적(static) 필드, 함수를 클래스 내에 둘 수 없다. 대신, 클래스별로 클래스 인스턴스 생성 없이 사용할 수 있는 오브젝트(object)를 정의할 수 있는데, 이를 companion object라고 한다.

//생성자가 private이므로 외부에서 접근 불가
class User private constructor(val name:String, val registerTime:Long){
    //Companion object는 내부에 존재하므로 private으로 선언된 생성자에 접근 가능
    companion object{
	    fun create(name:String):User {
    	    return User(name, System.currentTimeMillis())
        }
    }
}

 

싱글톤(singleton)

싱글톤은 단 하나의 객체만 생성되도록 제약을 둔 디자인 패턴이다. Java에서는 싱글톤 객체를 생성하기 위해 복잡한 작업이 필요하지만, Kotlin에서는 이를 object를 통해 간편하게 사용할 수 있다.

//싱글톤
object Foo {
    val FOO = "foo"
    
    fun foo() { }
}

//실사용
val fooVlaue = Foo.FOO
Foo.foo()

 

enum

Java에서는 enum이라는 자료형이 존재하지만, Kotlin의 경우 enum class를 사용한다. 선언 형태만 다르고 사용법은 완전히 일치한다.

enum class Direction{
    NORTH, SOUTH, WEST, EAST
}

 

중첩 클래스

Java에서는 특정 클래스 간 종속관계가 존재할 겨웅 중첩 클래스로 이를 표현할 수 있다.

클래스의 특징에 따라 정적 중첩 클래스와 비정적 중첩 클래스로 나뉜다.

  • 정적 중첩 클래스는 외부 자원을 사용할 수 없고, Outer 클래스 없이 단독으로 인스턴스화 가능하다.
  • 비정적 중첩 클래스는 외부 자원을 사용할 수 있조, Outer 클래스가 인스턴스화 되어야 Outer 내에서 인스턴스화 가능하다.
class Outer{
    //정적 중첩 클래스
    class StaticNested{
    }
    
    //비정적 중첩 클래스 --> inner class: 내부에서만 유효한 클래스
    inner class NonStaticNested{
    }
}

//정적: Outer 인스턴스 없이 생성
val staticInstance = Outer.StaticNested();
//비정적: Outer 인스턴스 생성 후 생성
val nontStaticInstance = Outer().StaticNested();

 

내용 일부는 '커니의 코틀린' 도서를 참조하였습니다.