kotlin (코틀린) 시작해보기 (6)

오늘은 코틀린의 함수에 대해서 살펴보도록하자.

함수

함수 선언 및 사용

함수 선언은 자바와는 달리 fun을 사용해서 함수를 선언한다.

fun function(x: Int): Int {
    return x * 2
}

위와 같은 함수가 있을 경우 사용법은 아래와 같다.

val result = function(2)
println(result)

쓰는 법은 일반 다른 언어와 비슷한 문법을 가지고 있다.
또한 위의 함수는 expression 으로도 나타낼 수 있다.

fun function(x: Int) = x * 2

expression으로 쓸 경우에는 return 타입을 명시해주지 않아도 되며 return 키워드는 사용하지 않아야 한다.

Infix notation

infix 키워드를 사용해서 중위(Infix) 표현으로 사용할 수 도 있다.

fun Int.multiply(x: Int): Int {
    return this * x
}

위의 코드는 Int를 확장한 확장 함수이다. 간단하게 생각하면 Int 클래스의 함수를 더 추가 한다고 생각하면 되겠다.

val multiply = 3.multiply(10)

그럼 위와 같이 확장함수를 이용해서 간단하게 만든 multiply 를 사용할 수 있다. 중위 표현으로 변경하면 다음과 같다.

infix fun Int.multiply(x: Int): Int {
    return this * x
}

위와 같이 infix 키워드를 사용하면 중위 표현을 사용할 수 있다.

val multiply = 3 multiply 10

Default Arguments

함수의 파라미터(인자)에 기본값을 정의해 줄 수도 있다.

fun defaultArgumentsFun(x: Int = 100, y: Int = 200) {
    println(x + y)
}

위의 함수의 파라미터에는 x = 100 y = 200 으로 기본값으로 정의해 두었다.

defaultArgumentsFun()

위와 같이 사용할 수 있는데 파라미터가 없이 함수를 사용할 경우에는 기본값인 x에는 100이 y에는 200이 들어간다.

defaultArgumentsFun(100, 200)

위의 두개의 함수 사용은 같다고 볼 수 있다.

Named Parameters

함수의 파라미터는 함수를 호출 할 때 이름을 지정해 줄수 있다. 파라미터의 개수가 많거나 기본 값을 가진 경우에 사용하면 매우 유용하다.

fun namedParametersFun(x: Int = 100, y: Int = 200, z: Int) {
    println(x + y + z)
}

위와 같은 함수가 정의 되어 있을 경우에도 (z는 기본값이 없다.) Named Parameters를 사용할 수 있다. 사용 법은 아래와 같다.

namedParametersFun(x = 200, z = 100)

함수를 호출할 때 해당하는 이름을 지정해주면 된다. 당연히 z의 경우에는 기본값이 없기에 무조건 넣어줘야한다.

namedParametersFun(x = 200)

위와 같이 호출 할 경우에는 컴파일 에러가 발생한다.

Unit-returning functions

Unit은 어떠한 값도 리턴 하지 않는 것을 의미한다. 자바의 void와 비슷한 의미를 갖고 있다.
Unit을 리턴할 경우에는 리턴 타입을 명시해주지 않아도 된다.

fun unitReturnFun(x: Int) : Unit{
    println(x)
}

위의 코드는 Unit을 리턴하는 (리턴 값이 없는) 코드이다. 위의 코드는 Unit을 생략해도 된다.

fun unitReturnFun(x: Int) {
    println(x)
}

위와 같이 사용해도 컴파일 에러가 나지 않는다.

Variable number of arguments (Varargs)

코틀린에서도 자바와 같이 가변인자를 지원한다.

public static <T> void varargsParameters(T... ts){
  for(T t: ts){
    System.out.println(t);
  }
}

자바의 경우에는 타입뒤에 ...을 사용해서 가변인자로 만들 수 있다. 코틀린의 경우에는 vararg 키워드를 사용해서 만들수 있다. 위 자바 코드를 코틀린으로 바꾼다면 아래와 같다.

