Kotlin 기본


Main Class

fun main() {
	println("Hello Kotlin")
}

자바에서 메인 함수는 별도의 class 내에 static 메서드로 지정하는 반면, 코틀린에서는 별도의 클래스 정의 없이 main function 선언으로 이상 없이 작동된다.

이는 코틀린 고유의 특성이라기 보다는 간결성을 제공하는 것이라 보면 된다.

실제로 JVM메인 함수가 지정된 파일 이름을 기준으로 자바 클래스가 자동으로 생성된다.

위 파일을 Decompile 해보면 자바 클래스와 유사한 구조의 모습을 볼 수 있다.

Decompile


코틀린 프로젝트 구성 요소

코틀린의 프로젝트는

  • Project
    • Module
      • Package
        • File

과 같이 구성된다.

여기서 Module의 개념이 조금 생소한데 Package의 상위 개념으로써 자바의 경우 1.9 버전부터 지원한다.

아주 간단히 이야기 하자면 Module은 기존의 Package 이하 단위에서 제공하지 않는 상위 수준의 정보 은닉(캡슐화)를 지원하기 위해 탄생했다.

다시말해, 외부에 공개하는 퍼블릭 인터페이스를 class 수준을 넘어 관심사 수준으로 끌어 올림으로써 구현 세부 사항에 접근하지 못하도록 캡슐화 하는 개념이다.


코틀린의 클래스 파일

코틀린이 자바와 다른 점이 또 하나 있다.

자바의 경우는 하나의 .java 파일의 명과 해당 파일 내 public class의 이름이 동일해야 한다.

반면에 코틀린의 경우는 하나의 .kt 파일 내 구성 요소에 대한 제한이 없다.

다시말해, 클래스를 정의하지 않고 변수와 함수만으로 구성이 가능하다. 즉, 자바와 다르게 구성 요소들(변수, 함수)를 클래스 내부에 정의할 필요 없이 외부에 정의가 가능하다.

또한 다른 이름의 클래스를 정의하거나 변수, 함수와 같은 구성요소를 외부에 선언하여 함께 사용 가능하다.

만일 위와 같은 파일이 Hello.kt 라는 파일 명으로 되어있다면 JVM은 이를 어떻게 처리할까?

사실은 Hello.kt와 같은 파일이 곧바로 JVM상에서 실행되는게 아니라, 컴파일 시 적절하게 클래스 파일들로 분리가 된다.

  • 즉, 정의된 T1_User.class 파일
  • 클래스로 묶이지 않은 변수와 함수를 별도로 묶은 HelloKt.class 파일로 묶인다.

이처럼 파일 내에 클래스가 정의되어 있다면 그 클래스에 해당하는 파일 명을 가진 .class 파일이, 그리고 나머지 변수나 함수를 묶은 kt가 붙은 xxxkt.class 파일이 생성된다.


코틀린 기본 패키지(표준 라이브러리 stb-lib)

코틀린의 기본 패키지는 import 키워드를 사용하지 않고도 바로 사용이 가능하다.

  • kotlin.* : Any, Int, Dobule 등 핵심 함수와 자료형
  • kotlin.text.* : 문자와 관련된 API
  • kotlin.sequences.* : 컬렉션 자료형의 하나로 반복이 허용되는 개체들을 열거
  • kotlin.ragnes.* : if 또는 for에서 사용할 범위 관련 요소
  • kotlin.io.* : 입출력 관련 API
  • kotlin.collections.* : List, Set, Map 등의 컬렉션
  • kotlin.annotationl.* : 애너테이션 관련 API


val과 var

코틀린은 변수 선언 시 valvar로 선언할 수 있다.

  • val : value의 약자로, 말 그대로 상수로 자바의 final과 같다. (실제로 Decompile 해보면 final로 선언되어 있음을 확인할 수 있다.) 한번 할당한 이후엔 변하지 않는, read only의 성격을 갖는다.
  • var : variable의 약자로 변수를 의미한다. 자바 일반 선언문과 같다. 글자 그대로 변수이기 때문에 최초 선언 이후 필요에 따라 변경이 가능하다.


코틀린의 자료형

자바의 경우 기본형(Primitive Data Type)참조형(Reference Data Type)이 별도로 존재한다. 반면에 코틀린의 경우 참조형 자료형만을 사용한다.

기본형의 경우 임시 메모리인 Stack에 저장된다.

참조형의 경우 인스턴스 생성 후 Heap에 저장되고 해당 주소값이 Stack에 저장되어 이 주소를 참조하는데, 코틀린은 성능 최적화를 위해 코틀린 컴파일러가 다시 기본형으로 대체한다. 따라서 참조형에 따른 성능 이슈는 걱정하지 않아도 된다.


