Kotlin

Kotlin - 문법 정리

Bin's. 2019. 2. 25. 21:11


코틀린(Kotlin) ?

구글 I/O 2017 에서 구글이 안드로이드 공식 언어로 코틀린을 채택.
코틀린 은 JetBrains 에서 만든 언어입니다.
IntelliJ , Android Studio 에서 코틀린을 완벽히 지원합니다.


장점

호환성 : JDK 6과 완벽호환 , 구형 안드로이드 기기에서도 완벽 실행, 안드로이드 빌드 시스템 완벽호환.

성능 :  자바만큼 빠르다, 람다로 실행되는 코드는 종종 자바보다 훨씬 바르게 동작.

상호 운용성 : 자바와 100% 상호 운용가능, 기존 모든 안드로이드 라이브러리 사용가능.

학습 곡선 : 자바를 하던 사람은 배우기 매우 쉽다, Android Studio 에서 자바 코드를 코틀린으로 자동 변환해주는 도구를 지원.

변수와 상수

var a: Int = 10 // var 변수명: 자료형 =
val b: Int = 20 // val 변수명: 자료형 =

 코틀린에서 변수는 var, 상수는 val 로 선언합니다.

코틀린은 자료형을 지정하지 않아도 추론하는 형추론을 지원합니다


var c = 10.10 // var c: Double
var d = "Hello World!" // var d: String

출력

println("Hello World!")

자료형

 정수

실수 

문자 

논리 

 Long : 64비트 정수

Double : 64비트 부동소수점

String : 문자열 

Boolean 

Int : 32비트 정수

 Short : 16비트 정수

Float : 32비트 부동소수점

 Char : 하나의 문자 

 Byte : 8비트 정수


자바와 달리 Char 형은 숫자형이아닙니다.

그리고 문자열의 리터럴은 큰따옴표 "", 한 문자는 ''로 자바와 동일합니다.


타입 캐스팅

var number = 1
var a = number.toLong() // .to자료형() 형태로 타입 캐스팅이 가능하다
var b = number.toShort()
var c = number.toByte()
var d = number.toDouble()
var e = number.toFloat()
var textNumber = "1"
var f = textNumber.toInt()


여러 줄의 문자열 표현

val str = """안녕하세요
코틀린 문법정리입니다.
trimIndent() = 들여쓰기 제거
""".trimIndent()

여러 줄에 걸쳐 문자열을 표현할 때는 큰따옴표 3개를 리터럴로 사용합니다.


문자열 비교

val str = "Hello"
if (str == "Hello"){
println("안녕하세요")
}else{
println("누구세요?")
}

문자열 비교는 == 를 사용합니다. 자바의 equals() 메서드와 대응합니다.

자바에서 == 오브젝트 비교시 사용합니다 , 코틀린은 === 를 사용.


문자열 템플릿

val str = "코틀린 "
println(str + "문법정리") // 자바에서 사용하던 방식
println("$str 문법정리") // 출력시 코틀린 문법정리
println("${str}문법정리}") // 출력시 코틀린 문법정리

복잡한 문자열 표현할 때 아주 편리합니다.


  배열

val num : Array<Int> = arrayOf(1, 2, 3, 4, 5)
val num2 = arrayOf(1, 2, 3, 4, 5) // 자료형이 생략가능합니다
num[0] = 5
num2[0] = 0

코틀린에서 배열은 Array 라는 타입으로 별도로 지정해줍니다

타입은 <> 제네릭을 사용해서 지정해줍니다

배열의 인덱스에 접근하는 것은 대괄호 [] 안에 인덱스 값을 지정하는 것과 자바와 동일.


제어문

if

val a = 10
val b = 20

var max = a
if(a < b) max = b

실행할 문장이 한 줄이면 코드블럭을 생략할수 있습니다.

위에 코드를 좀 더 코틀린 식으로 바꾸면

if(a > b){ // ; 세미콜론을 제외한 자바와 완전히 동일한 코드
max = a
}else{
max = b
}

if (a > b) max = a else max = b

max = if (a > b) a else b // 코틀린 방식

변수에 if문을 넣을수도 있습니다.


when

val x = 1