fun <T> varargsParametersFun(vararg ts: T) {
    for (t in ts){
        println(t)
    }
}

Higher-Order Functions and Lambdas

자바와 비슷하다. 코틀린은 고차함수도 사용가능하며 람다 표현식도 가능하다. 자바8을 사용하는 개발자라면 쉽게 다가갈 수 있다.
Higher-Order Functions은 파라미터로 함수를 받거나 함수를 리턴하는 함수이다.

잠깐 언급만 하고 가자. 일급언어의 조건은 다음과 같다.
1. 함수를 변수나 데이터 구조안에 담을 수 있다.
2. 함수를 파라미터로 전달 할 수 있다.
3. 함수를 반환(리턴) 값으로 사용할 수 있다.

위의 3가지를 만족하면 일급언어가 될 수 있다. 자바8도 3가지를 만족해서 일급 언어라고 할 수 있다. 메서드 레퍼런스가 영 맘에 안들어도 일단 만족은 하니…

아래 코드를 보자.

val list = mutableListOf(1, 2, 3, 4, 5)
val doubled = list.map { i -> i * 2 }

위의 코드는 고차 함수의 간단한 예이다. map은 함수를 파라미터로 받고 있다. 실제 코드는 아래와 같다.

public inline fun <T, R> Iterable<T>.map(transform: (T) -> R): List<R> {
    return mapTo(ArrayList<R>(collectionSizeOrDefault(10)), transform)
}

위에서 봤듯이 람다의 문법은 자바와 비슷하다. 다른 점이 있다면 () 대신 {} 감싸주는 부분이 다르다. 자바와는 조금 다르게 단일 파라미터에 it 이라는 명을 사용해서 조금더 간단하게 만들 수 있다. 위에 코드를 it을 사용해 바꾸어 보면 다음과 같다.

val list = mutableListOf(1, 2, 3, 4, 5)
val doubled = list.map { it * 2 }

-> 도 생략하고 조금더 간단해 졌다.

코틀린의 중요한 문법은 이정도로 된 듯 싶다. 아직 많긴 하지만 나머지는 공식 문서를 보면서 한번씩 해보는 것이 좋겠다.
다음시간에는 마지막으로 기타 여러 사항을 간단간단하게만 살펴보고 끝내자. 그래도 어느정도 공부는 된 듯 싶다.

필자가 한달전 쯤에 spring boot를 코틀린으로 만든 간단한 웹페이지가 있다. spring boot를 쓰는 개발자들은 한번씩 간단하게 살펴봐도 괜찮을 거 같다. 근데 워낙 자바로 만든거나 코틀린으로 만든거나 비슷한거 같다.

spring-boot-kotlin-example

위의 github에서 소스를 확인가능하다.

kotlin (코틀린) 시작해보기 (5)

오랜만에 코틀린 포스팅을 해보자!
이번시간에는 코틀린의 data class 와 object에 대해서 알아볼 예정이다.

data class

우리는 가끔 데이터만 저장하고 다른건 하지 않을 때가 많다. 예를들어 java의 getter, setter, hashcode/equals, toString 등 기계적인 작업을 할 때가 많다. 그래서 우리는 java 라이브러리인 lombok을 사용하긴 한다. java는 서드파트에서 지원해주지만 코틀린 경우에는 언어 자체에서 지원해 준다.

기본 문법은 아래와 같다.

data class User(val id: Long, val name: String)

class 앞에 data 키워드를 넣어 주면 된다. data class는 컴파일러가 자동으로 아래와 같은 메서드를 알아서 만들어 준다
– constructor
– getter/setter
– hashcode/equals
– toString
– componentN() functions
– copy() function

val user = User(1, "wonwoo")
println(user.id)
println(user.name)
val hashCode = user.hashCode()
val equals = user.equals(User(2, "test"))
val id = user.component1()
val name = user.component2()
val copy = user.copy(2, "wonwoo1")

