var
/ val
을 붙여주어야 한다.var
: 변경 가능 / val
: 변경 불가능 (read-only)null
이 들어갈 수 있는 변수는 타입 뒤에 ? 를 붙여주어야 한다.new
를 붙이지 않아야 한다.Safe Call (?) : null
이 아니면 실행하고 null
이면 그대로 null
(실행하지 않는다)
val str: String? = "ABC"
str.length // 불가능
str?.length // 가능
Elvis 연산자 (?:) : 앞의 연산 결과가 null
이면 뒤에 값을 사용
val str: String? = "ABC"
str?.length ?: 0
이 변수는 절대 null
이 아니라고 단언
// 혹시나 null이 들어오면 NPE가 나오기 때문에
// 정말 null이 아닌게 확실한 경우에만 널 아님 단언!! 을 사용해야 한다.
fun startsWith(str: String?): Boolean {
return str!!.startsWith("A")
}
코틀린에서 자바 클래스를 가져다 사용할 경우
// 자바 코드에 Null 정보에 대한 어노테이션을 사용하면 코틀린이 이해할 수 있음.
@NotNull 혹은 @Nullable
public String getName() {
return name;
}
플랫폼 타입
코틀린이 null
관련 정보를 알 수 없는 타입 (Runtime 시 Exception이 날 수 있다.)
코틀린에서 null
이 들어갈 수 있는 타입은 완전히 다르게 간주된다.
한번 null
검사를 하면 non-null임을 컴파일러가 알 수 있다.
null
이 아닌 경우에만 호출되는 Safe Call (?.
) 이 있다.
null
인 경우에만 호출되는 Elvis 연산자 (?:
) 가 있다.
null
이 절대 아닐때 사용할 수 있는 널 아님 단언 (!!) 이 있다.
Kotlin에서 Java 코드를 사용할 때 플랫폼 타입 사용에 유의해야 한다.
is
, !is
, as
, as?
를 이용해 타입을 확인하고 캐스팅한다. (스마트 캐스팅 존재)Any
는 Java의 Object
와 같은 최상위 타입이다. null
까지 포함 최상위는 Any?
Unit
은 Java의 void
와 동일하다.Nothing
은 정상적으로 끝나지 않는 함수의 반환을 의미한다.if
/ if - else
/ if - else if - else
모두 Java와 문법이 동일하다.따라서 Kotlin에서는 삼항 연산자가 없다.
fun getPassOrFail(score: Int): String {
return if (score >= 50) {
"P"
} else {
"F"
}
}
when
으로 대체되었고, when
은 더 강력한 기능을 갖는다.in
을 사용한다.for
문 문법은 모두 동일하다.while
문과 do while
문은 동일하다.함수의 문법은 Java와 다르다!
접근지시어 fun 함수이름(파라미터): 반환타입 { }
public fun maxV1(a: Int, b: Int): Int {
if (a > b) {
return a
}
return b
}
// Kotlin에서 public은 생략 가능하다. 기본이 public 이다.
fun maxV2(a: Int, b: Int): Int {
return if (a > b) {
a
} else {
b
}
}
fun maxV3(a: Int, b: Int): Int =
if (a > b) {
a
} else {
b
}
}
// = 을 사용하면 명시적인 반환 타입을 생략 가능
fun maxV4(a: Int, b: Int) = if (a > b) a else b
*
(spread 연산자)를 붙여주어야 한다.class Person(
val name: String = "홍길동",
var age: Int = 1
)
constructor
키워드를 사용해 부생성자를 추가로 만들 수 있다.class Person(
val name: String = "홍길동",
var age: Int = 1
) {
val isAdult: Boolean
get() = this.age >= 20
// 혹은
val isAdult: Boolean
get() {
return this.age >= 20
}
}
class Person(
name: String = "홍길동",
var age: Int = 1
) {
val name = name
get() = field.uppercase() // name.uppercase()는 getter의 무한 순환으로 인해 사용 불가
}
fun getUppercaseName(): String {
return this.age.uppercase()
}
// 혹은
fun getUppercaseName() = this.name.uppercase()
var name = name
set(value) {
field = value.uppercase()
}
final
: override
를 할 수 없게 한다. default
로 보이지 않게 존재한다.
open
: override
를 열어 준다.
abstract
: 반드시 override
를 해야 한다.
override
: 상위 타입을 오버라이드 하고 있다. (어노테이션이 아니라 키워드)
상속 또는 구현을 할 때에 : 을 사용해야 한다.
상위 클래스 상속을 구현할 때 생성자를 반드시 호출해야 한다.
override
를 필수로 붙여야 한다.
추상 멤버가 아니면 기본적으로 오버라이드가 불가능하다.
open
을 사용해주어야 한다.상위 클래스의 생성자 또는 초기화 블럭에서 open
프로퍼티를 사용하면 예상하지 못한 버그가 생길 수 있다.
추상클래스
abstract class Animal(
protected val species: String,
protected open val legCount: Int
) {
abstract fun move()
}
class Cat(
species: String
) : Animal(species, 4) {
override fun move() {
println("고양이가 사뿐 사뿐 걸어가~")
}
}
class Penguin(
species: String
) : Animal(species, 2) {
private val wingCount: Int = 2
override fun move() {
println("펭귄이 움직인다~ 꿱꿱")
}
// 추상 클래스에서 자동으로 만들어진 getter를 override
// 추상 프로퍼티가 아닌 프로퍼티에 접근하기 위해선 open이 필요함
override val legCount: Int
get() = super.legCount + this.wingCount
}
인터페이스
interface Flyable {
// 자바의 default가 필요 없음
fun act() {
println("파닥 파닥")
}
fun fly() // 추상 메서드
}
interface Swimable {
// 코틀린에서는 backing field가 없는
// 프로퍼티를 Interface에 만들 수 있다.
val swimAbility: Int
fun act() {
println(swimAbility)
println("어푸 어푸")
}
}
class Penguin(
species: String
) : Animal(species, 2) {
private val wingCount: Int = 2
...
override fun act() {
super<Swimable>.act()
super<Flyable>.act()
}
override val swimAbility: Int
get() = 3
}
클래스를 상속받을 때 조심해야할 점
fun main() {
Derived(300)
}
open class Base(
open val number: Int = 100
) {
init {
println("Base Class")
println(number)
}
}
class Derived(
override val number: Int
) : Base(number) {
init {
println("Derived Class")
}
}
예상되는 결과는 Base Class와 3을 출력하길 기대한다.
하지만 상위 클래스에서 println(number)
를 호출하면 하위 클래스에 있는 number를 가져오는데
아직 상위 클래스에 constructor
가 먼저 실행된 단계라서 하위 클래스의 number의 초기화가 이루어지지 않아서
0이라는 값이 출력된다.
따라서 상위 클래스에서 constructor
와 init
블럭에서는 final
이 아닌 프로퍼티에는 접근하면 안된다.
자바에서의 접근 제어자
public
: 모든 곳에서 접근 가능protected
: 같은 패키지 또는 하위 클래스에서만 접근 가능default
: 같은 패키지에서만 접근 가능private
: 선언된 클래스 내에서만 접근 가능코틀린에서의 접근 제어자
public
: 모든 곳에서 접근 가능
protected
: 선언된 클래스 또는 하위 클래스에서만 접근 가능
internal
: 같은 모듈에서만 접근 가능
private
: 선언된 클래스 내에서만 접근 가능
Kotlin에서 패키지는 namespace 관리용이기 때문에 protected
는 의미가 달라졌다.
Kotlin에서는 default
가 사라지고, 모듈 간의 접근을 통제하는 internal
이 새로 생겼다.
생성자에 접근 지시어를 붙일 때는 접근 제어자 뒤에 constructor
를 명시적으로 써주어야 한다.
유틸성 함수를 만들 때는 파일 최상단을 이용하면 편리하다.
프로퍼티의 custom setter에 접근 지시어를 붙일 수 있다.
class Car(
internal val name: String,
private var owner: String,
_price: Int
) {
var price = _price
private set
}
internal
과 protected
는 주의해야 한다.public
이 된다. 따라서 Java에서는 Kotlin 모듈의 internal
코드를 가져올 수 있다.protected
멤버에 접근할 수 있다.companion object
를 사용해야 한다.companion object
도 하나의 객체로 간주되기 때문에 이름을 붙일 수 있고, 다른 타입을 상속 받을 수 도 있다.object
키워드를 사용한다.object : 타입
을 사용한다.static
을 사용하는 클래스static
을 사용하지 않는 클래스static
을 사용하는 클래스이다.inner
키워드를 붙여야 한다.inner
class에서 바깥 클래스를 참조하려면 this@바깥클래스
를 사용해야 한다.class House(
var address: String
) {
var livingRoom = this.LivingRoom(10.0)
// static이 아닌 클래스로 생성, 바깥 클래스 참조 가능
inner class LivingRoom(
private var area: Double
) {
val address: String
get() = this@House.address
}
}
when
과 함께 주로 사용된다.example.withIndex()
, example.keys
, example[1] = “test”
, example.entries
…null
을 넣을 수도 있다.fun 확장하려는클래스.함수이름(파라미터): 리턴타입 {
// this를 이용해 실제 클래스 안의 값에 접근
}
val 확장하려는클래스.프로퍼티이름: 리턴타입 {
get() 혹은 set() = ...
private
, protected
멤버 접근이 안된다!static
함수를 쓰는 것 처럼 Kotlin의 확장함수를 쓸 수 있다.infix
함수가 존재한다. 3 add 4
inline
함수가 존재한다.Kotlin에서 람다를 만드는 방법
// 람다를 만드는 방법 1
val isApple: (Fruit) -> Boolean = fun(fruit: Fruit): Boolean {
return fruit.name == "사과"
}
// 람다를 만드는 방법 2
val isApple2: (Fruit) -> Boolean = { fruit: Fruit -> fruit.name == "사과" }
Kotlin에서 람다를 직접 호출하는 방법
// 람다를 직접 호출하는 방법 1
isApple(Fruit("사과", 1000))
// 람다를 직접 호출하는 방법 2
isApple.invoke(Fruit("사과", 1000))
(파라티머 타입, …)
→ 반환 타입 이었다.filterFruits(fruits) { fruit -> fruit.name == "사과" }
// it로 생략 가능
filterFruits(fruits) { it.name == "사과" }
filter
/ filterIndexed
(필터에서 index가 필요할 시)map
(객체에서 특정 필드만 꺼내볼 때) / mapIndexed
/ mapNotNull
all
(조건을 모두 만족하면 true, 아니면 false) / none
(all의 반대) / any
(하나라도 만족하면 true)count
(List의 size랑 동일) / sortedBy
/ sortedbyDescending
/ distinctBy
(람다를 가지고 중복을 제거함)first
(무조건 null이 아님) / firstOrNull
(첫 번째 값 또는 null) / last
/ lastOrNull
groupBy
val map: Map<String, List<Fruit>> = fruits.groupBy { fruit -> fruit.name }
associateBy
중복되지 않는 키를 가지고 Map을 만들 때 사용val map: Map<Long, Fruit> = fruits.associateBy { fruit -> fruit.id }
flatMap / flattern (List<List>를 List로 바꿀 때)
data class
가 아닌 class
에서도 사용 가능하다.for ((key, value) in map.entries) { }
와 같은 key, value를 꺼내는 방식도 구조분해이다.break
와 continue
를 사용할 수 없다.takeIf
(주어진 조건을 만족하면 그 값이, 그렇지 않으면 null이 반환)와 takeUnless
(주어진 조건을 만족하지 않으면 그 값이, 그렇지 않으면 null이 반환)를 활용해 코드 양을 줄이고 method chaning을 활용할 수 있다.코틀린의 scope function은 일시적인 영역을 만들어 코드를 더 간결하게 하거나, method chain에 활용된다.
let
하나 이상의 함수를 call chain 결과로 호출할 때
val strings = listOf("APPLE", "CAR")
strings.map { it.length }
.filter { it > 3 }
.let(::println)
non-null 값에 대해서만 code block을 실행시킬 때
val length = str?.let {
println(it.uppercase())
it.length
}
일회성으로 제한된 영역에 지역 변수를 만들 때
val numbers = listOf("one", "two", "three", "four")
val modifiedFirstItem = numbers.first()
.let { firstItem ->
if (firstItem.length >= 5) firstItem else "!$firstItem!"
}.uppercase()
println(modifiedFirstItem)
run
객체 초기화와 반환 값의 계산을 동시에 해야할 때
객체를 만들어 DB에 바로 저장하고, 그 인스턴스를 활용할 때
반복되는 생성 후 처리는 생성자, 프로퍼티, init block으로 넣는 것이 좋다.
val person = Person("홍길동", 100).run(personRepository::save)
apply
객체 그 자체가 반환된다.
객체 설정을 할 때에 객체를 수정하는 로직이 call chain 중간에 필요할 때
fun createPreson(
name: String,
age: Int,
hobby: String
): Person {
return Person(
name = name,
age = age,
).apply {
this.hobby = hobby
}
}
also
객체를 수정하는 로직이 call chain 중간에 필요할 때
mutableListOf("one", "two", "three")
.also { println("four 추가 이전 지금 값: $it") }
.add("four")
with
특정 객체를 다른 객체로 변환해야 하는데,
모듈 간의 의존성에 의해 정적 팩토리 혹은 toClass 함수를 만들기 어려울 때
// this를 생략할 수 있어 필드가 많아도 코드가 간결해진다.
return with(person) {
PersonDto(
name = name
age = age
)
}