when(x){
1 -> println("x == 1")
2, 3 -> ("x == 2 or x == 3") // 값 하나
in 4..7 -> ("x == 4..7") // 여러 값은 .. 로 사용
!in 8..10 -> ("!8..7") // in 연산자로 범위지정
else -> {// 나머지
print("x 1이나 2가 아님")
}
}

자바에서 switch문과 매우 비슷해보일겁니다

맞습니다 코틀린에서는 switch문이 when 문 입니다.

val number = 1

val numStr = when (number % 2){
0 -> ""
1 -> ""
}

이런식으로 if 문과 같이 상수에 사용이 가능했습니다.

val number = 1

fun kotlinTest(num: Int) = when (num % 2) {
0 -> ""
else -> ""
}

println(kotlinTest(number))

이런식으로 함수의 반환값으로 사용할 수도 있습니다.


for

val numbers = arrayOf(1, 2, 3, 4, 5)

for (num in numbers){
println(num)
}

코틀린의 for 문은 자바의 foreach 문과 흡사합니다.

이 코드를 해석하자면 1부터 5까지 담겨 있는 배열을 

순회하면서 모든 인덱스 값을 출력하는 for 문의 예 입니다.

in 키워드를 이용하여 모든 요소를 num 변수로 가져옵니다

// 1 ~ 3 까지 출력
for (i in 1..3) {
println(i)
}
// 0 ~ 10 까지 2씩 증가하며 출력
for (i in 0..10 step 2) {
println(i)
}
// 10부터 0 까지 2씩 감소하며 출력
for (i in 10 downTo 0 step 2) {
println(i)
}

step 키워드랑 downTo 를 이용한 for 문 예제입니다.


while

var count = 5
println(count)
while (count > 0) {
count--
println(count) // 4; 3; 2; 1; 0;
}

//do while
var count2 = 10
do {
count2--
println(count2) // 9; 8; 7; 6; 5; 4; 3; 2; 1; 0;
}while (count2 > 0)

자바와 완전히 동일합니다.


클래스(class)


클래스 선언

class Person{
// 클래스 선언
}

val person = Person() // new 키워드 사용안함

클래스를 선언했고, 생성한 클래스를 인스턴스를 할때 new 키워드는 코틀린에서 사용하지 않습니다.


생성자

class Person {
constructor(name: String){
println(name)
}
}

코틀린에서는 constructor 키워드로 생성자를 표현합니다.

이 키워드는 생략할수도있습니다.

class Person(name: String) {
init {
println(name)
}
}

보조 생성자가 없을시 초기화 코드가 필요하다면 init 키워드 안에 

작성한 코드는 클래스를 인스턴스화할 때 가장 먼저 초기화됩니다.


프로퍼티

class Person(var name: String)

val person = Person("코틀린")
person.name = "문법정리"
println(person.name)

자바에서는 클래스에서 속성을 사용하여 멤버에 직접 접근하기위해 

게터(getter) / 세터(setter) 메서드 를 작성해줬는데요

코틀린에서는 보통 프로퍼티를 사용합니다.


접근 제한자

 public ( 생략 가능 ) : 전체 공개, default 값 = 아무것도 안 쓰면 기본적으로 public. 

 private : 현재 파일 내부에서만 사용가능

 internal : 같은 모듈 내에서만 사용가능

 protected : 상속받은 클래스에서 사용가능

 

class A {
val a = 1
private val b = 2
protected val c = 3
internal val d = 4
}

자바와 달리 internal 이라는 접근 제한자가 존재합니다

프로젝트가 안드로이드 app 모듈뿐만아니라

 스마트폰용, 시계용, TV용, Android Things 용 

안드로이드 앱을 만든다면 모듈 4개를 생성합니다.

internal 은 이 모듈 간 접근을 제한하는 키워드 입니다.


상속

open class Animal(val name: String) {

}

class Dog(name: String) : Animal(name) {

}

코틀린에서 클래스 상속은 기본적으로 금지됩니다. 

자바에서는 extends 사용한다면 코틀린에서 : 키워드로 대채합니다

여기서 상속을 하기위해서 open 이라는 키워드를 사용합니다.

또한 생성자가 존재한다면 위에 코드처럼 작성가능합니다.


내부 클래스

class OuterClass {
var a = 10

inner class OuterClass2 {
fun kotlintest(){
a = 20
}
}
}

