ABOUT ME

-

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

    이번 포스팅에서는 자바와는 다른 코틀린의 특징에 대해서 다뤄 보도록 하겠다.

    (예진, 예원이 아주 빠른 속도로 공부를 하고 있다,, 빨리 쫓아가야겠다)

     

     

    클래스

    코틀린에서만 제공하는 특수한 클래스와 코틀린 클래스에서만 제공하는 기능에 대해 정리하겠다.

     

    데이터 클래스

    프로그램을 작성하다 보면 여러 유형의 자료를 다루게 된다.

    예를 들어 Java에서 Person 이라는 유형의 자료를 클래스로 관리한다면,

    1. Person이라는 class를 만들고

    2. getter와 setter 메소드를 만들고

    3. 필드가 추가됨에 따라 equals(), hashCode()등의 메소드들을 일일히 수정해야한다.

     

    하지만, Kotlin에서는 데이터 클래스(data calss)라는 특별한 클래스를 제공한다.

    데이터 클래스는 자료를 구성하는 프로퍼티만 선언하면 컴파일러가 equals(), hashCode() 등 필요한 함수를 자동으로 생성해준다.

    data class Person (
        val name: String,
        val address: String
    )

     

     

    프로퍼티의 사용자 지정 Getter/Setter

    프로퍼티에는 내부의 저장된 필드 값을 가져오거나 설정할 수 있도록 getter와 setter를 내부적으로 구현하고 있으며, 단순히 필드의 값을 반환하거나 설정한다.

    사용자 지정 Getter/Setter를 사용하면 프로퍼티에서 Getter 및 Setter의 구현을 원하는 대로 변경할 수 있다.

    특정 객체의 값에 따른 다양한 정보를 속성 형태로 제공할 때 유용하다.

    class Person(val age: Int, val name: String) { 
    	
        val adult: Boolean
        	get() = age >= 19 // 19세 이상이면 성인
            
        var address: String = ""
        	set(value) {
                // 인자로 들어온 문자열의 앞 10 자리만 필드에 저장
                field = value.substring(0..9)
            }
    }

    사용자 지정 setter를 사용하면 프로퍼티 내 필드의 설정되는 값을 제어할 수 있으나 var로 선언된 프로퍼티만 가능하다.

     

     

     

     

    함수

    기본 매개변수

    Java에서는 메서드의 매개변수가 많은 경우, 더 편리하게 사용하기 위해 축약된 매개변수를 갖는 매서드와 전체 매개변수를 갖는 매서드를 별도로 만들어 사용했다.

    void drawCircle(int x, int y){
        // 원본 메서드 호출
        drawCircle(x, y, 25);
    }
    
    void drawCircle(int x, int y, int radius){
        ...
    }
    
    drawCircle(10, 5);

     

    Kotlin에서는 매개변수에 기본 값을 지정할 수 있는 기본 매개변수(default parameter)가 존재하기 때문에 한개의 메서드만 생성해도 된다.

    // 반지름의 기본값으로 25를 갖는 함수
    fun drawCircle(x: Int, y: Int, radius: Int = 25){
        ...
    }
    
    drawCircle(10, 5)

     

     

    단일 표현식 표기

    Java에서는 매서드 내용을 항상 중괄호로 감싸야 했지만,

    Kotlin에서는 Unit 타입을 제외한 타입을 반환하는 함수라면 함수의 내용을 단일 표현식(single expression)을 사용하여 정의할 수 있다.

    fun calculateDouble(num: Int) = num * 2

     

     

    확장 함수

    Java에서는 기존에 만들어져 있는 클래스에 새로운 메서드를 추가하려면 해당 클래스를 상속하는 새로운 클래스를 작성해야한다.

    반면 Kotlin에서는 확장 함수(extension function)을 사용하여 상속 없이 기존 클래스에 새로운 함수를 추가할 수 있다.

    private fun String.withPostfix(postfix: String) = "$this$PostFix"
    
    // this를 사용하여 인스턴스에 접근
    fun String.withBar() = this.withPostfix("Bar")
    
    val foo = "Foo"
    
    // String 클래스에 포함된 함수를 호출하듯이 사용
    // foobar = "FooBar"
    val foobar = foo.withBar()
    

     

     

     

    람다 표현식

    람다 표현식(Lamda expression)은 람다식이라고 변역되고, 간단히 람다라고도 한다.

     

    익명 함수

    쉽게 말하면 람다는 익명함수(annoanonymous funtion)이다.

    익명 함수는 이름이 없는 함수를 말하며, 한번 사용된 후에 다시 사용되지 않는 함수를 만들 때 익명함수를 사용한다.

    함수를 따로 만들지 않고 코드 중간에 바로 익명함수를 만들면, 가독성이 좋아진다.

    val isPositive = fun(num: Int): Boolean{ 
    
    	return num > 0
    }
    
    // true 출력
    println(${isPositive(10)})

     

    람다 표현식의 형태

    • 람다는 { } 안에 작성된다.
    • { 매개변수 -> 함수본체 }의 형태를 띈다.
    • 함수 본체에서 마지막줄은 자동으로 리턴타입이 된다.

    위 익명 함수를 람다를 이용해 표현해보면 아래와 같다.

    val isPositive = { num : Int -> num > 0 }
    
    // true 출력
    println(${isPositive(10)})

     

    실제 프로젝트에서는 Listener를 달 때 람다식을 많이 사용하게 되는데, 예시는 다음과 같다.

    val button: Button = ... // 버튼 인스턴스
    
    // 람다 표현식을 사용하여 리스터 생성
    button.setOnClickListener({ v: View -> doSomething()})
    
    // 인자 타입 생략 가능
    button.setOnClickListener({ v -> doSomething() })

    하나의 메서드만 호출하는 람다 표현식은 멤버 참조(member reference)를 사용하여 간략하게 표현할 수 있다. 다음 코드는 멤버 참조를 사용한 예이다.

    // View를 인자로 받는 함수
    fun doSomethingWithView(view: View){
    	...
    }
    
    val button: Buttom = ... 
    
    button.setOnClickListener({ v -> doSomethingWithView(v)})
    
    // 멤버 참조를 사용하여 doSomethingWithView() 함수 바로 대입 가능
    button.setOnClickListener(::doSomethingWithView)

    메서드 뿐만아니라 프로퍼티도 멤버 참조를 지원한다.

    class Person(val name: String, val age: Int) {
        val adult = age > 19
    }
    
    // 전체 사람 목록 중, 성인의 이름만 출력
    fun printAdults(people: List<Person>) {
        people.filter({person -> person.adult})
        		.forEach { println("Name= ${it.name}") }
        
        // 멤버 참조를 사용하여 adult 프로퍼티를 바로 대입
        people.filter(Person::adult)
        		.forEach { println("Name= ${it.name}") }
    }

     

     

    코틀린 람다 표현식의 유용한 기능

     

    함수를 호출할 때 대입하는 파라미터 중 마지막 파라미터가 함수 타입이고,

    이 파라미터에 함수를 대입할 때 람다 표현식을 사용하면 이 람다 표현식은 함수의 인자를 대입하는 괄호 외부에 선언할 수 있다,

    val dialog = AlertDialog.Builder(this)
        ...
        // 마지막 파라미터가 함수 타입
        .setPositiveButton("OK", { dialog, which -> doOnOkay(which) })
        
        // 괄호 외부에 선언
        .setNagativeButton("Cancel") { dialog, which -> doOnCancel(which) }
        .create()

     

    함수가 단 하나의 함수 타입 매개변수를 가질 경우, 파라미터 대입을 위한 괄호를 생략하고 바로 람다 표현식을 사용할 수 있다.

    val button: Button = ... // 버튼 인스턴스
    
    button.setOnClickListener { v -> doSomething() }

     

    람다 표현식 내 매개변수의 개수가 하나인 경우 매개변수 선언을 생략할 수 있고, 매개변수의 참조가 필요한 경우 it을 사용한다.

    val button: Button = ... // 버튼 인스턴스
    
    button.setOnClickListener { doSomethingWithView(it) }

     

     

    인라인 함수

    람다 표현식을 사용하면 함수를 인자로 넘길 수 있는 고차함수를 쉽게 표현할 수 있다.

    그런데 람다 표현식으로 작성한 함수는 컴파일 과정에서 익명 클래스로 변환되는데, 이 코드를 호출할 때마다 매번 새로운 객체가 생성되므로 실행 시점의 성능에 영향을 미칠 수 있다.

     

    인라인 함수(inline function)를 사용하면 함수형 인자의 본체를 해당 인자가 사용되는 부분에 그대로 대입해서 성능하락을 방지할 수 있다.

    인라인 함수로 선언하려면 함수 선언 앞에 inline 키워드를 추가하면 된다.

    // 인자로 받은 함수를 내부에서 실행하는 함수
    inline fun doSomething(body: () -> Unit) { 
        println("onPreExecute()")
        body()
        println("onPostExecute()")
    }

     

     



    코틀린의 여타 특징

    타입 별칭

    제네릭 타입을 사용하다보면 복잡한 형태의 타입을 사용하게 되는 경우가 많다.

    이럴 때 제네릭의 타입 정의만으로는 내가 표현하고자 했던 정보를 정확히 유추하기 어렵다.

    코틀린에서는 타입 별칭(type alias) 기능을 제공하며 다음과 같이 선언 및 사용한다.

    // 타입 별칭 선언
    typealias PeopleList = List<Person>
    
    // List<Person> 대신 PeopleList 사용
    fun sendMessage(people: PeopleList) {
        people.forEach {
        	// 메세지 전송
        }
    }

     

    함수형 타입에도 타입 별칭을 지정할 수 있다.

    // 함수형 타입을 타입 별칭으로 설정합니다.
    typealias PersonFilter = (Person) -> Boolean
    
    // 메시지를 보낼 사람을 선택할 때 기준이 되는 조건을 함수의 인자(filterFunc)로 받는다.
    fun sendMessage(people: List<Person>, filterFunc: PersonFilter ) {
        people.filter(filterFunc)
            .forEach {
                // 메시지 전송
            }
    }

     

     

     

     

     

     

     

     

     

Designed by Tistory.