본문 바로가기
Android 개발/Kotlin

Kotlin을 배워보자 part1(Basic Types, Function, Null, 타입 변환, Lambda, 고차함수, Elvis, inline)

by Developer88 2019. 9. 24.
반응형

2017년 5월에 공식적으로 안드로이드 언어로 채택된 Kotlin은,

Java의 virtual Machine인 JVM에서 동작하는 컴파일 언어입니다.

 

2019 IO를 보면 Kotlin First, Kotlin/Everywhere라는 단어가 보입니다.

구글의 안드로이드 공식문서에서도 Java보다 kotlin 예제코드를 먼저 보여주기 시작 했구요.

점점 많은 개발자들이 안드로이드의 실제 프로덕트에 적용하며 추천하고 있기도 합니다.

 

이러한 Kotlin에 대해서, 이번 글을 시작으로 5part에 걸쳐 정리해 보겠습니다.

그럼 가장 먼저 변수선언에 대해서 정리해 보도록 하겠습니다.

 

1. 변수 선언을 위한 val과 var

변수 혹은 상수를 선언하는 방법에 대해서 알아보도록 할텐데요.

Java에서는 상수의 경우 final키워드를 사용 했었는데,

Kotlin에서는 val을 사용합니다.

Java와는 다르게 ":"다음에 타입을 기술하구요.

 

Kotlin은 형을 표현하지 않아도 대입된 값으로 부터 타입을 추론해 냅니다.

아래의 변수d를 보면 타입을 선언하지 않았지만, String으로 추론되므로,

멤버함수인 toUpeprCase()를 사용할 수 있었습니다.

 

변수는 var키워드를 사용하면 되는데요.

var도 val과 같이 ":"다음에 타입을 기술하는 것은 같습니다.

타입 추론이 가능하므로 생략 할 수도 있는데요,

다만, 초기화한 값과 다른 타입을 마음대로 넣을 수 있는 것은 아닙니다.

아래와 같이 Int로 초기화 하고 String을 넣으려고 하면 error가 발생하게 됩니다.

 

 

2. Basic Types

Kotlin의 기본타입들에 대해서 알아보겠습니다.

Java와는 다르게, Kotlin에서는 모든 타입이 객체입니다.

따로 primitive타입같은 것은 존재하지 않습니다.

그래서 각 타입에 멤버 함수를 바로 사용하는 것도 가능하구요

 

java에서 primitive type의 경우 double, float, long, int, short, byte를 소문자로 String만 대문자로 시작한 것과 다르게,

Kotlin에서는 객체이므로 아래 이미지와 같이 Double, Float, Long, Int, Short, Byte, String과 같이 다 대문자로 시작하게 합니다.

 

 

특이한 것은 char가 Java에서는 숫자형이었지만, Kotlin에서는 문자형으로 처리되므로 위의 Basic Type표에는 없습니다.

toInt() 메소드로 변경해 주면 해당 문자의 숫자값을 알수는 있지만, Java에서와 같이 숫자값을 바로 사용할 수는 없습니다.

 

그리고, Kotlin에서는 숫자에 underscore(_)를 지원하는데요.

예를들어 100만원일 경우, 1_000_000과 같이 표현 해도 숫자로 인식될 수 있다는 것이지요.

아래와 같이 언더스코어가 들어가더라도 숫자를 비교해 볼 수 있습니다.

 

 

 

3. Null

코틀린의 타입시스템은 null reference를 제거하는 것을 목적으로 한다고 공식문서에 나와 있을 정도로

Null Safety에 대해서 철저한 편입니다.

 

Java에서 했던 것 처럼, null값으로 초기화 하려고 하면 아래와 같이 컴파일 에러가 나게 됩니다.

 

 

물론 아래와 같이 String 으로 선언된 값에 null을 집어넣어도 컴파일 에러가 나게 됩니다.

 

 

3-1. safe-call operator "?"

그럼 null 값을 아예 못넣는 다는 것일까요? 그것은 아닙니다.

대신, null이 가능한 값에서는 null이 가능한 타입이라고 명확하게 표시하도록 하고 있습니다.

safe-call operator인 "?"표시를 붙이는 것인데요.

아래와 같이 String?타입으로 선언하면 null값을 넣을 수 있습니다.

 

 

String의 length를 볼 때도 아래와 같이,

아래와 같이 nullable한 변수에 대해서는 "?"표시를 붙인다음 값을 받아볼 수 있습니다.

nullable한 값에는 일일히 표시하도록 한 것이지요.

 

 

