고딩왕 코범석

변수, 타입, 연산자 본문

Language & Framework/Kotlin

변수, 타입, 연산자

고딩왕 코범석 2022. 7. 30. 17:20
반응형

Index


  1. 변수 다루기
    1. 변수 선언 키워드 - var, val 차이점
    2. Kotlin에서의 Primitive Type
    3. Kotlin에서의 nullable 변수
    4. Kotlin에서의 객체 인스턴스화
  2. null 다루기
    1. Kotlin에서의 null 체크
    2. Safe Call과 Elvis 연산자
    3. 널 아님 단언!!
    4. 플랫폼 타입
  3. Type 다루기
    1. 기본 타입
    2. 타입 캐스팅
    3. Kotlin의 3가지 특이한 타입
    4. String Interpolation, String indexing
  4. 연산자 다루기
    1. 단항 연산자 / 산술 연산자
    2. 비교 연산자와 동등성, 동일성
    3. 논리 연산자 / 코틀린에 있는 특이한 연산자
    4. 연산자 오버로딩

변수 다루기

변수 선언 키워드 - var, val 차이점

Java에서의 int와 final int의 차이 = 가변이냐? 불변이냐?


        var n = 10  // int n = 10
        n = 20
        val x = 20  // final int n = 10
        x = 20  // Error

코틀린은 타입을 자동으로 컴파일러가 추론해준다.

초기값을 지정하지 않는 경우?


        var n
        val x  // 이렇게만 작성할 경우 컴파일러가 타입을 추론할 수 없어 컴파일 에러 발생
        
        x = 10
        x = 20  // val이라 컴파일 에러

val 컬렉션에는 element를 추가할 수 있다.

자바에서도 컬렉션 자체를 바꿀 수 없지만 컬렉션 내 조작은 가능하다. 코틀린도 마찬가지

Kotlin에서의 Primitive Type


        var n1 = 20L
        var n3 = 1_000L
    

자바에서는 Primitive Type과 Reference Type이 존재한다. 코틀린에서는 이런 구분이 따로 없다. 그렇다면 성능 문제는 없는가?

  • 코틀린에서는 기본적으로 Reference Type을 사용하지만 디컴파일했을 때의 자바 코드는 primitive type으로 작성되어있다.
  • 즉, 개발자가 Boxing / Unboxing을 신경쓰지 않도록 해준다.

Kotlin에서의 nullable 변수

코틀린은 null이 들어갈 수 있는 경우를 간주한다. 타입 옆에 ‘?’를 붙여준다.


        var n = 10L
        n = null // 컴파일에러 발생
    
        var n2? = 10L
        n2 = null
    

Kotlin에서의 객체 인스턴스화


        var person = Person("고범석")
    

자바는 new 키워드를 사용하지만, 코틀린은 new를 붙이지 않는다.

위로

null 다루기

코틀린은 null이 들어가는 변수를 다르게 취급한다. 그렇다면 코틀린은 어떻게 이를 다룰까?

Kotlin에서의 null 체크


        public boolean startsWithA(String str) {
            return str.startsWith("A");
        }
    

위 자바 코드는 안전한 코드가 아니다. str에 null이 올 경우 NPE가 발생한다. 이를 안전하게 고쳐보면


    public boolean startsWithA1(String str) {
        if (str == null) {
            throw new IllegalArgumentException();
        }
        return str.startsWith("A");
    }
    
    public Boolean startsWithA2(String str) {
        if (str == null) {
            return null;
        }
        return str.startsWith("A");
    }
    
    public boolean startsWithA3(String str) {
        if (str == null) {
            return false;
        }
        return str.startsWith("A");
    }
    

위 방법들을 코틀린으로 작성해보면


    fun startsWithA1(str: String?): Boolean {
        if (str == null) {
            throw IllegalArgumentException()
        }
        return str.startsWith("A")
    }
    
    fun startsWithA2(str: String?): Boolean? {
        if (str == null) {
            return null
        }
        return str.startsWith("A")
    }
    
    fun startsWithA3(str: String?): Boolean {
        if (str == null) {
            return false
        }
      // 앞에서 null 체크를 하지 않으면 코틀린에서는 컴파일 에러가 발생.
        return str.startsWith("A")
    }
    

즉, 코틀린에서는 null이 가능한 타입을 완전히 다르게 취급한다.

Safe Call과 Elvis 연산자

Safe Call


        val str: String? = "ABC"
        str.length // 불가능, null인 경우도 있기 떄문
        str?.length // 가능, null이 아닌 경우만 호출, null일 경우는 애초에 null로 취급
    

Elvis 연산자


        val str: String? = "ABC"
        str?.length ?: 0 // ?: 뒤 쪽의 '0'은 앞의 호출 결과가 null일 경우 리턴된다.
    

Safe Call, Elvis를 위 함수에서 활용해본다면?


        fun startsWithA1(str: String?): Boolean {
            return str?.startsWith("A") ?: throw IllegalArgumentException()
        }
    
        fun startsWithA2(str: String?): Boolean? {
            return str?.startsWith("A")
        }
    
        fun startsWithA3(str: String?): Boolean {
            return str?.startsWith("A") ?: false
        }
    