클래스 안에 내부 클래스를 선언할시 inner 키워드를 사용합니다.

내부 클래스는 외부 클래스에 대한 참조를 가지고 있습니다.


추상 클래스

abstract class A { // 추상 클래스
abstract fun func() // 추상 메서드
fun func2(){

}
}

class B : A(){ // 추상 클래스를 상속 받았을시 미구현 메서드를 구현해야함
override fun func() { // 구현함
println("코틀린 문법정리")

}
}

val a = A() // 에러
val b = B() // OK

자바와 동일한 특성을 가지고 있고

abstract 키워드를 붙히고 class 를 적어줘야 오류가 나지않는다.


인터페이스

인터페이스는 미구현 메서드를 포함하고 구현된 메소드등 클래스에서 다중구현을 할때 사용합니다.

인터페이스 선언

interface Runnable{
fun run()
fun fastRun() = println("빨리 달린다")
}

인터페이스는 추상 클래스와 달리 다중 구현이 가능합니다.

인터페이스는 구현이 없는 메서드뿐만 아니라 구현된 메서드를 포함할 수 있습니다. 

자바 8 의 default 메서드.


인터페이스 구현

class Human : Runnable{
override fun run() {
println("달린다")
}
}

클래스를 상속받는거와 같이 : 키워드를 사용해 인터페이스를 구현해줍니다


상속과 인터페이스

open class Animal

interface Run {
fun run()
fun fastRun() = println("빨리 달린다")
}

interface Eat {
fun eat()
}

class Dog : Animal(), Run, Eat {
override fun run() {
println("달린다")
}

override fun eat() {
println("먹는다")
}
}

val dog = Dog()
dog.run() // 달린다
dog.eat() // 먹는다

상속은 단하는의 클래스만 상속하고 인터페이스는 , 콤마 로 구분합니다.


 데이터 클래스( Data Transfer Object )

// data class 클래스명(val 변수명 : 쟈료형..)
data class DTO(val dto: String)

코틀린은 getter / setter 를 구현할필요없이 DTO 를 간단히 구현가능하다.


null 가능성

코틀린에서는 null 가능성을 매우 중요시여깁니다.
기본적으로 객체를 불변으로 보고 null값을 허용하지 않습니다.

null 허용( ? )

val a: String // 에러 초기화를 해줘야합
val b: String = null // 에러 : 코틀린은 기본적으로 Null 을 허용하지 않음.
val c: String? = null //OK

코틀린에서 null 값을 허용하기위해 자료형 오른쪽에 ? 기호를 붙여주면

null 값을 허용하게 됩니다.


lateinit

lateinit var a: String
a = "Hello Kotlin"
println(a) // 출력값 : Hello Kotlin

코틀린에서 변수는 초기화를 해줘야합니다.

하지만 lateinit 키워드를 사용해 늦은 초기화를 할 수 있습니다.

lateinit 사용조건

 var 변수에만 사용한다.

 null 값으로 초기화할 수 없다.

 초기화 전에는 변수를 사용할 수 없다.

 Int, Long, Double, Float 에서 사용불가


lazy

val str: String by lazy {
println("안녕")
"Kotlin"
}

println(str) // 안녕; Kotlin
println(str) // Kotlin

var 에서 lateinit 을 사용해줬다면

val에서만 사용가능한 lazy 사용방법은

val 선언 뒤에 by lazy 와 블록을 작성해주면됩니다

마지막 줄에는 초기화할 값을 작성합니다.


null값이 아닐때( !! )

val name: String? = "코틀린"

val name2: String = name // 에러
val name3: String? = name // OK

val name4: String =name!!

변수 뒤에 !!를 추가하면 null 값이 아님을 보증하게되어

name 변수는 String? 타입입니다.

null 을 허용하죠 String 타입으로 변환하기위해서 !! 키워드를 붙여줘서 

null 이 아니라는걸 보증해줍니다.


안전한 호출( ?. )

val str: String? = null

var upperCase = if (str != null) str else null //null
upperCase = str?.toUpperCase() // null

안전한 호출은 ?. 연산자를 사용합니다

?. 연산자를 사용하면 null 이 아닐경우에만 호출이 됩니다. 위 코드는 str 이 null 이고

upperCase 라는 변수에 str 이 null 아니면 str 을 대입 아니면 null 을반환합니다