또한 이런 타입에 값을 대입하기 위해서는 null체크를  

 

이만큼 Nullble한 값과 아닌 값에 있어서 Kotlin은 명시적인 표현을 요구하고 있습니다.

 

3-2. Elvis operator "?:" (엘비스 연산자)

엘비스 프레슬리의 머리모양 같다고 해서 붙인, Elvis Operator라고 하는 것이 있는데요.

만약 null이 나오면, 다음에 나오는 값이 디폴트가 된다라는 것을 표현할 수 있는 Operator입니다.

 

Elvis Operator는 "?:" 를 이용하는데요.

Java에서 하던 null체크보다 훨씬 코드가 간결해 집니다.

아래에서는 null이 나오면 디폴트 이름이 대신 나오도록 하였습니다.

 

아래와 같이 응용해 볼 수도 있을 것 같네요.

 

 

 

밑에서 다룰 as라는 타입캐스팅 키워드를 이용하면,

Any?타입으로 받아서, Int로 캐스팅을 할 수 있는데요.

아래에서는 만약 null이라면 8을 대신 넣어주도록 하였습니다.

 

 

다음 글에서 list에 대해서 배울텐데요.

미리 가져와보면, 어떤 연산을 한 뒤에 결과값을 받아보고,

그 결과값이 null 이라면, emptyList()를 결과값으로 받아오게 할 수도 있을 것 입니다.

 

 

3-3. null과 let

Kotlin의 기본 라이브러리에서 제공하는 함수 중

nullable하지 않은 값에만 operation을 하기 위해 사용하는 let이라는 키워드가 있습니다.

Kotlin예제 같은 것을 보면 많이 쓰이기도 하는,

아주 유용한 함수인데요.

 

코드를 먼저 보겠습니다.

 

 

만약 list에 null이 아닌 것과 null이 포함되어 있을 경우,

아래와 같이 let을 사용해서, null이 아닌 값만 블록안으로 들어 오게 해서 연산을 할 수 있는 것이지요.

참고로 it은 아래 lamda에서도 다루겠지만, 블록안에 들어온 객체에 접근할 수 있는 키워드 입니다.

 

3-4. !! Operator

Kotlin에서는 NonNull즉 절대로 null이 아니라고 표현할 수 도 있는데요.

그것은 느낌표 두개"!!" 연산자를 통해서 아래와 같이 사용할 수 있습니다.

 

 

대신 이렇게 명시적으로 null이 아니라고 했는데,

null이 들어가면 exception이 나게 되므로 조심해서 사용해야 합니다.

4. 함수

4-1. Java와는 다른 Kotlin 함수 표현

Java에서는 아래와 같이 return타입을 선언하고 나서 인자와 그 타입을 정의해 주었는데요.

 

 

Kotlin에서는 fun키워드로 함수를 표시한 다음,

함수명을 쓰고, 인자가 있으면 인자와 그 타입을 정의 해 준 다음 ":"다음에 return 타입을 정의해 줍니다.

(위에서 변수 선언할 때도 보셨지만, 코틀린에서는 타입이 주로 뒤에 붙습니다.)

 

Kotlin에서는 함수를 간결하게 표현하는 방식들을 가지고 있는데요.

testSum2와 같이 대괄호를 표시하지 않고 "="으로 줄일 수 있습니다. 읽기도 편하고 간결하네요.

그리고 함수가 testSum3와 같이 expression(결과 값이 나오는)형태라면,  return타입을 정의하지 않고도 함수 선언이 가능합니다.

whichOneIsBigger와 같이 Expression으로도 표현이 가능구요.

 

 

 

4-2. Higher-order functions(고차 함수)

함수의 인자로 함수를 넣을 수도 있는데요.

이러한 함수를 Higher-order function이라고 합니다.

아래의 경우 String을 인자로 받아서 Int값을 return하는 mapper라는 함수를 인자로 받는 Higher-order함수 StringMapper입니다.

HelloWorld문자를 받아서, 인자로 함수가 동작해 그 결과 값인 10을 정상적으로 return해 줍니다.

 

Higer-order function이 함수에서 정의한 마지막 인자인경우,

"( )"를 빠져나와서 아래와 같이 넘겨줄 수 있습니다.

 

 

fold라는 메소드가 이를 이용하는 좋은 예라고 할 수 있는데요.

아래에서는  list의 각 아이템들을 모두 더한 값을 sum이라는 변수에 넣었습니다.

참고로 fold의 첫번째 인자는 초기값입니다.

 

 

위에서도 잠깐 다루었던,