(기본적으로)null을 허용하지 않는다.

코틀린은 변수를 사용할 때 반드시 값이 할당되어 있어야 한다는 원칙이 있다. 만약 null 변수를 사용하려 하면 오류가 발생한다.

즉, 코틀린은 기본적으로 변수에 null을 허용하지 않는다.

코틀린에서는 null 상태의 변수를 허용하려면 ? 기호를 사용하여 선언해야 한다.


세이프 콜과 non-null 단정 기호를 활용한 null 변수 사용

위 코드에서 str1의 길이를 구하기 위해 str1.length 표현 시 compile error가 표시된다.

Only safe (?.) or non-null asserted (!!.) calls are allowed on a nullable receiver of type String?

null을 허용한 ? 타입에서는 세이프콜(?.) 또는 non-null 단정(!!)만 허용한다는 것을 알 수 있다.


세이프 콜

세이프 콜null이 할당되어 있을 가능성이 있는 변수(? 타입)을 사전에 검사하여 안전하게 호출될 수 있도록 한다. 세이프 콜을 추가하려면 ?.를 추가해주면 된다.

println("$str1 length : ${str1?.length}") // output : "null length is null"

세이프 콜은 먼저 변수(str1)을 검사한 다음 null이 아니면 해당 변수에 접근하여 값을 읽는다. 만일 검사를 했는데 null이라면 변수에 접근하지 않고 null을 표현한다.


non-null 단정

non-null 단정은 변수에 할당한 값이 null이 아님을 단정 하므로 컴파일러가 (자바처럼)null 검사를 하지 않고 바로 접근한다. 따라서 변수에 null이 할당되어 있다면 런타임에 NullPointerException을 발생시킨다.


세이프 콜과 엘비스(?:) 연산자

null 허용 변수(?타입)을 더 안전하게 사용하기 위해서 세이프 콜(?.)과 엘비스 연산자(?:)를 함께 사용하면 좋다.

엘비스 연산자(?:)는 변수가 null인지 아닌지 검사하여 null이 아니라면 왼쪽 식을 사용하고 null이라면 오른쪽 식을 사용한다.

위의 str1?.length ?: -1 연산은 if (str1 != null) str1.length else -1과 동일하다.


기본형(Primitive Data Type)과 참조형(Reference Data Type) 값 비교

  • == : 이중 등호. 단순히 변수의 값을 비교할 때 사용한다.
  • ===: 삼중 등호 : 참조형 변수의 주소값을 비교할 때 사용한다.

단, Int, Double, Long 등과 같이 컴파일러에 의해 기본형으로 변환되는 변수들은 ==를 사용하던, ===를 사용하던 값 비교를 수행한다.


한 가지 조심해야 할 점은 null 허용 타입의 경우는 이중 등호와 삼중 등호의 결과가 다르다.

앞서 이야기 했듯이 Int는 컴파일러에 의해 기본형으로 변환되어 stack에 값 자체를 저장하지만, Int?의 경우에는 참조형으로 저장되기 때문에 Heap에 할당되고, stack에는 Heap 참조 주소가 저장된다. 따라서 ===로 비교하게 되면 값 compare 주소값이 된다.


스마트 캐스트

스마트 캐스트는 컴파일러가 자동으로 형 변환을 하는 것을 말한다.

스마트 캐스트가 적용되는 대표적인 자료형은 Number가 있는데, Number로 정의된 변수에는 저장되는 값에 따라 실수형이나 정수형으로 변환된다.


자료형 검사 is와 최상위 클래스 Any

어떤 변수의 자료형을 검사하기 위해서 is 키워드를 사용할 수 있다.


Any는 코틀린의 최상위 기본 클래스로써 어떤 자료형이든 될 수 있는 특수한 자료형이다.

Any로 선언된 변수는 is로 타입 검사를 하면 검사한 자료형으로 스마트캐스트가 된다.


as에 의한 스마트 캐스트

as 키워드로 스마트 캐스트 할 수 있다. as는 형 변환이 가능하지 않다면 예외를 발생시킨다.

val x: String = y as String


Any

Any는 자료형이 특별히 정해지지 않은 경우에 사용한다. 자바의 Object와 유사하지만 서로 다르다.

형변환에 있어 자바보다 훨씬 유연하고 자유롭다!!

Any는 어떤 타입으로든 캐스팅 될 수 있기 때문에 언제든 스마트 캐스트(자동 변환)이 가능하다. 이를 묵시적 변환이라고도 한다.

var a: Any = 1  // a는 1로 초기화 될 때 Int 타입으로 자동 형변환이 이뤄진다.
a = 20L // Int형이었던 a는 20L에 의해 Long형이 된다.