Elvis 연산자를 이용한 Early Return


        public long calculate(Long number) {
            if (number == null) {
                return 0;
            }
        
            // 계산 로직
        }
    

        fun calculate(number: Long?): Long {
            // number가 null일 경우 로직을 수행하지 않고 바로 리턴
            number ?: return 0
    
            // 계산 로직
        }
    

널 아님 단언!!

nullable type이지만 아무리 생각해도 null이 될 수 없을 경우에 사용한다.


        fun startsWith(str: String?): Boolean {
            return str!!.startWith("A")
        }
    

만약 파라미터에 null이 들어올 경우 NPE가 발생한다.

플랫폼 타입

한 프로젝트에서 코틀린과 자바를 같이 사용할 때가 있다.


        public class Person {
            private final String name;
        
            public Person(String name) {
                this.name = name;
            }
        
            @Nullable  // 코틀린에서 인식 가능
            public String getName() {
                return name;
            }
        }
    

        fun main() {
            val person = Person("고범석") // 자바 코드의 Person 클래스 
            startWithA(person.name)  // 컴파일 에러 발생
        }
    
        // 이 함수의 매개변수는 null이 올 수 없다. Person 객체는 nullable
        fun startsWithA(str: String): Boolean {
            return str.startsWith("A")
        }
    

코틀린에서는 다음과 같은 패키지에서 null을 다루는 어노테이션을 활용할 수 있다.

  • javax.annotation
  • android.support.annotation
  • org.jetbrains.annotation

만약 Person 자바 클래스에서 @Nullable이 없다면?

  • 컴파일 에러는 발생하지 않는다.
  • 하지만 런타임에서 예외가 발생한다.
  • null이 들어갈 수 있는 가능성을 꼼꼼히 체크해야한다.
  • 코틀린에서 null인지 파악하지 못하는 타입을 플랫폼 타입이라 한다.

위로

Type 다루기

기본 타입

코틀린은 선언된 기본값을 보고 타입을 추론한다.


        val n1 = 3 // Int
        val n2 = 3L // Long
        val n3 = 3.0f // Float
        val n4 = 3.0 // Double

자바는 기본 타입간의 변환은 암시적으로 이뤄질 수 있으나, 코틀린의 기본 타입간의 변환은 명시적으로 이루어져야 한다.


        int n1 = 4;
        long n2 = n1; // 암시적으로 큰 타입으로 형변환

        val n1 = 4
        val n2: Long = n1 // Type Mismatch, 컴파일 에러
    
        val n1 = 4
        val n2: Long = n1.toLong() // toXXX() 다양한 타입 변환 메서드가 있다.
    

변수가 nullable이라면 적절한 처리를 해줘야한다.


        val n1: Int? = 3
        val n2: Long = n1.toLong() // 컴파일 에러
    
        val n2: Long = n1?.toLong() ?: 0L
    

타입 캐스팅

자바에서는 instanceof로 타입을 체크하고 (타입)을 통해 캐스팅한다.


        fun printAgeIfPerson(obj: Any) {
            if (obj is Person) {  // is == instanceof
                val person = obj as Person  // (Person) == as Person
                println(person.age)
            }
        }
    

스마트 캐스트

만약 is를 통해 특정 타입이 맞다고 판단되면 as를 쓰지 않아도 된다. 이를 스마트 캐스트라고 한다.


        fun printAgeIfPerson(obj: Any) {
            if (obj is Person) {
                println(obj.age)
            }
        }
    

instanceof의 반대는? 즉, 자바에서 if (!(obj instanceof Person)) {…} 를 코틀린에서는 어떻게 표현할까?


        fun printAgeIfPerson1(obj: Any) {
            if (!(obj is Person)) {
                println(obj.age)
            }
        }
    
        // 이 방법이 좀 더 간결
        fun printAgeIfPerson2(obj: Any) {
            if (obj !is Person) {
                println(obj.age)
            }
        }
    

그럼 obj에 null이 들어온다면?


        fun printAgeIfPerson2(obj: Any?) {
            val person = obj as? Person
            println(person?.age)
        }
    

정리

https://user-images.githubusercontent.com/37062337/181878492-56ebb2dc-c43c-4859-a44d-8875c54603c4.png

https://user-images.githubusercontent.com/37062337/181878501-ea19751f-e811-412e-8437-189255a09ea5.png

https://user-images.githubusercontent.com/37062337/181878516-5923b311-a6b1-456e-9975-609a53568830.png

Kotlin의 3가지 특이한 타입

코틀린에서는 Any, Unit, Nothing 이라는 세 가지 타입이 존재한다.

