Programming Language/Kotlin & Java

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

개발하는 곰돌이 2022. 11. 19. 11:43

목차

    개요

    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 클래스에 대해 조금 더 자세히 정리하고자 한다.

     

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

    728x90