위와 같이 생성자와 getter, hashCode, equals, componentN, copy 등이 만들어 졌다.
좀 더 자세히 살펴보자.

생성자

생성자는 기본적으로 파라미터에 따라서 만들어 진다. 예를들어 위와 같은 경우에는 id와 name의 생성자만 존재한다. 예를들어 모든 생성자를 만들고 싶다면 아래와 같이 하면 된다.

data class User(var id: Long? = null, var name: String? = null)

해당 변수에 초기화를 해주면 그에 맞게 생성자를 만들어 준다.

getter, setter

data class의 생성자에는 var혹은 val를 사용해야 된다. 동일하게 var는 읽기 쓰기가 가능하므로 getter, setter모두 생성 되지만 val 경우에는 읽기 전용이므로 getter만 생성 된다.

data class User(val id: Long, var name: String)

val user = User(1, "wonwoo")
user.id = 1 //컴파일 에러
user.name = "wonwoo1"

위의 name은 var로 선언하여 변경이 가능하지만 id 경우에는 val로 선언되어서 변경이 불가능하다.

hashcode/equals 와 toString

자바와 동일하게 hashcode와 equals는 쌍으로 만들어 진다. 물론 구현 또한 비슷하게 구현되어 지지 않나 싶다.
toString의 경우에는 "User(id=1, name=wonwoo)" 와 같은 형태로 구현되어 진다.
딱히 설명할게 없기에 넘어가자.

componentN

다른 언어(스칼라)에서는 Destructuring라고도 불린다.

val id = user.component1()
val name = user.component2()

위와 같이 N번째에 값을 자유롭게 꺼낼수 있다. component1 경우에는 첫 번째 파라미터인 id component2 경우에는 두 번째 파라미터인 name을 꺼내서 사용 할 수 있다. 이 것도 귀찮다 좀 더 깔끔하고 짧게 사용할 수 있다.

val (id, name) = user
println("id : $id name : $name")

위와 같이 각각의 원소들을 순서대로 꺼내서 사용할 수 있다. 좀 더 간편하게 object의 프로퍼티에 접근할 수 있다.

copy

말 그대로 복사를 하는 개념이다. java의 clone과 비슷하다. 조금 다른점은 일부 프로퍼티만 변경하고 나머지는 그대로 유지 할 수 있다는 점이다.

val user = User(1, "wonwoo")
val copy = user.copy(name = "wonwoo1")

위와 같이 id는 기존과 동일하게 하고 name만 wonwoo1로 변경하였다. 여기서 주의할 점은 새로운 user가 다시 생기는거지 기존 user를 변경하는 것은 아니다. 여기서 생소한 것이 보인다. 바로 (name = "wonwoo1") 이부분이다. 요즘 나오는 언어에는 거의 대부분 지원하는 듯하다. named parameters 혹은 Named Arguments 라고 불리는데 필자는 named parameters 라고 알고 있었는데 코틀린 문서에는 Named Arguments라고 있어 추가해서 넣었다.

object

코틀린에서는 static한 함수가 존재 하지 않는다. 하지만 그에 맞게 object라는 것을 지원해주는데 싱글톤 개념이다. 스칼라에서도 object가 있는데 스칼라와 동일한 것으로 보인다.
문법을 살짝 보자.

object Utils {
    fun sum(a: Int, b :Int) = a + b
}

위와 같이 object라는 키워드를 선언하면 된다. class와 문법은 비슷하다. object 뒤에 object명을 기술 해주고 바디를 작성해주면 된다. 사용법은 아래와 같다.

Utils.sum(1,10)

자바의 static 메서드를 호출하듯이 호출 하면 된다. 더 다양하게 사용하는 법은 있지만 아직 필자도 잘 모르기에 나중에 기회가 된다면 설명하도록 하자.

이렇게 간단하게 data class와 object에 대해서 알아봤다. object는 좀 더 봐야 할 듯하다. data class의 경우에는 아주 유용하게 쓰일 것 같다.
다음 시간에는 function에 대해서 살펴 보도록 하자.