Any

  • 자바에서의 Object 와 같은 역할을 한다.

  • 자바는 Primitive Type은 Object로 치환할 수 없지만 코틀린에서는 애초에 Int, Long으로 취급하기 때문에 코틀린의 Primitive Type의 최상위도 Any다.

  • Any 자체로는 null을 표현할 수 없어 Any?로 표현한다.

  • Anyequals / hashCode / toString 존재

    https://user-images.githubusercontent.com/37062337/181878670-abce49a6-f492-47d3-b963-bc50c95a0f15.png

Unit

  • 자바의 void와 동일한 역할
  • void와 다르게 Unit은 그 자체로 타입 인자로 사용 가능
    • 자바에서는 Void 클래스를 따로 썼다. 코틀린에서는 Unit으로 사용 가능
  • 함수형 프로그래밍에서 Unit은 단 하나의 인스턴스만 갖는 타입을 의미
    • 코틀린의 Unit은 실제 존재하는 타입이라는 것을 표현

Nothing

  • 함수가 정상적으로 끝나지 않았다는 사실을 표현하는 역할

  • 무조건 예외를 반환하는 함수, 무한 루프 함수 등등..

    
            fun fail(message: String): Nothing {
                throw IllegalArgumentException()
            }
        

String Interpolation, String indexing

코틀린에서는 문자열을 출력할 때 “” 안에 ${변수}를 사용하면 변수에 해당하는 값이 문자열로 들어간다. 또한 중괄호는 생략이 가능하다.


        val person = Person("고범석", 28)
        val log = "내 이름은 ${person.name}이고, 나이는 ${person.age} 이다."
        val log = "이름 : $person.name"
    

코틀린에서 여러 줄의 문자열을 “”” “”” 안에 쉽게 작성할 수 있다.


        fun main() {
            val str = """
                ABC
                DEF
            """.trimIndent()
            println(str)
        }
    

자바에서 문자열 안에 특정 문자를 가져올 때는 charAt()으로 가져와야 한다. 코틀린에서는 문자열[index]로 가져온다.


        val str = "ABCDE"
        println(str[1])
    

위로

연산자 다루기

단항 연산자 / 산술 연산자

자바와 완전 동일하다.

비교 연산자와 동등성, 동일성

비교 연산자

비교 연산자도 자바와 동일하다.

차이점이 있다면 객체를 비교할 때 비교 연산자를 사용하면 자동으로 compareTo()를 호출한다.


        // 자바에서의 대소 비교
        Money m1 = new Money(200L);
        Money m2 = new Money(100L);
        if (m1.compareTo(m2) > 0) {
            System.out.println("m1이 m2 보다 크다.")
        }
        
        // 코틀린에서의 대소 비교
        val m1 = Money(100L);
        val m2 = Money(200L);
        // 객체 비교 시 compareTo() 호출, 더 직관적
        if (m1 > m2) {
            println("m1이 m2보다 크다.");
        }
    

동등성, 동일성

  • 동등성 : 두 객체의 값이 같은가?
  • 동일성 : 동일한 객체의 주소인가?

자바에서는 동일성을 판단할 때는 ‘==’, 동등성을 판단할 때는 equals()로 판단한다.

코틀린에서는 동일성은 ‘===’, 동등성은 ‘==’로 판단한다. 또한 ‘==’를 사용하면 간접적으로 equals()를 호출한다.

논리 연산자 / 코틀린에 있는 특이한 연산자

논리 연산자는 자바와 완전히 동일하다. 자바처럼 lazy 연산을 수행한다.


        // Lazy 연산?
        fun main() {
            // fun2()를 호출하지 않는다. 이게 lazy 연산
            // 어차피 fun1()이 true를 반환하면 뒤에 함수는 실행할 이유가 없으니 호출하지 않음
            if (fun1() || fun2()) {
                println("lazy 연산 수행")
            }
        }
        
        fun fun1() {
            println("fun1()")
            return true
        }
        
        fun fun2() {
            println("fun2()")
            return true
        }
    

in / !in

  • 컬렉션이나 범위에 포함되어 있다, 포함되어 있지 않음을 판단할 때 쓰는 연산자

a..b

  • a 부터 b까지 range 연산자를 의미

a[i]

  • a 컬렉션에서 i 번째 원소를 가져오는 연산자

a[i] = b

  • a 컬렉션에서 i 번째 원소 자리에 b를 넣는다.

연산자 오버로딩

코틀린에서는 연산자를 직접 오버로딩할 수 있다. Money 클래스에서 operator 키워드와 지정된 예약어를 통해 오버로딩 할 수 있고, 메서드가 아닌 연산자를 통해 메서드를 수행하여 가독성을 높여준다.


        fun main() {
            val m1 = Money(1_000L)
            val m2 = Money(2_000L)
            println("(m1 + m2) == (m1.plus(m2)) => ${(m1 + m2) == (m1.plus(m2))}")
        }
        
        data class Money(
            val amount: Long,
        ) {
            operator fun plus(other: Money) = Money(this.amount + other.amount)
        }
    

위로

반응형

'Language & Framework > Kotlin' 카테고리의 다른 글

특성  (0) 2022.08.05
FP  (0) 2022.08.03
OOP  (0) 2022.08.02
코드 제어  (0) 2022.07.30