본문 바로가기
  • 개발하는 곰돌이
Programming Language/Kotlin & Java

Kotlin 기본 문법 2 : 자료형과 null-safe

by 개발하는 곰돌이 2022. 11. 19.

목차

    개요

    Java에서는 자료형이 원시형(Primitive Type)과 참조형(Reference Type)으로 나뉜다. 원시형이란 int, long, double 등과 같이 실제 자료값만을 저장하는 타입이고, 참조형은 객체를 생성하고 메모리 영역에 값을 저장한 후 메모리 주소를 통해 값을 참조하는 타입으로, 원시형을 제외한 나머지 타입(배열, 열거형, 각종 클래스의 객체 등)이 포함된다. 원시 자료형의 Wrapper Class인 Integer, Long, Double 등 또한 모두 참조형에 속한다.

    Kotlin의 자료형에는 원시형이 존재하지 않고 모든 자료형이 참조형이다. 다르게 말하면, 모든 자료형은 클래스이고 모든 변수는 객체가 된다는 뜻이다. 이 때문에 모든 자료형의 첫 글자가 대문자로 작성된다. 또한, Java에서는 원시 자료형에 별도의 메소드를 사용하거나 Collection 객체에 원시 자료형을 추가하려면 별도로 Wrapper Class를 사용해야 했던 것과는 달리 자료형을 그대로 사용할 수 있다.

    이번 포스팅에서는 정수, 실수, 논리, 문자, 문자열 자료형에 대해 정리하고자 한다.


    정수 자료형

    Kotlin도 Java와 마찬가지로 정수 자료형에는 Byte, Short, Int, Long이 존재한다. 각 자료형의 크기와 값의 범위는 다음 표와 같다. 이 중 Long 타입의 경우에는 값을 변경할 때 숫자 뒤에 반드시 L을 붙여줘야 한다.

    타입 크기 최소값 최대값
    Byte 1Bytes(8bits) -128(-27) 127(27-1)
    Short 2Bytes(16bits) -32,768(-215) 32,767(215-1)
    Int 4Bytes(32bits) -2,147,483,648(-231) 2,147,483,647(231-1)
    Long 8Bytes(64bits) -9,223,372,036,854,775,808(-263) 9,223,372,036,854,775,807(263-1)
    var a: Long = 123	// 컴파일 에러
    var b: Long = 123L	// OK

    Kotlin에서는 정수 자료형에 한해서 Java에서 제공하지 않는 Unsigned 자료형도 제공한다. 이 경우 각 자료형의 앞에 U가 붙은 UByte, UShort, UInt, ULong로 사용할 수 있다. Unsigned 자료형의 범위는 아래와 같다. Unsigned로 선언된 변수를 변경하고 싶다면 숫자 뒤에 반드시 u를 붙여야 한다.

    타입 크기 최소값 최대값
    UByte 1Bytes(8bits) 0 255(28-1)
    UShort 2Bytes(16bits) 0 65,535(216-1)
    UInt 4Bytes(32bits) 0 4,294,967,295(232-1)
    ULong 8Bytes(64bits) 0 18,446,744,073,709,551,615(264-1)
    var a: UInt = 123	// 컴파일 에러
    var b: UInt = 123u	// OK

    Kotlin은 Java와 마찬가지로 정수값을 대입할 때 리터럴의 맨 앞에 0x를 붙여 16진수를, 0b를 붙여 2진수를 대입할 수 있으며, 언더스코어(_)를 이용하여 자리값을 구분할 수도 있다.

    val a = 123_456		// 123456
    val b = 1_23_4_5_6	// 언더스코어는 숫자 사이 아무 위치에나 추가할 수 있음
    val c = 0xff	// 255
    val d = 0b1111	// 15

    실수 자료형

    Kotlin도 Java처럼 FloatDouble을 실수 자료형으로 사용한다. 이 두 유형은 아래의 표와 같이 서로 다른 정밀도와 크기, 소수점 이하 자릿수를 가진다. Java와 마찬가지로 Float 타입은 숫자 맨 끝에 f를 붙여야 한다. 소수점 이하의 수가 지수부의 범위를 초과하게 되면 초과한 부분은 버려진다.

    타입 크기 가수부 지수부 소수점 이하 자릿수
    Float 4Bytes(32bits) 24 8 6~7
    Double 8Bytes(64bits) 53 11 15~16
    val a = 1.23	// Double
    val b = 4.56f	// Float
    val c = 0.987654321987654321	// 0.9876543219876543
    val d = 0.987654321987654321f	// 0.9876543

    논리 자료형

    논리 자료형은 Java와 마찬가지로 Boolean만 존재하며 true와 false만을 값으로 가질 수 있다. 주로 조건을 검사할 때 사용한다.

    val a = 5 % 2
    val isOdd = a == 1	// true
    val isEven = a == 0	// false

    문자 자료형

    문자 자료형은 Java와 마찬가지로 Char를 사용한다. Char는 2바이트의 크기를 가지며, 'a'와 같이 리터럴을 작은 따옴표로 감싸서 한 글자를 표현한다. 문자.code를 사용하면 해당 문자의 유니코드 값을 10진수로 얻을 수 있다. 만약 작은 따옴표를 문자형 변수에 집어넣고 싶다면 작은따옴표 앞에 역슬래시(\)를 붙여주면 된다. 또한, 역슬래시 뒤에 u와 함께 4자리 16진수 형태의 유니코드 값을 집어넣는 것으로 그 값에 해당하는 문자를 표현할 수도 있다.

    val a = '가'
    val b = '\''
    val c = '\uac00'
    
    println(a)	// 가
    println(a.code)	// 44032
    println(b)	// '
    println(c)	// 가

    Java에서는 char 타입에 정수를 저장하면 자동으로 그 값에 해당하는 문자가 되었으나 Kotlin에서는 이러한 방식이 불가능하다. Kotlin의 Char 타입에 정수 값에 해당하는 문자를 저장하고 싶다면 정수.toChar() 메소드를 사용하여 문자형으로 변환하거나 상기했듯이 '\u(4자리의 16진수)'의 형태로 작성해야 한다.

    val a: Char = 97	// 컴파일 에러
    val b = 97.toChar()	// a
    val c = '\u0061'	// a

    문자열

    문자열은 Java와 마찬가지로 String을 사용하며, 큰 따옴표(")로 감싸서 여러 문자를 표현한다. 또한, 큰 따옴표 3개로 리터럴을 감싸면 HTML의 <pre> 태그처럼 개행과 공백을 모두 포함하여 문자열을 저장할 수 있다. Java에서는 \n+를 사용해서 줄을 바꿔가며 입력해야 했던 것에 비하면 상당히 편리하다고 볼 수 있다. 이외에도 Kotlin의 String 클래스는 Java와 다른점이 좀 있기 때문에 이 부분에 대해서는 다음 포스트에서 더 자세히 정리하고자 한다.

    // Java
    String str1 = "Hello World";
    String str2 = "Hello World!\n"
               + "This is Test String.\n"
               + "Colabear754";
    // Kotlin
    val str1 = "Hello World"
    val str2 = """
    Hello World!
    This is Test String.
    Colabear754.
    """
    
    println(str1)	// Hello World
    println(str2)
    /*
    
    Hello World!
    This is Test String.
    Colabear754.
    
    */

    """를 사용하면 코드의 가독성을 높이기 위한 들여쓰기 공백까지 함께 저장하게 되는데 이 경우 저장할 문자열 각 줄의 맨 앞에 파이프 문자(|)를 붙이고 trimMargin()를 사용하면 | 앞의 공백을 제거해 주므로 들여쓰기로 코드의 가독성을 높이면서 문자열에는 들여쓰기 공백을 제거하여 저장할 수 있다. 만약 파이프 문자 대신 다른 문자로 줄의 시작 부분을 표시하고 싶다면 해당 문자를 trimMargin() 메소드의 파라미터로 보내면 된다.

    val str1 = """
        Hello World!
        This is Test String.
        Colabear754.
        """
    
    println(str1)
    /*
    
        Hello World!
        This is Test String.
        Colabear754.
        
    */
    
    val str2 = """|
        |Hello World!
        |This is Test String.
        |Colabear754.
        """.trimMargin()
    
    println(str2)
    /*
    
    Hello World!
    This is Test String.
    Colabear754.
    */
    
    val str3 = """
        @Hello World!
        @This is Test String.
        @Colabear754.
        """.trimMargin("@")
        
    println(str3)
    /*
    Hello World!
    This is Test String.
    Colabear754.
    */

    Kotlin의 null-safe 특성

    Kotlin의 모든 자료형은 기본적으로 null 값을 허용하지 않아 null이 발생하게 될 경우 컴파일 시점에 미리 알려줘서 런타임에 예상치 못한 NullPointerException이 발생하는 것을 방지할 수 있다. 만약 null을 사용하고 싶다면, 클래스나 자료형의 이름 뒤에 물음표(?)를 붙여서 nullable한 객체라는 것을 명시해줘야 한다.

    val a: Int = null	// 컴파일 에러
    val b: Int? = null	// OK

    Java의 원시형에 해당하는 자료형의 경우엔 이 nullable 부분이 조금 다르게 동작하는데, non-null 타입일 경우에는 원시형으로 변환되지만 nullable 타입일 경우에는 Wrapper Class로 변환된다. 위의 코드를 Java로 변환하면 아래와 같아진다.

    int a = null;		// 컴파일 에러
    Integer b = null;	// OK

    Kotlin에서는 nullable한 객체와 non-null인 객체는 서로 다른 자료형으로 취급한다. 그렇기 때문에 non-null인 객체에 nullable한 객체를 대입하려고 시도하면 컴파일 에러가 발생한다.

    var a = 123
    var b: Int? = 456
    a = b	// 컴파일 에러

    하지만 방법이 전혀 없는 것은 아니다. non-null인 객체에 대입하고자 하는 nullable한 객체 뒤에 느낌표를 2개 붙여주면 대입하고자 하는 값이 null이 아니라는 것을 보장해준다. 다만, 이 경우 실제 값이 null인 객체에 !!를 붙여서 저장하려고 하면 NullPointerException이 발생하기 때문에 주의를 기울여서 사용해야 한다.

    var a = 123
    var b: Int? = 456
    a = b!!		// OK
    
    var c = 789
    var d: Int? = null
    c = d!!		// NullPointerException

    다른 방법으로는 non-null인 객체에 대입하고자 하는 nullable한 객체 뒤에 물음표와 콜론(:)을 붙이는 방법도 있다. 이렇게 하면 대입하고자 하는 객체가 null일 경우에 콜론 뒤에 있는 값을 대신 대입한다.

    var a: Int
    val b = null
    a = b ?: 123
    println(a)	// 123

    nullable한 객체에는 제한 없이 non-null인 객체를 대입할 수 있다. non-null인 객체를 주입받은 nullable한 객체는 null을 다시 대입하지 않는 한, non-null인 객체처럼 사용할 수 있다.

    var a = 10
    var b: Int? = 20
    var c = 30
    b = c
    a = b	// nullable인 b에 non-null인 c를 저장하여 b의 non-null이 보장됨
    b = null
    c = b	// b에 null을 저장하면서 non-null 보장성이 사라졌기 때문에 컴파일 에러 발생

    마무리

    Kotlin의 기본 자료형과 null-safe 특성에 대해 정리해보았다.

     

    다음 포스트에서는 Kotlin의 String 클래스에 대해 조금 더 자세히 정리하고자 한다.

     

    댓글 피드백은 언제나 환영합니다.

    댓글