kotlin (코틀린) 시작해보기 (4)

이번시간에는 코틀린의 프로퍼티와 인터페이스에 대해서 알아보자. 프로퍼티 경우에는 조금 다른점이 있는데 인터페이스같은 경우에는 자바와 거의 비슷하므로 간단하게만 설명해보겠다.

프로퍼티

프로퍼티 선언

class Product {
    val id :Long? = null
    val name : String? = null
    val price : BigDecimal? = null
}

null은 컴파일 에러만 피하기 위해 작성하였다. 여기서는 중요한게 아니므로 주의깊게 보지 않아도 된다. 실제 저런 코드는 거의 사용할 일이 없을 듯하다.
코틀린의 경우에는 위와 같은 형식으로 프로퍼티를 작성할 수 있다. val 로 선언된 경우에는 읽기만 가능하고 var 로 선언한 것은 쓰기 읽기 모두 가능하다.

var product = Product()
product.name = "iphone 7" //컴파일 에러

위와 같이 Product를 생성 후에 name에 접근해서 어사인을 할 경우에는 컴파일 에러가 발생한다. 위의 코드를 쓰기도 가능하게 하려면 프로퍼티 선언을 다음과 같이 해야한다.

class Product {
    var id :Long? = null
    var name : String? = null
    var price : BigDecimal? = null
}

한마디로 getter만 사용하고 싶을 경우에는 val getter setter 모두 사용하고 싶을 때는 var로 선언 하면 된다.
위의 경우에는 일반적인 방법이다. 위와 같이 일반적으로 할당한 값을 넣고 빼고 할 수도 있겠지만 그렇지 않은 경우도 있다.
예를들어 Product 클래스의 name 프로퍼티에 무조건 # 해쉬 값을 넣고 싶다고 가정해보자. 그럼 우리는 조금 커스텀하게 getter나 setter를 만들어야 된다.

getter와 setter

class Product {
    var id :Long? = null
    var name : String? = null
        set(value) {
            field = "#" + value
        }
    var price : BigDecimal? = null
}

위와 같이 name 프로퍼티 아래에 set이라는 키워드를 사용하면 된다. set이라는 키워드에 파라미터는 굳이 value라는 변수로 하지 않아도 된다. 코틀린의 권장사항일 뿐이지 다른 변수명으로 사용해도 상관은 없다. 그리고 뜬금 없이 field 라는 필드가 보인다. 저 변수는 getter와 setter 안에서만 사용할 수 있는 Backing 필드라고 부른다. 저 필드가 실제 위에서는 name이라는 필드에 어사인을 하는 그런 필드 인 듯 싶다.
위에서는 setter만 이용해서 값을 넣어지만 이번에는 getter로 가져올 때 # 해쉬를 붙어서 가져와 보자.

class Product1 {
    var id: Long? = null
    var name: String? = null
        get() {
            return "#" + field
        }
    var price: BigDecimal? = null
}

setter와 마찬가지로 get이라는 키워드를 사용해서 getter를 커스텀하게 만들 수 있다. getter의 경우에는 expression 으로도 만들 수 있다.

var name: String? = null
    get() = "#" + field

expression으로 좀 더 간단하고 깔끔해 진 듯하다. 하지만 setter의 경우에는 expression으로 만들 수 없는 듯하다. 해보니까 컴파일 에러가 발생한다. 가능하게 만들 수 있을 지는 모르겠으나 일단 현재는 잘 모르겠다.

접근제한자 및 어노테이션

setter나 getter에 접근제한자 및 어노테이션을 선언 할 수 있다.

var name: String? = null
    private set(value) {
        field = "#" + value
    }

위와 같이 set 키워드 앞에 접근제한자 private를 선언 하고 있다. 그러면 자바와 동일하게 현재 클래스 외에 다른 클래스들은 name에 접근하여 값을 어사인 할 수 없다.

var name: String? = null
    private get() = "#" + field

