ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 코틀린을 코틀린답게 사용하는 법 - (2)
    안드로이드 2020. 3. 3. 00:41

    코로나19 때문에 집에만 박혀있어서, 생각보다 빨리 돌아온 코틀린 공부 시간! 신난다아 😂

     

    오늘 포스팅에선 비교적 가벼운 내용을 다뤄 보겠다 (책 순서가 그렇습니다요 ㅎ,ㅎ)

    앗, 그리고 자바와 코틀린을 비교하는 것이 기반인 만큼 자바와 쓰임이 같은 것들은 따로 정리하지 않으려한다. (귀찮)

     

     

    자료/자료형의 확인 및 변환

    1. 자료의 동일성 확인

    코틀린의 == 연산자는 비교하는 값의 null 여부를 함께 확인한다.

    • 객체의 값을 비교: == 연산자
    • 객체 자체가 동일한지 비교: === 연산자

    2. 자료형 확인: is 연산자

     

    3. 자료형 변환: as 연산자

     

     

     

     

    흐름 제어

    when 문

    코틀린의 when은 자바의 switch 문을 대체한다.

    when 문도 if-else 문과 마찬가지로 값을 반환할 수 있으며, 다음과 같이 반환 값을 바로 사용할 수 있다.

    val bags = 1
    val bagString = when(bags) {
        0 -> "가방이 없습니다."
        1,2 -> "$bags 개의 가방을 가지고 있습니다."
        else -> "오류 입니다."
    }
    
    // 1 개의 가방을 가지고 있습니다. 출력
    Log.e("Bags", bagString)
        

     

    또한, 코틀린에서는 각 경우의 조건을 표현식(expression)으로 작성할 수 있다.

    val e : Exception = ... // 값 e에 여러 종류의 예외가 대입
    when(e){
        is IOException -> Log.e("Message", "NetworkError")
        is IllegalStateException -> Log.e("Message", "Invalid State")
        ...
    }
    
    val str : String = ... // 값 str에 임의의 문자열이 대입
    when(str){
        str.startWith('a') -> Log.d("Message", "A for Android")
        str.startWith('k') -> Log.d("Message", "K for Kotlin")
    }

     

     

    for 문

    자바는 인덱스 기반 for 문과 for-each 문을 지원하는 반면,

    코틀린은 for-each 형태만 지원하며 반복자를 통해 접근하는 인자의 타입을 생략할 수 있다.

    val names = listOf("시연", "예원", "예진", "찬영")
    
    for(name in names){
    	Log.e("안드 이비", name)
    }

     

    💡for 문에서 인덱스 접근하기

    for 문 내에서 현재 항목의 인덱스가 필요한 경우, Collection.indicies 프로퍼티를 사용하면 컬렉션의 인덱스를 순환하며 인덱스 인자로 배열 내 항목에 접근할 수 있다.

    val names = listOf("시연", "예원", "예진", "찬영")
    
    for(i in names.indicies){
    	Log.e("안드 이비", names[i])
    }

     

     

     

    제네릭

    제네릭(Generics) 또는 제네릭 타입(Generic type)이란 인자로 사용하는 타입에 따라 구체화되는 클래스나 인터페이스를 말한다.

     

     

    제네릭의 장점

    1. 프로그램 성능 저하를 유발하는 캐스팅(강제 데이터 타입 변환)을 제거한다.
    2. 코드 절약 및 코드 재사용성을 증진시켜 유지보수를 편하게 한다.
    3. 컴파일 시 타입 오류를 체크하여, 사전에 엄격한 데이터 타입 체크를 가능케한다.

     

     

    생김새

    클래스나 인터페이스 이름 뒤에 '<>' 부호가 붙고, 이 부호 사이에 타입 파라미터가 위치한다.

    class ClassName<T> { ... }
    interface InterfaceName<T> { ... }

     

     

    제네릭 클래스/인터페이스 정의

    class Car {
    	...
    }
    
    // 항목을 담거나 뺄 수 있는 제네릭 인터페이스 정의
    interface Container<T> {
    	
        fun put(item: T)
        fun take() : T
     }
     
     // Car를 담거나 뺄 수 있는 클래스 정의
     class Garage : Container<Car> { 
     	
        override fun put(itme: Car) { ... }
        override fun take() : Car { ... }
    }

    단, 제네릭 클래스/인터페이스가 인자로 받을 수 있는 타입을 한정하는 방법은 다음과 같다.

    아래와 같이 코드를 작성하면 Container 인터페이스가 받을 수 있는 타입은 Car 클래스와 그 하위 클래스이다.

    interface Container<T: Car> { ... }

     

     

    제네릭을 인자로 받는 함수

    // 자동차 클래스
    open class Car { ... } 
    
    // 세단 클래스
    class Sedan : Car() { ... }
    
    // 트럭 클래스
    class Truck : Car() { ... }
    
    // src로 받은 목록을 dest에 추가
    fun <T> append(dest: MutableList<in T>, src: List<out T> {
    	dest.addAll(src)
    }
    
    
    // 사용 예
    val sedans: List<Sedan> = ...
    val trucks: List<Truck> = ...
    val cars: MutableList<Car> = ...
    append(cars, sedans)
    append(cars, trucks)

     

    💡타입 파라미터에 지정되는 구체적인 타입을 제한하는 방법은 다음과 같다.

    • ? : 제한 없음
    • in T : T 상위 클래스 제한
    • out T : T 하위 클래스 제한

     

     

     

    Null 안전성

    안드로이드 개발을 하다가 가장 많이 만나는 exception은 null pointer exception이다... 나만 그런 것이 아닐 것이다...

    코틀린은 이 문제를 해결하기 위해 모든 타입에 명시적으로 null 허용 여부를 함께 표기한다.

     

    @Nullable, @NonNull 어노테이션을 사용해 null 허용 여부를 표시하는 자바와 다르게,

    코틀린은 별도 표기가 없는 경우 null 값을 허용하지 않는다.

    null 값을 가질 수 있도록 하려면 다음과 같이 명시적으로 타입 뒤에 ? 를 붙여주어야 한다.

    val nullableString : String? = null
    val nonNullString : String = "Foo"
    
    // 오류가 나는 상황
    val name : String // 오류: 값이 초기화되지 않음
    val address : String = null // 오류: null을 허용하지 않는 값에 null 대입 불가

     

    엘비스(?:) 연산자 - null 값을 대신하는 방법

    null 값을 허용하지 않는 값이나 변수에 null 값이 들어갈 수도 있는 경우, 이에 대한 처리를 별도로 해야 한다.

    // foork null이 아닐 경우에는 foo를, null이라면 bar를 반환
    foo ?: bar

     

    안전한 호출(.?) 연산자 - null 값 확인과 처리를 한 번에

    코틀린에서는 안전한 호출(safe call) 연산자를 사용하여 null 값 확인과 값 접근/함수 호출을 한 번에 할 수 있다.

    • null 값일 경우: 연산자 뒤의 문장을 실행하지 않고 null 반환
    • null 이 아닐 경우: 연산자 뒤의 문장 실행
    // bar가 null이 아닐 경우에만 해당 값을 대입, 그렇지 않은 경우 null을 foo에 대입
    val foo = bar?.baz
    
    // foo가 null이 아닐 경우에만 bar() 호출
    foo?.bar()

     

    null 값이 아님을 보증하기(!!)

    null 값을 포함할 수 있는 타입에 null 값이 아닌 값만 포함되는 경우가 생길 수 있다.

    이런 경우, non-null을 보증해 줄 수 있는데, 보증하려는 항목 뒤에 !!을 붙이면 된다.

    // foo는 null일 수 있음
    val foo : Foo? = ...
    
    // foo는 null이 아님을 보증
    val nonNullFoo : Foo = foo!!
    
    // foo가 null이 아님을 보장하면서 bar() 호출
    foo!!.bar()
    
    // foo가 null이 아님을 보장하면서 baz 프로퍼티 접근
    val myBaz = foo!!.baz

    null값이 아님을 보증하였으나 실제 객체에 null이 들어가 있을 경우, null pointer exception이 발생하므로 유의해서 사용하자

     

     

    lateinit 키워드 - 나중에 초기화되는 변수를 위해

    저번 포스팅에서 살짝 다뤘던 것처럼 프로퍼티는 객체를 생성할 때 값을 할당하는 경우도 많지만, 

    의존성 주입(dependency injection)을 사용하거나 설계상 이유로 객체를 생성한 후 나중에 따로 초기화를 수행하는 경우도 있다.

    이때, lateinit 키워드를 사용하면 초기화 없이 변수만 선언할 수 있다.

    class MyActivity : Activity() {
    
        lateinit var api : Api
        
        ...
        
    }

    얘도 마찬가지로 초기화를 하지 않은 상태로 사용하려 하면 null pointer exception이 발생하므로 초기화를 빠뜨리지 않도록 유의하자!

     

     

     

     

    이렇게 자바와 코틀린의 비교를 통해 코틀린을 코틀린답게 쓰는 법을 알아보았다!!

    다음 포스팅에서는 자바와는 다른 코틀린의 특징에 대해서 정리를 해보겠다!!

    꼭 올 거니까 null이 아님을 보증하겠다!! ⭐️

     

Designed by Tistory.