마지막줄은 upperCase 라는 변수에 null 아니면 대문자로 변경하고 null값이면 null을 반환하는 코드입니다.


엘비스 연산자( ?: )

val str: String? = null

var upperCase = if (str != null) str else null
upperCase = str?.toUpperCase() ?: "null 값입니다 초기화 하시오"

안전한 호출 시 null이 아닌 기본값을 반환하고 싶을 때 엘비스 연산자를 함께 사용합니다.

str이 null이 아니면 대문자로 변경하고 null 값이면 

뒤에 문자열을 출력합니다.


컬렉션

컬렉션은 개발에 유용한 자료구조를 뜻합니다.

리스트 ( List )

val timetable: List<String> = listOf("국어", "수학", "영어")

배열과 비슷하게 자료형의 데이터들을 순서대로 가지고 있는 자료구조입니다.

요소를 변경할 수 없는 읽기 전용 리스트는 listOf() 메서드로 작성.

val timetable = listOf("국어", "수학", "영어")

물론 코틀린은 형추론으로 자료형  생략이 가능합니다.

val timetable = mutableListOf("국어", "수학", "영어")

timetable.add("프로그래밍") // 프로그래밍을 맨 뒤에 추가

timetable.removeAt(0) // index 값이 0 인 국어를 삭제

timetable[0] = "코틀린" // index 값이 0 인 국어를 코틀린으로 변경

println(timetable) // [코틀린, 영어, 프로그래밍]
println(timetable[0]) // 코틀린

요소를 변경하는 리스트를 작성할 때는 mutableListOf() 메서드를 사용합니다.


여기서 자바의 리스트와 다른점은 index 값으로 접근할 수 있다는 겁니다.


맵 ( Map )

// 읽기 전용 맵
val map = mapOf("a" to 1, "b" to 2, "c" to 3)

// 변경 가능한 맵
val citiesMap = mutableMapOf("한국" to "서울", "일본" to "동경", "중국" to "북경")

citiesMap["한국"] = "서울특별시" // 요소에 덮어쓰기
citiesMap["미국"] = "워싱턴" // 요소 추가

맵 ( Map ) 은 키( key ) 와 값( value )의 쌍으로 이루어진 키가

중복될 수 없는 구조인 자료구조입니다.

리스트와 같이 읽기 전용과 요소를 변경할수 있는 Map 으로 만들수있습니다

mapOf() : 읽기 전용 맵

mutableMapOf() : 변경 가능한 맵

for ((k, v) in map){ // map 의 키 와 값을 탐색
println("$k -> $v") //출력값 : a -> 1; b -> 2; c -> 3;
}

 for 문을 이용하여 간단히 map 의 키 와 값을 탐색 할 수 있습니다.


집합

// 읽기 전용 집합
val languageSet = setOf("코틀린", "자바", "노드")
// 수정 가능 집합
val languageSet2 = mutableSetOf("코틀린", "자바", "노드")
languageSet2.add("파이썬")
languageSet2.remove("노드")

println(languageSet2.size) // 집합의 크기
println(languageSet2.contains("코틀린"))// 집합에 요소값이 존재하는지 : true

집합은 중복되지 않는 요소들로 구성된 자료구조입니다.

setOf() : 읽기 전용 집합

mutableSetOf() : 수정 가능 집합


람다식

코틀린은 자바 8 에서 지원하는 람다식을 지원합니다.

람다식은 함수를 표현하는 방법으로 익명 클래스, 함수를 간결하게 표현할수있습니다.

fun addOne(x: Int, y: Int): Int {
return x + y
}

add() 메서드는 2개의 인수를 받아 더해주는 메서드입니다.

fun addTwo(x: Int, y: Int) = x + y

반환 자료형을 생략하고 블록 {} 을 생략하고 return 도 생략했습니다.

이렇게 람다식을 사용하게되면 코드가 간결해주는 장점이 있지만

디버깅이 어렵고 많이 쓰면 코드의 가독성이 떨어지게 된다는 단점이 있습니다.

var add = { x: Int, y: Int -> x + y }
println(add(3, 5)) // 출력 값: 8

또한 변수에 일반 함수처럼 사용할 수 있씁니다.


SAM 변환