let함수의 소스코드를 보면 아래와 같이 정의되어 있는데요.

복잡해 보이지만, 결국 T를 인자로 받고, R을 리턴하는 함수를 인자로 받는 것이라는 사실을 알 수 있습니다.

 

 

4-3. Lambdas 식

이번에는 람다식을 보도록 하겠습니다.

많이들 들어보셨고 Java에서 사용해 보셧던 분들도 있으실 텐데요.

Java의 Lambdas식의 문법과 크게 다르지 않습니다.

코드 먼저 보도록 하겠습니다.

 

안드로이드 개발할 때 쓰던 onTouchListener나 onClickListener를 봐야 실감이 좀 날 것 같은데요.

Java6으로 쓸때와는 확연히 다른 모습이지요. return도 없고, 인자에 붙이는 괄호도 않보이네요.

 

 

그럼, 위에서 사용했던 Lamda의 문법들을 정리해 보도록 하겠습니다.

 

a. Lambdas 문법

- Lambdas 표현식은 전체를 중괄호로 감싸야 함 { }

- 인자와  body는 "->"로 구분

- 인자를 "( )"로 감싸지 않음

- return값이 있는경우, lambda식의 마지막 expression이 return값이 됨

 

b. it

lambdas의 경우 인자가 하나인 경우가 대부분입니다.

이럴 경우는 "->"로 인자와 body 를 구별해줄 필요도 없어서,

아예 인자를 넣어주지 않고 it을 인자로 사용합니다.

 

 

 

4-4. inline 함수

Kotlin에서는 고차함수(Higer-order function)를 사용하면,

각각의 함수가 객체가 되어서,

메모리 할당과 가상 호출에 따른 runtime에 overhead가 발생할 수 있다고 합니다.

고차함수를 여러번 실행 하게 되면 그 만큼의 부담이 생긴다는 것 인데요.

이를 피하기 위해서 inline함수를 사용할 수 있습니다.

 

위의 말이 조금 어려울 수 있는데요.

다시 한번 정리해 보자면, 고차 함수로 함수를 인자로 사용할 경우,

그 함수가 객체가 되어서 리소스가 낭비될 수도 있다는 것입니다.

 

그런 현상을 피하게 위해, inline을 사용하면,

컴파일시에 해당 함수를 객체가 아니라 함수자체로 풀어서 넣어준다는 것입니다.

아래 이미지에서 보이듯이, 사실상 inline이라는 키워드가 추가된 것 이외에는 특별히 보이는 것은 없습니다.

다만 일부 overhead가 발생할 수 있는 코드에서, 그것을 방지할 수 있다는 것이지요.

 

 

조금 더 복잡해 보이는 경우도 보도록 하겠습니다. (아직은 이 부분까지 이해가 않되도 걱정하지 않아도 됩니다.)

결국에는 inline함수라는 것도 함수를 인자로 전달하는 고차함수인데요.

mapper(mEditor)의 사용을 보면, mapper에 인자로 mEditor를 넘겨서,

이 함수를 람다식으로 실행해서 mEditor객체를 받아서 사용할 수 있도록 하였습니다.

그럼 기존에는 일일히 이 함수를 실행하는 모든 메소드가 써야했던 한 줄이,

이러한 함수를 이용함으로서 줄어들게 되는 것이지요.

 

 

 

 

4-5. 익명 함수(Anonymous Function)

익명함수는 함수의 이름을 선언하지 않는다는 점을 제외하면 일반 함수와 크게 다르지는 않는데요.

lamdas를 쓰면 되는데, 왜 굳이 쓰려고 하는 걸까요?

그 이유중 하나는 익명함수를 이용하면 return타입을 표현할 수 있기 때문입니다.

 

 

익명함수를 쓰는 다른이유로는,

return키워드를 사용하여 원하는 곳에서 exit할 수 있기 때문인데요.

이에 반해,람다식의 경우는 일반적으로 return키워드를 사용하지 못 합니다.

물론 람다식에서도 label을 붙이거나, inline키워드를 사용하는 방법이 있기는 하지만,

훨씬 간편하게 원하는 위치에서 return할 수 있는 방법은 익명함수를 사용하는 방법이지요.

(이 부분에 대해서는 다른 글에서 자세히 다뤄볼 기회가 있을 것 같네요.)

 

 

4-6. Closure

자바스크립트에서 보고 듣던 Closure인데요.

익명함수나 Lambda식에서는 이러한 것이 가능합니다.

