섹션3 코틀린에서의 OOP
9강 코틀린에서 클래스를 다루는 방법
Kotlin에서 기본 클래스가 마치 Java의 record와 비슷하게 동작.
var이면 자동으로 getter, setter 생성하고 val이면 자동으로 getter를 생성함
생성자 키워드 constructor는 생략 가능하며, 클래스 헤더에 val 및 var 키워드 생성시 필드 또한 자동으로 생성해줌
특이한 건 init 메서드
가 존재하는데 생성자 호출시 실행하여 validation할 때 자주 사용됨
생성자를 추가로 생성하고 싶은 경우 constructor 키워드를 새로 만들면됨
class Person(
val name: String,
var age:Int
) {
// 생성자가 호출되는 시점에 호출됨
init {
if (age <= 0) {
throw IllegalArgumentException("나이는 ${age}일 수 없습니다.")
}
}
constructor(name: String) : this(name, 3) {
println("첫 번째 부 생성자")
}
constructor() : this("홍길동") {
println("두 번째 부 생성자")
}
}
하지만 Kotlin에서는 기본 값을 사용하는 걸 추천함
Custom Getter
아래 2개의 메서드는 같은 의미. isAdult라는 값 자체를 만들고, get으로 isAdult를 요청했을 경우 아래를 반환
프로퍼티 인것처럼 접근하는지, 함수인 것 처럼 접근하는지를 확인하고 아래 2가지 방식 중 하나로 결정
fun isAdult(): Boolean {
return this.age >= 20
}
val isAdult: Boolean
get() = this.age >= 20
backing field
Getter를 자동으로 생성해주지 않고, 커스텀한다면?
Kotlin에서는 get할 때 person.name
와 같이 접근함
만약 get을 custom 하고싶다고 해보자.
생성자 헤더에서 val 키워드를 제거하고, 블록 내부에 val name = name으로 선언 후 아래와 같은 상황이다.
val name = name
get() = name.uppercase()
근데, 오류가 난다. 그 이유는 외부에서 name을 호출 했을 때 name은 다시 get을 호출을 무한반복하게 됨
그러므로 name.uppercase()
가 아닌 field.uppercase
로 접근해야함
val name = name
get() = field.uppercase()
자기자신을 가리키는, 보이지 않는 Field 이므로 backing field라고 부름
위 상황에서 실제로 저장은 소문자로 되고, get() 할 때는 대문자로 반영이됨
하지만 이런식으로는 실제로 많이 사용하지 않고, 아래와 같이 실제로는 없는 값이지만 외부에는 있는 것처럼 보일 때는 아래와 같이 쓰임
val uppercaseName: String
get() = this.name.uppercase()
10강 코틀린에서 상속을 다루는 방법
추상 클래스를 상속할 때는 문제가 없지만 일반 클래스를 상속할 때는 class
에 open
을 꼭 붙여줘야 함
오버라이드를 할 때 자바에서는 @Override
를 사용했지만, Kotlin에선 메서드 키워드에 override
를 사용함
상위 클래스에서 정의한 필드의 getter를 오버라이딩 하고 싶으면 상위 클래스의 필드에 open을 붙여줘야함
상위 클래스를 설계할 때 생성자 또는 초기화 블록에 사용되는 프로퍼티는 open을 피해야 함
open 역할: override를 열어준다
11강 코틀린에서 접근 제어를 다루는 방법
자바의 접근 제어자
접근 제어자 | 설명 |
---|---|
public |
모든 곳에서 접근 가능 |
protected |
같은 패키지 또는 하위 클래스에서만 접근 가능 |
default |
같은 패키지에서만 접근 가능 |
private |
선언된 클래스 내에서만 접근 가능 |
코틀린의 접근 제어자
접근 제어자 | 설명 |
---|---|
public |
모든 곳에서 접근 가능 |
protected |
선언된 클래스 또는 하위 클래스에서만 접근 가능 (같은 패키지가 빠졌다고 생각) |
internal |
같은 모듈에서만 접근 가능 |
private |
선언된 클래스 내에서만 접근 가능 |
Util Method
Java
추상 클래스로 생성 후 생성자를 막아 인스턴스화 할 수 없게하고, 메서드를 만들어 사용
public abstract class JavaStringUtils {
public JavaStringUtils() {
}
public boolean isDirectoryPath(String path) {
return path.endsWith("/");
}
public static void main(String[] args) {
StringUtilsKt.isDirectoryPath("/");
}
}
Kotlin
그냥 파일에 선언 후 import하면 끝
// StringUtils.kt
package lec11
fun isDirectoryPath(path: String): Boolean {
return path.endsWith("/")
}
// Main Class
package lec11
class Lec11Main {
fun main() {
isDirectoryPath("/")
}
}
12강 코틀린에서 object 키워드를 다루는 방법
Java에서 static 클래스를 Kotlin에선 아래와 같이 companion object
표현
companion object Factory : Log {
private const val MIN_AGE = 1
fun newBaby(name: String): Person {
return Person(name, MIN_AGE)
}
@JvmStatic
override fun log() {
println("나는 Person 클래스 동행 객체에요")
}
}
외부에선 아래와 같이 적용 가능하며 마찬가지로 static import와 유사하게 가능
Person.newBaby("hello")
Java에서 컴패니언 사용하려면 @JvmStatic을 붙여야함
싱글톤
object 키워드 쓰면 끝
익명클래스 구현시
moveSomething(object : Moveable {
override fun move() {
println("움직인다")
}
override fun fly() {
println("난다")
}
})
13강 코틀린에서 중첩 클래스를 다루는 방법
Effecttive Java에서는 내부 클래스를 생성하는 것보다, static을 사용하는 것을 권장함
Kotlin에서는 이 권장사항을 따라 자동으로 static과 같이 동작함
inner는 권장하지는 않음
Java
클래스 안의 static 클래스 | 바깥 클래스 참조 없음 권장되는 유형 |
---|
Kotlin
클래스 안의 클래스 | 바깥 클래스 참조 없음 권장되는 유형 |
---|
14강 코틀린에서 다양한 클래스를 다루는 방법
data class
Kotlin에서 data 키워드를 붙여주면 Java에서 toString
, equals
, hashCode
을 쓰는 것과 동일하다
사실상 namedQuery를 사용하면 Builder 또한 사용 가능
enum class
Java와 Kotlin은 동일. 하지만 when을 쓰면 강력해짐
Java
private static void handleCountry(JavaCountry country) {
if (country == JavaCountry.KOREA) {
// 로직 처리
}
if (country == JavaCountry.AMERICA) {
// 로직 처리
}
}
Kotlin
fun handleCountry(country: Country) {
when (country) {
KOREA -> TODO()
AMERICA -> TODO()
}
}
else 처리를 하지 않아도됨. 이유는? 어차피 지금 Country에는 KOREA, AMERICA 밖에 없기 떄문에, 별도의 예외처리를 하지 않아도 됨.
코틀린에서의 FP
15강 코틀린에서 배열과 컬렉션을 다루는 방법
불변 가변
Kotlin은 불변/가변을 지정해 주어야 함
Mutable이 Prefix면 가변, 없다면 불면
불변일 때 컬렉션 속 요소를 바꿀 수는 없음. 단 요소의 필드는 수정 가능.
MutableList일 때 Person을 추가, 삭제는 할 수 있음. 단 MutableList.remove(0) 불가능
listOf()
메서드로 리스트를 만들 수 있으며 타입이 추론 가능함
컬렉션에서 withIndex()
메서드를 사용하면 인덱스 번호도 같이 갖고 옴
컬렌션의 null 가능성
List<Int?>
: 리스트에 null 들어갈 수 있음, 리스트는 절대 null이 아님
List<Int>?
: 리스트에 null 들어갈 수 없음, 리스트는 null일 수 있음
List<Int?>?
: 리스트에 null이 들어갈 수 있고, 리스트가 null일 수 있음
Java
와 Kotlin
은 컬렉션에서 null 값을 다루는 과정이 다르기 때문에 방어 로직을 짜는게 좋음
코틀린에서 다양한 함수를 다루는 방법
확장함수
나오게된 배경
Java와 100% 호환성을 원해서 나오게 됨
기존 Java 코드 위에 자연스럽게 코틀린 코드를 추가할 수 없을까?
Java로 만들어진 라이브러리를 유지보수, 확장할 때 Kotlin 코드를 덧붙이기 위해
어떤 클래스 안에 있는 메서드처럼 호출할 수 있지만, 함수는 밖에 만들 수 있게 해보자
// String 클래스를 확장한 함수
fun String.lastChar(): Char {
return this[this.length - 1]
}
위에서 this 는 String 클래스를 확장한 것이므로 String을 가리킴
String 클래스를 확장한 함수
만약 확장함수와 멤버함수의 메서드 이름이 같다면 멤버함수가 우선적으로 호출된다.
- 확장함수는 원본 클래스의 Private, protected 멤버 접근이 되지 않음
- 확장함수는 현재 타입을 기준으로 호출됨
Java에서는 Kotlin의 화강함수가 정적 메서드처럼 사용할 수 있음
infix(중위) 함수
fun Int.add(other: Int): Int {
return this + orther
}
infix fun Int.add2(other: Int): Int {
return this + orther
}
// class.add2 4
// 위와 같이 사용 가능
inline 함수
함수가 호출되는 대신, 함수를 호출한 지점에 함수 본문을 그대로 복붙하고 싶은 경우
지역함수
함수 안에 함수를 선언할 수 있음
하지만 depth가 깊어지고, 실무에서 잘안씀, 안쓰는게 좋음
17강 코틀린에서 람다를 다루는 방법
이름 없는 함수처럼 사용 가위는
// 이름 없는 함수처럼 사용 가능, 원래는 fun isFruit()와 같음
val isApple = fun(fruit: Fruit):Boolean {
return fru능it.name == "사과"
}
val isApple2 = { fruit: Fruit -> fruit.name == "사과"}
위는 반환 타입이 추론 가능하기 때문에 생략했지만 실제로는
val isApple:(Fruit) -> Boolean = fun(fruit: Fruit):Boolean {
return fruit.name == "사과"
}
이것과 같음.
Kotlin
은 함수를 파라미터로 바로 넣을 수 있음
fun main() {
val isApple: (Fruit) -> Boolean = fun(fruit: Fruit): Boolean {
return fruit.name == "사과"
}
filterFruits(fruits, isApple)
}
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
}
18강 코틀린에서 컬렉션을 함수형으로 다루는 방법
private fun filterFruits(
fruits: List<Fruit>, filter: (Fruit) -> Boolean
){
}
filter
메서드를 그대로 받아서 사용: fruits.filter(filter)
map
형변환: val map = fruits.map { fruit -> fruit.price }
all
전부 조건을 만족하면 true: fruits.all {fruit -> fruit.name == "사과" }
none
전부 조건을 만족하지 않으면 true: fruits.none {fruit -> fruit.name == "사과" }
any
하나라도 만족하면 true: fruits.any {fruit -> fruit.name == "사과" }
sortedBy
원하는 조건대로 오름차순 정렬: fruits.sortedBy { fruit: Fruit -> fruit.price }
sortedByDescending
원하는 조건대로 내림차순 정렬: fruits.sortedByDescending { fruit: Fruit -> fruit.price }
counted
List의 size와 같음
distinctBy
변형된 값을 기준으로 중복을 제거함
// 이름을 기준으로 중복을 제거하고 이름만 남기는 로직
fruits.distinctBy { fruit -> fruit.name }
.map { fruit -> fruit.name }
first
첫번째 값을 가져옴(무조건 null이 아니어야함)
last
마지막 값을 가져옴(무조건 null이 아니어야함)
groupBy
과일이름 -> List<과일>
Map 이 필요할 때
val map: Map<String, List<Fruit>> = fruits.groupBy { fruit -> fruit.name}
associateBy
과일 하나가 들어가는 경우. Map안에 List가 아닌, 단일 객체로 사용할 때 사용
flatMap, flatten
Java와 같은 평탄화 작업
'Kotlin > 입문편' 카테고리의 다른 글
Java 개발자를 위한 Kotlin 입문 2. 코틀린에서 코드를 제어하는 방법 (0) | 2024.04.23 |
---|---|
Java 개발자를 위한 Kotlin 입문 1. 코틀린에서 변수와 타입, 연산자 (0) | 2024.04.21 |