위와 같이 getter에도 할 수 있을 거라고 생각했지만 위의 코드는 컴파일 에러가 발생한다. 그 이유는 필드와 getter의 접근제한자가 다르기 때문이다. 그래서 필드에도 private를 선언해줬다.

private var name: String? = null
    private get() = "#" + field
    set(value) {
        field = "#" + value
    }

하지만 필드에 접근제한자를 사용하면 그 아래에 있는 getter setter 모두가 필드의 접근제한자를 따라간다.

var product = Product1()
product.name = "iphone 7" //컴파일 에러
println(product.name)     //컴파일 에러

위의 코드는 어사인할 때 도 컴파일 에러가 나며 가져올 때도 컴파일 에러가 발생한다.

@Inject set(value) {
    field = "#" + value
}

위와 같이 어노테이션도 사용할 수 있다.

상수

컴파일 타임에 값을 알 수 있는 프로퍼티는 const라는 키워드를 사용하면 된다.

const val SUBSYSTEM_DEPRECATED: String = "This subsystem is deprecated"
@Deprecated(SUBSYSTEM_DEPRECATED) fun foo() {  }

위와 같이 어노테이션에서도 사용할 수 있다.

초기화 지연

보통 non-null 타입을 갖는 프로퍼티를 선언하면 생성자에서 초기화해야 한다. 물론 개발을 할 때는 초기화 해주는 것이 좋지만 테스트할 경우에는 늘 좋은 것만은 아니다. 예를 들어 의존성 주입이나 단위 테스트를 통해 setup 메서드에서 프로퍼티를 초기화 할 수 도 있다. 그때 사용할 수 있는 키워드가 lateinit이라는 키워드이다.

class MyTest {
    lateinit var subject: TestSubject
    @SetUp 
    fun setup() {
        subject = TestSubject()
    }

    @Test 
    fun test() {
        subject.method() // null 검사 없이 직접 접근
    }
}

위와 같이 초기화를 하지 않아도 사용 할 수 있다. 하지만 이 경우에는 val 키워드를 사용할 수 없고 var 키워드만 사용 가능하다. 만약 초기화되기 전에 lateinit 키워드가 사용된 프로퍼티에 접근하면 초기화되지 않은 프로퍼티에 접근했음을 정확하게 알려주기 위해 특수한 익셉션 을 발생한다.

인터페이스

코틀린의 인터페이스는 java8과 아주 비슷하다. 그래서 java8을 사용하는 개발자라면 쉽게 배울 수 있을 듯하다. 자바와 마찬가지로 추상클래스와 인터페이스의 차이는 상태를 가질 수 없다.

interface MyInterface {
    fun bar()
    fun foo() {
      //함수 바디
    }
}

자바와 동일하게 interface라는 키워드를 사용하면 된다. java8과 동일하게 함수의 바디를 가질 수 있다.

인터페이스 구현

class MyClass : MyInterface{
    override fun bar() {

    }
}

class 명 뒤에 :를 사용해서 구현할 수 있다. 자바와 마찬가지로 한개 이상의 인터페이스를 구현할 수 있다.

인터페이스의 프로퍼티

interface MyInterface {
    val price :Int
    fun bar()
    fun foo() {
        println(price)
    }
}

위와 같이 인터페이스에 프로퍼티를 선언 할 수 있다. 인터페이스의 선언된 프로퍼티는 추상프로퍼티이므로 구현체에서 구현을 해주어야 한다.

class MyClass : MyInterface{
    override val price: Int = 100
    override fun bar() {

    }
}

위와 같이 구현클래스에서 price라는 프로퍼티를 구현?(선언)해야 한다.
인터페이스의 프로퍼티를 제외 하곤 자바의 인터페이스가 거의 동일하므로 나머지는 생략하겠다.

이렇게 오늘은 코틀린의 프로퍼티와 인터페이스에 대해 살펴 봤다. 다음 시간에는 data class와 object에 대해서 살펴보기로 하겠다.