아래와 같이, foreach의 scope 밖에 있는 sum이라는 변수에 접근해서 값을 수정하는 것이 가능합니다.

 

 

역시 안드로이드에서의 사용법을 보면 좀 더 감이 올텐데요.

java에서 final이었어야 했던 sum이 이제는 closure라는 개념을 통해 쉽게 접근이 가능합니다.

 

 

4-7. 함수와 관련된 타입들

함수를 사용하는데 있어서 알아야할 타입들이 몇가지 있는데요.

하나씩 보도록 하겠습니다.

 

A. Unit 타입

Java에서의 void와 유사한 타입인데요.

아무값도 return하지 않을 때 사용합니다.

그런데 아무값도 return하지 않을 때는 Unit을 생략하는 것을 권장하고 있습니다.

noRedundency1과 noRedundency2는 같은 것이지요

Unit이라는 타입은 Java의 void 만큼 많이 쓰지는 않게 되겠습니다.

 

 

B. nothing타입

nothing타입은 throw의 사용과 관련이 있는데요.

throw를 하게될 때 코드는 더 이상 진행이 않되므로,

더이상 닿을 수 없는 그럼 지점을 마킹하기위해 사용됩니다.

아무 변수도 저장할 수 없는 키워드이기도 하구요.

 

쉽게 말하면, throw 할 때 return값의 키워드로 사용한다는 것 입니다.

throw를 하므로 어짜피 코드는 더이상 진행되지 못하고 exception처리를 하게 될테니까요.

 

위에서 정리해 봤던 elvis 표현식을 사용하면 다음과 같이 할 수 있습니다.

 

nothing은 어떤 변수가 null로 초기화 되었을 경우,

nothing?의 타입으로 추론되어 집니다.

 

 

 

5. Type Check와 Smart Cast 그리고 타입 변환

5-1. is 와 SmartCast

Java에서 InstanceOf를 통해서 특정타입인지 TypeCheck를 했었는데요.

Kotlin에서는 런타임에서 해당 비교대상이 TypeCheck를 위해,

is를 이용할 수 있습니다. 물론 반대인 "!is"도 사용가능하구요.

 

아래에서 a is String이라는 문장이 보이실텐데요.

인자로 들어온 a가 String이냐고 물어보는 것이지요.

 

 

그런데 위에서 재미있는 것은 whatIs함수 인자의 타입이 Any라는 사실입니다.

Any는 모든 클래스의 최상위 클래스이므로 어떤 타입이든 들어올 수 있는 상태인 건데요.

 

중요한 것은, Any타입으로 들어온 "Hello World"객체에 is를 사용함으로써,

String의 멤버함수인 length가 바로 실행되어서 11이 결과로 나온 부분인데요.

이것은, Kotlin이 is를 사용함과 동시에 Smat하게 String으로 Cast 해 주었기 때문에 가능한 것 입니다.

length를 얻기위해, 일일히 String으로 저희가 cast해 줄 필요가 없다는 것이지요.

이를 Smart Cast라고 합니다.

 

5-2. as

is가 값의 타입이 맞는지 확인하는 것이라면,

as는 원하는 타입으로 캐스팅을 하는 것 입니다.

아래와 같이 사용을 할 수 있구요.

 

 

as를 쓰는데 있어서 unsafe cast와 safe cast가 있는데요.

위와 같이,  만약 a가 null인 경우 형변환이 불가능하므로 Exception을 발생시킵니다.

그래서 unsafe cast라고 부르구요.

이런 것에 대비하기 위해서, safe cast라는 것이 있는데요.

 

만약 null값이 나오면 null을 반환할 수 있는 "as?"를 사용함으로써

아래와 같은 경우 safe cast라고 할 수 있습니다.

 

 

6. Interpolation

마지막으로 interpolation에 대해서 알아보겠습니다.

아래와 같이 달러표시를 이용하면 문자열에 계산된 값을 보여줄 수 있습니다.

 

 

혹은 아래와 같이 대괄호를 사용해서 문자열내에서 계산한 값을 보여줄 수 있구요.

 

 

7. 정리

여기까지 part1에서,

변수선언 방법, Basic Types, Null, 함수, Interpolation, Type Check와 SmartCast 및 형변환 및 interpolation까지 정리해 보았습니다. 가장 기본적인 부분들에 대해서 정리한 것인데요.

다음 글에서는 if, when 등의 conditional과 array와 같은 Collection등에 대해서 다루도록 하겠습니다.

>> Kotlin을 배워보자 part2(if, when, for, while, array, list, ranges)

728x90

댓글