자바로 작성된 메서드가 하나인 인터페이스를 구현할 때는 함수를 작성할수있습니다
바로 SAM ( Single Abstract Method) 변환이라고합니다

button.setOnLongClickListener(object : View.OnClickListener{
override fun onClick(p0: View?) {
// 클릭 이벤트
}
})

안드로이드에서 버튼에 클릭 이벤트 리스너를 구현하는 코드입니다.

여기서 SAM 변환을하면

button.setOnClickListener {
// 클릭 이벤트
}

이런식으로 인수가 람다식인 경우는 람다식을 괄호 밖으로 뺄 수있고유일한 인수일 경운에는 괄호를 생략하고

자료형과 인수가 하나인 경우에는 이를 아예 생략하고 블록내에서 접근이 가능하게됩니다.


확장 함수

확장 함수 : 기존에 있던 클래스에 기능을 추가하는 함수.

fun Int.isEven() = this % 2 == 0

val a = 10
val b = 11

println(a.isEven()) // true
println(b.isEven()) // flase

Int class 에 . 을 찍고 함수 이름을 작성해줍니다

이 객체를 this 로 접근할 수 있습니다 또한 리시버 객체라고 부릅니다.


형 변환 ( as )

open class Animal

class Dog : Animal()

val dog = Dog()

val animal = dog as Animal

클래스 간에 형 변환을 하려면 as 키워드를 사용합니다


형 체크 ( is )

val str = "Hello"

if (str is String){ // str String 형이라면
println(str.toUpperCase())
}

is 키워드를 사용하여 형 체크를 합니다.

자바에서 instanceOf


고차 함수

고차 함수 : 함수의 인수로 함수를 전달하거나 함수를 반환할 수 있는 함수.

// 인수 : 숫자, 숫자, 하나의 숫자를 인수로 하는 반환값이 없는 함수
fun add(x: Int, y: Int, callback: (sum: Int) -> Unit) {
callback(x + y)
}

add(5,3) { println(it)} // 8

add 함수는 x, y, callback 3개의 인수를 받고

callback 에 x + y 값을 전달합니다.

그리고 add() 함수에 5와 3을 인자로 넣어주면 

callback 은 더한값을 반환해줍니다.


동반 객체 ( companion object )

class Fragment {
companion object {
fun newInstance() {
println("생성됨")
}
}
}

val fragment = Fragment.newInstance()

프래그먼트는 팩토리 메서드를 정의하여 인스턴스를 생성해야 합니다.

팩토리 메서드 : 생성자가 아닌 메서드를 사용해 객체를 생성하는 코딩 패턴

코틀린은 자바의 static과 같은 정적인 메서드를 만들 수 있는 키워드를 제공않함,

대신 동반 객채( companion object ) 으로 구현합니다.


let() 함수

//fun <T, R> T.let(block (T) -> R): R
var str: String? = null
val result = str?.let{
Integer.parseInt(it)
}

let() 함수는 블록에 자기 자신을 인수로 전달하고 수행된 결과를 반환합니다.


 with() 함수

//fun <T, R> with(receiver: T, block T.() -> R): R
var str: String? = null

with(str){
println(this?.toUpperCase())
}

with() 함수는 인수로 객체를 받고 블록에 리시버 객체로 전달합니댜.


apply() 함수

// fun <T> T.apply(block: T.() -> Unit): T
val result = car?.apply{
car.setColor(Color.RED)
}

apply() 함수는 블록에 객체 자신이 리시버 객체로 전달되고 이 객체가 반환됩니다.

객체의 상태를 변회시키고 그 객체를 다시 반활할 때 주로사용합니다.


run() 함수

// fun <R> run(block: () -> R): R
val avg = run {
val korean = 100
val english = 80
val math = 60
(korean + english + math) / 3.0
}

run() 함수는 익명 함수처럼 사용하는 방법과,

객체에서 호출하는 방법을 모두 제공합니다.

익명 함수처럼 사용할 대는 블록의 결과를 반환합니다.

임시변수가 많을대 유용

// fun <T, R> T.run(block: T.() -> R): R
str?.run{
println(toUpperCase())
}

객체를 블록의 리시버 객체로 전달하고 블록의 결과를 반환합니다

안전한 호출을 사용할 수 있어서 with() 합수보다는 더 유용