코틀린을 다시 정리해보자!
우아한테크코스 서비스 근로 크루들과 ‘코틀린 인 액션’ 책으로 한번 공부를 하였지만 다시한번 초심의 마음으로 정리를 한번 해보고자 한다.
인프런 최태현님의 자바 개발자를 위한 코틀린 입문 강의를 보고 정리하였다.
코틀린에 관한 TMI
- 코틀린은 IntelliJ를 만든 JetBrains 회사에서 만들었다.
- IntelliJ가 Java로 작성되어 있는데 유지보수 하다가 화가 났다고한다.
- 코틀린은 Java와 100% 호환 가능하다.
- 코틀린은 정적 타입 언어이다. (구성 요소를 컴파일 시점에 알 수 있다)
- 코틀린에서 별도의 지시어를 붙이지 않으면 모두 public 이다.
1. 변수 다루기
- 모든 변수는 val로 만들고 꼭 필요한 경우만 var로 변경한다.
- 코틀린에서의 원시 타입 long vs Long
- 실행시 Long으로 보이지만 상황에 따라 코틀린이 원시타입으로 바꾸어 적절히 처리해준다.
- nullable 변수에 따라 ?를 추가한다.
- new 키워드를 사용하지 않는다.
2. null 다루기
1. Safe Call : null이 아니면 실행하고, null이면 실행하지 않는다.
- null인 경우 값 자체가 null이 된다.
2. Elvis 연산자 : 앞의 연산 결과가 null이면 뒤의 값 사용 (null 값 변경) ?:
val str: String? = "ABC" // null 가능
str?.length ?: 0
3. 널 아님 단언 : nullable type 이지만, 아무리 생각해도 null이 될 수 없는경우 !!
val str: String? = "ABC" // null 가능
str!!.length
4. Kotlin에서 Java 코드를 사용할 때 플랫폼 타입 사용에 유의해야 한다.
- Java 코드를 읽으며 널 가능성을 확인할 수 있다.
- NotNull, Nullable
3. Type을 다루는 방법 (is, !is, as, as?, Any, Unit)
1. 자바 처럼 암시적 변경이 불가능하다. 명시적으로 변환하자.
val number1 = 3 // Int
val number2: Long = number1.toLong() // 명시적 변환
2. 타입변환(Java와 Kotlin을 비교해보자) - 스마트캐스트
if (obj instanceof Person) {
Person person = (Person) obj;
}
if (obj is Person) {
// val person = obj as Person
println(obj.age) // if에서 체크해주어서 스마트캐스트!
}
- is, !is
- as, as? (safe call과 비슷하다)
- 타입이 맞으면 타입 캐스팅
- 타입이 맞지 않으면 null
- null이면 null
3. 타입 3가지
- Any : 자바의 Object (모든 객체의 최상위 타입)
- Unit : 자바의 Void, 타입 인자로 사용 가능하다.
- Nothing : 함수가 정상적으로 끝나지 않음. 에러 실행
fun fail(message: String): Nothing {
throw IlleagalArgumentException(message)
}
4. String 인덱스
- 중괄호가 없어도 좋으나 중괄호를 사용하는게 좋다.
- 가독성
- 일괄 변환
- 정규식 활용
val name = "제로"
println("이름 : {$name}")
4. 연산자를 다루는 방법
- 비교 연산자에서 Java와 다르게 자동으로 compareTo를 호출한다.
- 동등성, 동일성
- 동일성 : 값이 같다 (자바에서 equals, 코틀린에서 ==)
- 동일성 : 완전히 동일한 객체이다 (자바에서 ==, 코틀린에서 ===)
5. 제어문을 다루는 방법
- if 사용방법은 Java와 같다.
- 한 가지 다른 점이 있다. 값이 도출된다. 즉 바로 리턴할 수 있다.
- Java의 If-else 는 Statement : 프로그램의 문장. 하나의 값으로 도출되지 않는다.
- Kotlin의 If-else는 Expression : 하나의 값으로 도출되는 문장
fun getGrade(score: Int): String {
return if (score >= 90) {
"A"
} else if (score >= 80) {
"B"
} else if (score >= 70) {
"C"
} else {
"D"
}
}
3. when절 (자바의 switch)
fun getGrade(score: Int): String {
return when(score / 10) {
9 -> "A"
8 -> "B"
7 -> "C"
else -> "D"
}
}
// 다양한 분기 가능
fun getGrade(score: Int): String {
return when(score) {
in 90..99 -> "A"
in 80..89 -> "B"
in 70..79 -> "C"
else -> "D"
}
}
- Enum Class, Sealed Class와 함께 사용할 경우 더욱더 진가를 발휘한다.
6. 반복문을 다루는 방법
1. for-each
val numbers = listOf(1L, 2L, 3L)
for (number in numbers) {
println(number)
}
2. 정통적인 for문
- downTo: 내려가는 경우
- step : 증가값이 1이 아닌경우 지정 가능
for (i in 1..3) {
println(i)
}
7. 예외를 다루는 방법
- try-catch-finally
- 문법적으로 동일하다
- Expression이다.
- 모두 Unchecked Exception 간주한다.
- 자바의 경우 예외가 발생할 수 있는 메서드라면 throws를 통해 명시를 해주어야 한다(Checked Exception).
- 코틀린은 구분하지 않는다.
8. 함수를 다루는 방법
= 문법 : 하나의 결과값이라면 =으로 대체할 수 있다. 반환타입을 생략할 수 있다(유추할 수 있기 때문이다).
public fun max(a: Int, b: Int): Int {
return if (a > b) {
a
} else {
b
}
}
public fun max(a: Int, b: Int) = if (a > b) a else b
default parameter: 값이 들어오지 않으면 default로 설정한 값으로 들어간다.
public fun max(a: Int = 3, b: Int): Int {
return a + b
}
named argument : 넣고싶은 파라미터에 명시적으로 넣어줄 수 있다.
가변인자: 같은 타입의 여러 파라미터 받기 vararg
public static void printAll(String... strings) {
for (String str : strings){
System.out.println(str);
}
}
fun printAll(vararg strings: String) {
for (str in strings){
println(str);
}
}
- 배열을 printAll에 삽입한다면 *을 통해 값을 꺼내어 넣어줘야 한다.
9. 클래스를 다루는 방법
9-1. class와 property
- 프로퍼티 = 필드 + getter + setter
- 필드(var, val)만 만들면 getter, setter를 자동으로 만든다
9-2. 생성자와 init
- init : 초기화 시점에 한 번 실행한다. 보통 검증할 때 사용한다.
- constructor(파라미터)로 새로운 생성자를 추가할 수 있다. (부 생성자)
- 최종적으로 this로 호출해야 한다.
- 부 생성자보다는 default parameter를 권장한다.
- 부 생성자보다 정적 팩토리 메소드를 사용한다.
- 실무에서 부 생성자를 잘 사용하지 않는다.
9-3. custom getter/setter
두 가지 방법이 존재한다.
- 함수처럼 만드는 방법 (객체의 속성이 아니라면)
fun isAdult(): Boolean {
return this.age >= 20
}
- 프로퍼티 처럼 만드는 방법 (객체의 속성이라면)
val isAdult: Boolean
get() {
return this.age >= 20
}
val isAdult: Boolean
get() = this.age >= 20
9-4. backing-field 사용
주생성자 getter를 변환해서 반환할 수 있다. field
class Person(
name: String, // val가 빠짐
var age: Int
) {
val name: String = name
get() = field.uppercase()
}
하지만, 다음과 같이 구현할 수 있기 때문에 개발자에 따라 자주 사용되지 않기도 하다.
val name: String = get() = this.name.uppercase()
10. 상속을 다루는 방법
10-1. 추상클래스
예제로 알아보자. Animal / Cat, Penguin
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("고양이가 사뿐사뿐 걸어가~")
}
override val legCount: Int {
get() = super.legCount + this.wingCount
}
}
- 추상 프로퍼티가 아니라면 상속받을 때, open을 붙여야 한다.
10-2. 인터페이스
펭귄이 인터페이스에 대한 의존을 가지는 예시를 알아보자
interface Flyable {
fun act() {
println("파닥파닥")
}
}
class Penguin(
species: String
) : Animal(species, 2), Flyable {
private val wingCount: Int = 2
override fun move() {
println("고양이가 사뿐사뿐 걸어가~")
}
override val legCount: Int {
get() = super.legCount + this.wingCount
}
// 인터페이스 추가됨
override fun act() {
super<Flyable>.act()
}
}
11. 접근 제어를 다루는 방법
11-1. 가시성 제어
종류
- public : 모든 곳 접근 가능
- protected : 선언된 클래스, 하위 클래스만 접근 가능 (자바의 같은패키지 개념X)
- internal : 같은 모듈에서만 접근 가능
- 모듈: 한 번에 컴파일 되는 Kotlin 코드
- private : 선언된 클래스 내에서만
11-2. 접근 제어
- 생성자 : 가시성 제어를 넣기 위해 constructor 를 명시적으로 적어주어야 한다.
- 프로퍼티 : getter/setter 를 한번에 부여하느냐, setter에만 가시성을 부여하느냐로 나뉠 수 있다.
class Car(
internal val name: String, // 한 번에 접근 지어서 정하기
_price: Int
) {
var price = _price
private set // 가시성 적용
}
12. object 키워드를 다루는 방법 (자바의 static)
12-1. companion object (자바의 static 함수와 변수)
object를 통해 static 처럼 사용할 수 있다.
class Person private constructor(
private val name: String,
private val age: Int
) {
companion object {
private const val MIN_AGE = 0
fun newBaby(name: String): Person {
return Person(name, MIN_AGE)
}
}
}
이름도 지정할 수 있다.
companion object Factory{
private const val MIN_AGE = 0
fun newBaby(name: String): Person {
return Person(name, MIN_AGE)
}
}
자바에서 사용한다면 @JvmStatic을 붙여야 한다!
12-2. 싱글톤: object
싱글톤 : 클래스의 인스턴스가 단 하나이다.
object Singleton {
var a: Int = 0
}
12-3. 익명클래스(일회성)
자바에서는 익명클래스를 만들어 함수의 인자로 던질 수 있었다. (@Override를 한 뒤에 새롭게 정의한 것을 한번 사용)
fun main() {
moveSomething(object : Moveable {
override fun move() {
println("움직인다~")
}
override fun fly() {
println("난다~")
}
})
}
private fun moveSomething(moveable: Movable) {
movable.move()
movable.fly()
}
13. 중첩 클래스를 다루는 방법
실무 서버 개발에서 많이 사용된 부분은 아니다. 간혹 사용된다.
중첩클래스
- static을 사용하는 중첩클래스
- static을 사용하지 않는 중첩클래스 (클래스 안에 클래스를 만들 때는 static 클래스를 사용하자. by 이펙티브자바)
- 내부 클래스
- 지역 클래스
- 익명 클래스
코틀린에서는 위의 Guide를 충실히 따른다
즉, 기본적으로 바깥 클래스를 참조하지 않는다. 바깥 클래스를 참조하고 싶다면 inner 키워드를 추가한다.
class House(
var address: String,
var livingRoom: LivingRoom = LivingRoom(10.0)
) {
class LivingRoom(
private var area: Double
)
}
14. 다양한 클래스를 다루는 방법
다양한 클래스가 존재한다.
- Data class
- Enum class
- Sealed Class, Sealed Interface
하나씩 살펴보자
14-1. Data class (DTO)
계층간의 데이터를 전달하기 위한 DTO는 다음과 같은 내용이 필요하다.
- 데이터(필드)
- 생성자, getter
- equals, hashCode
- toString
Data class PersonDto(
val name: String,
val age: Int,
)
14-2. Enum class
다음과 같은 특징을 가진다
- 클래스를 상속받을 수 없다.
- 인터페이스는 구현할 수 있으며 각 코드가 싱글톤이다.
enum class Country (
private val code: String
) {
KOREA("KO")
AMERICA("US")
;
}
만약 다음과 같이 코드가 많아진다면? else 로직 처리가 애매해진다.
fun handleCountry(JavaCountry country) {
if (country == JavaCountry.KOREA) {
// 로직 처리
}
if (country == JavaCountry.AMERICA) {
// 로직 처리
}
}
when으로 극복한 경우, else를 사용하지 않아도 된다.
fun handleCountry(JavaCountry country) {
when (country) {
Country.KOREA -> TODO()
Country.AMERICA -> TODO()
}
}
14-3. Sealed Class, Sealed Interface
어디에 사용될까?
- 추상화가 필요한 Entity or DTO에 사용한다.
개념을 알아보자
sealed : 봉인된
Sealed Class는 상속이 가능하도록 추상클래스를 만들까하지만, 외부에서는 이 클래스를 상속받지 않도록 함 → 하위 클래스 봉인
- 하위 클래스는 같은 패키지에 존재한다
- 컴파일 타임 때 하위 클래스의 타입을 모두 기억한다. 런타임때 클래스 타입이 추가될 수 없다.
- Enum과 다른점
- 클래스를 상속받을 수 있다.
- 하위 클래스는 멀티 인스턴스가 가능하다.
예제로 살펴보자
abstract와 기능이 비슷해보인다.
sealed class HyundaiCar(
val name: String,
val price, Long,
)
class Avante : HyundaiCar("아반떼", 1_000L)
class Sonata : HyundaiCar("소나타", 2_000L)
class Grandeur : HyundaiCar("그렌저", 3_000L)
private fun handleCar(car: HyundaiCar) {
when (car) {
is Avante -> TODO()
is Sonata -> TODO()
is Grandeur -> TODO()
}
}
15. 배열과 컬렉션을 다루는 방법
15-1. 배열
잘 사용하지 않는다. 이펙티브 자바에서도 배열보다 리스트를 사용하라고 말한다.
val array = arrayOf(100, 200)
for ((idx, value) in array.withIndex()){
println("${idx} ${value}")
}
15-2. Collection - List, Set, Map
컬렉션을 만들어 줄 때, 불변인지, 가변인지 설정해야 한다. 불변이라 하더라도 값 수정은 가능하다(삽입, 제거는 불가능).
List
우선 불변 리스트로 만들고, 꼭 필요한 경우에만 가변 리스트로 바꾸자.
// 1. 불변
val numbers = listOf(100, 200) // 값을 추론할 수 있기에 <Int> 생략가능
val emptyList = emptyList<Int>() // 어떤 값이 들어올 지 모르기에 명시
// 2. 가변
val numbers = mutableListOf(100, 200)
numbes.add(300)
numbers.get(0)
numbers[0] // 위와 같다.
// withIndex 사용이 가능하다.
for ((idx, value) in array.withIndex()){
println("${idx} ${value}")
}
set과 map
- set의 사용법은 List와 같다.
- map
val oldMap = mutableMapOf<Int, String>() // 타입 명시
oldMap[1] = "MONDAY"
oldMap[2] = "TUESDAY"
// 아래와 같이 생성도 가능하다
mapOf(1 to "MONDAY", 2 to "TUESDAY")
// 사용법 1
for (key in oldMap.keys) {
println(oldMap[key])
}
// 사용법 2
for ((key, value) in oldMap.entries) {
}
15-3. 컬렉션의 null 가능성
? 위치에 따라 의미가 다르다.
- List<Int?>
- List<Int>?
- List<Int?>?
16. 다양한 함수를 다루는 방법
16-1. 확장함수 : 함수를 사용하는 새로운 방법
fun String.lastChar(): Char { // 확장하려는 클래스: String
return this[this.length - 1]
}
val str = "ABC"
println(str.lastChar()) // C
- 멤버함수와 확장함수 둘 다 선언되어있다면 멤버함수가 우선적으로 호출된다.
- 원본 클래스의 private, protected 멤버 접근이 안된다.
- 현재 타입을 기준으로 호출된다.
16-2. infix함수 : 함수를 호출하는 새로운 방법
downTo, step도 함수이다.
- 변수.함수이름(argument)
- 변수 함수이름 argument (infix함수)
infix fun Int.add2(other: Int): Int {
return this + other
}
3.add2(4)
3 add2 4 // 둘 다 가능
17. 람다를 다루는 방법
17-1. 람다 만들기
두 가지 방법이 있다.
// 1
val isApple = fun(fruit: Fruit): Boolean {
return fruit.name == "사과"
}
// 2
val isApple = { fruit: Fruit -> fruit.name == 사과 }
// 파라미터타입 -> 반환타입을 명시적으로했을 때
val isApple: (Fruit) -> Boolean = fun(fruit: Fruit): Boolean {
return fruit.name == "사과"
}
17-2. 람다 호출방법
// 1
isApple(Fruit("사과", 1000))
// 2
isApple.invoke(Fruit("사과", 1000))
예제
private fun filterFruits(
fruits: List<Fruit>, filter: (Fruit) -> Boolean
): List<Fruit> {
val results = mutableListOf<Fruit>()
for (fruit in fruits) {
if (filter(fruit)) {
results.add(fruit)
}
}
return results
}
// { fruit: Fruit -> fruit.name == 사과 }를
// { it.name == 사과 } 로 표현
filterFruits(fruits, { it.name == 사과 })
- fruit를 명시해주는게 좋다!
Closure
- 코틀린에서 Closure를 사용하여 non-final 변수도 람다에서 사용할 수 있다. (자바는 final 변수만 가능하다)
- 코틀린에서는 람다가 시작하는 지점에 참조하고 있는 변수들을 모두 포획하여 그 정보를 가지고있다.
var fruitName = "바나나"
fruitName = "수박"
filterFruits(fruits) { it.name == fruitName }
18. 컬렉션을 함수형으로 다루는 방법
다음 내용은, 이러한 내용이 있구나 정도로 알고 필요할 때 찾아서 사용해보자.
18-1. filter
private fun filterFruits(
fruits: List<Fruit>, filter: (Fruit) -> Boolean
): List<Fruit> {
return fruits.filter(filter)
}
- filter / filterIndexed / map / mapIndexed / mapNotNull
- all / none / any
- count / sortedBy / distinctBy
18-2. List를 Map으로 변경해보자
val map: Map<String, List<Fruit>> = fruits.groupBy { fruit -> fruit.name }
- 과일이름 : key
- 해당과일List : value
val map: Map<String, Fruit> = fruits.associateBy { fruit -> fruit.id }
- id : key
- 과일 : value
19. scope function (let, run, also, apply, with)
람다를 사용해 일시적인 영역을 만들고 코드를 더 간결하게 만들거나, method chaning에 활용하는 함수를 scope function이라고 한다.
fun printPerson(person: Person?) {
person?.let {
println(it.name) // it는 person임
println(it.age)
}
}
확장함수
- let / run : 람다를 받아 람다 결과를 반환한다.
- let : it 사용 (생략 불가능, 이름변경 가능)
- run : this 사용 (생략 가능)
- also / apply : 객체 그 자체를 반환한다.
- also : it 사용
- apply : this 사용
// value에 age가 들어온다.
val value1 = person.let {
it.age
}
val value2 = person.run {
this.age
}
// value에 person이 들어온다.
val value3 = person.also {
it.age
}
val value4 = person.apply {
this.age
}
확장함수가 아닌 with
- with : this 사용 / 생략가능
with(person) {
println(name)
println(this.age)
}
- apply : Test Fixture를 만들 때 활용할 수 있다.
fun createPerson(
name: String,
age: Int,
hobby: String,
): Person {
return Person (
name = name,
age = age,
).apply {
this.hobby = hobby
}
}
'공부 > Kotlin' 카테고리의 다른 글
JUnit5에서 Kotest로 마이그레이션 하며 겪은 트러블슈팅들 (1) | 2022.10.20 |
---|---|
코틀린에서 JPA를 사용할 때 고려할 점(SETTER, 생성자 안의 프로퍼티, data class) (3) | 2022.08.28 |
왜 Kotest를 사용해야 할까? (2) | 2022.07.26 |
Kotest의 테스트스타일 10가지 (0) | 2022.07.26 |
11장 DSL 만들기 (0) | 2022.07.18 |
댓글