코틀린 기초
코틀린 기초
코틀린 객체 지향 프로그래밍
함수형 프로그래밍
Kotlin Basic
사용 라이브러리
testImplementation("org.assertj:assertj-core:3.26.3")
testImplementation("org.hamcrest:hamcrest:2.2")Null 허용 타입
변수가 null 값을 갖지 못하게 하려면
안전 호출 연산자(?.)나 엘비스 연산자(?:)와 결합해서 사용하자.
👉🏻 val 변수의 영리한 타입 변환(smart cast)
널 할당이 불가능한 문자열 타입으로 영리한 변환 가능
val p = Person(first = "North", middle = null, last = "West")
if (p.middle != null) {
val middleNameLength = p.middle.length
}👉🏻 var 변수가 널 값이 아님을 단언하기 !!
var 변수는 String 타입으로 영리한 타입 변환이 불가능
널 아님 단언 연산자(
!!, not-null assertion operator)로 널 아님을 단언할 수 있지만, 코드 스멜이다변수가 널이 아닌 값으로 다뤄지도록 강제하고 해당 변수가 널이라면 예외를 던진다
널 값에 이 연산자를 사용하는 것은 코틀린에서 NPE를 만날 수 있는 몇 가지 상황 중 하나
가능하면 사용하지 않도록 노력하자.
👉🏻 var 변수에 안전 호출 연산자 사용하기 ?.
이 상황에서 안전 호출(
?., safe call)를 사용하는 것이 좋다.결과 타입은 Type? 형태이고, null 이면 null을 반환
👉🏻 var 변수에 안전 호출 연산자와 엘비스 연산자 사용하기 ?:
?:: 왼쪽 식의 값을 확인해서 해당 값이 널이 아니면 그 값을 리턴, 널이라면 오른쪽 값을 리턴
👉🏻 안전 타입 변환 연산자 as?
타입 변환이 올바르게 동작하지 않은 경우 ClassCastException이 발생하는 상황을 방지
명시적 타입 변환
코틀린은 자동으로 기본 타입을 더 넓은 타입으로 승격하지 않는다. Int -> Long (X)
더 작은 타입을 명시적으로 변환하려면 toInt, toLong 등 구체적인 변환 함수를 사용하자.
사용 가능한 타입 변환 메소드
toByte(): Byte
toChar(): Char
toShort(): Short
toInt(): Int
toLong(): Long
toFloat(): Float
toDouble(): Double
중위(infix) 함수
코틀린에는 자바처럼 내장 거듭제곱 연산자가 없다.
👉🏻 시그니처의 확장 함수를 정의
👉🏻 중위 연산자 infix 정의
Pair 인스턴스
중위(infix) to 함수로 Pair 클래스의 인스턴스를 생성할 수 있다.
코틀린은 Pair 인스턴스의 리스트로부터 맵을 생성하는 mapOf와 같은 맵 생성을 위한 최상위 함수 몇 가지를 제공
Pair는 first, second 이름의 두 개의 원소를 갖는 데이터 클래스이다.
Pair 클래스는 두 개의 인자를 받는 생성자를 사용해서 Pair 클래스를 생성할 수 있지만, to 함수를 사용하는 것이 일반적이다.
👉🏻 mapOf 인자인 pair를 생성하기 위해 to 함수 사용하기
⚠️ Pair는 데이터 클래스이므로 구조 분해를 통해서만 개별 원소에 접근할 수 있다.
객체 지향 프로그래밍
const와 val의 차이
런타임보다 컴파일 타임에 변수가 상수임을 나타내야 한다
컴파일 타임 상수에 const 변경자를 사용하자.
val 키워드는 변수에 한 번 할당되면 변경이 불가능함을 나타내지만 이러한 할당은 실행 시간에 일어난다.
👉🏻 컴파일 타임 상수 정의하기
const가 val 키워드를 대체하는 것이 아니라 반드시 같이 쓰여야 한다.
사용자 정의 획득자와 설정자 생성하기
코틀린은 모든 것이 기본적으로 public 이다.
데이터 은닉 원칙을 침해하는 딜레마를 클래스에서 필드는 직접 선언할 수 없도록 하는 방법으로 해결하였다.
👉🏻 사용자 정의 획득자와 설정자
속성 정의 문법
데이터 클래스 정의하기
클래스를 정의할 때 data 키워드로
equals,hashCode,toString등을 갖춘 엔티티를 나타낼 수 있다.
코틀린은 데이터를 담는 특정 클래스의 용도로 나타내기 위해 data 키워드를 제공한다.
.
data 키워드로 생성되는 함수들..
👉🏻 주 생성자에 선언된 속성 바탕으로 생성되는 equals, hashCode 함수
👉🏻 클래스의 속성 값을 보여주는 toString 함수
👉🏻 copy 함수
원본과 같은 속성 값으로 시작해서 copy 함수에 제공된 속성 값만을 변경해 새로운 객체를 생성하는 인스턴스 메소드
copy 함수는 얕은 복사를 수행
👉🏻 구조 분해를 위한 component 함수
지원 속성 기법
클래스의 속성을 클라이언트에게 노출하고 싶지만, 해당 속성을 초기화하거나 읽는 방법을 제어하고 싶다면
같은 타입의 속성을 하나 더 정의하고 사용자 정의 획득자와 설정자를 이용해 원하는 속성에 접근하자.
👉🏻 생성 즉시 초기화되지 않도록 message 속성과 같은 타입의 널 허용 _message 속성을 추가
.
👉🏻 lazy 대리자 함수로 쉽게 지연로딩을 구현
연산자 중복(Overloading)
코틀린의 연산자 중복(overloading) 매커니즘을 사용해서 +, * 등의 연산자와 연관된 함수를 구현할 수 있다.
👉🏻 함수 재정의
많은 연산자가 코틀린에서 함수로 구현되어 있고, 기호를 사용하면 해당 연산자와 연관된 함수에 처리를 위임
모든 연산자 함수 재정의 시
operator키워드는 필수(equals 제외)
.
👉🏻 확장 함수
자신이 작성하지 않은 클래스에 연산과 관련 함수를 추가하고 싶다면 확장 함수를 사용할 수 있다.
Complex 클래스의 이와 같은 기존 함수에 연산을 위임하는 확장 함수
지연 초기화 lateinit
널 비허용으로 선언된 클래스 속성은 생성자에서 초기화되어야 하지만, 속성에 할당할 값의 정보가 충분하지 않다면
나중 초기화를 위해
lateinit을 사용할 수 있다.단, 의존성 주입의 경우 유용하지만, 일반적으로는 지연 평가 같은 대안을 권장
👉🏻 스프링 컨트롤러 테스트
.
lateinit 변경자는 클래스 몸체에서만 선언되고 사용자 정의 획득자와 설정자가 없는 var 속성에서만 사용할 수 있다.
lateinit 사용 가능한 속성 타입은 널 할당이 불가능한 타입
기본 타입에는 lateinit 사용 불가
lateinit 추가 시 해당 변수가 처음 사용되기 전에 초기화 가능
사용 전 초기화에 실패하면 예외 발생
👉🏻 lateinit 속성 동작 방식
equals 메소드 구현
논리적으로 동등한 인스턴스인지 확인 가능한 equals 재정의를 위해
레퍼런스 동등 연산자(
==), 안전 타입 변환 함수(as?), 엘비스 연산자(?:)를 다 같이 사용
👉🏻 Any class
equals 문법에서 equalsa 구현은 반사성(reflexive), 대칭성(symmetirc), 추이성(transitive), 일관성(consistent)이 있어야 하고, 널도 적절하게 처리할 수 있어야 한다.
.
👉🏻 equals 구현의 좋은 예 (KotlinVersion 클래스의 equals)
equals 함수가 재정의되면 hashCode 함수도 재정의
.
👉🏻 equals, hashCode 구현해 보기
싱글톤 생성하기
클래스 하나당 인스턴스를 하나만 만드려면 class 대신 object 키워드를 사용하자.
싱글톤 디자인 패턴은 특정 클래스의 인스턴스를 오직 하나만 존재하도록 메커니즘을 정의
클래스의 모든 생성자를 private 로 정의
필요하다면 해당 클래스를 인스턴스화하고 그 인스턴스 레퍼런스를 리턴하는 정적 팩토리 메소드를 제공
👉🏻 코틀린에서 싱글톤 선언
👉🏻 싱글톤을 위해 object를 디컴파일한 코드
Nothing에 관한 야단법석
코틀린의 Nothing 클래스
Nothing의 인스턴스는 존재하지 않는다.결코 존재할 수 없는 값을 나타내기 위해
Nothing을 사용할 수 있다.
.
Nothing 클래스는 아래 두 가지 상황에서 사용될 수 있다
👉🏻 코틀린에서 예외 던지기
리턴 타입을 반드시 명시해야 하지만 리턴하지 않으므로 리턴 타입은 Nothing
👉🏻 변수에 널을 할당할 때 구체적인 타입을 명시하지 않는 경우
x에 대한 다른 정보가 없으므로 추론된 x의 타입은
Nothing?코틀린에서 Nothing 클래스는 실제로 다른 모든 타입의 하위 타입
함수형 프로그래밍
알고리즘에서 fold 사용하기
fold 함수를 사용해 시퀀스나 컬렉션을 하나의 값으로 축약할 수 있다.
fold 함수는 배열 또는 반복 가능한 컬렉션에 적용할 수 있는 축약 연산
fold는 두 개의 인자를 받는다.첫 번째는 누적자의 초기값
두 번째는 두 개의 인자를 받아 누적자를 위해 새로운 값을 리턴하는 함수
👉🏻 fold를 사용해 정수의 합 계산하기
👉🏻 fold를 사용해 팩토리얼 구현하기
👉🏻 fold를 사용해 피보나치 수 계산하기
reduce 함수를 사용해 축약하기
비어 있지 않는 컬렉션의 값을 축약하고 싶지만 누적자의 초기값을 설정하고 싶지 안다면,
fold 대신 reduce 연산을 사용
reduce 함수는 fold 함수와 사용 목적도 같고 비슷하다.
큰 차이점은
reduce함수에는 누적자의 초기값 인자가 없는 것따라서 누적자의 초기값은 컬렉션의 첫 번째 값으로 초기화
reduce 함수는 누적자를 컬렉션의 첫 번째 값으로 초기화할 수 있는 경우에만 사용 가능
인자와 함께 함수가 호출되면 첫 번째 인자가 누적자로 초기화되며 다른 값들은 한 번에 하나씩 누적자에 더해진다.
인자 없이 호출될 경우 예외를 던진다.
👉🏻 reduce로 구현한 sum 함수
꼬리 재귀 적용하기
재귀 프로세스를 실행하는 데 필요한 메모리를 최소화하고 싶다면,
tailrec키워드를 추가하자.
꼬리 재귀로 알려진 접근법은 콜 스택에 새 스택 프레임을 추가하지 않게 구현하는 특별한 종류의 재귀이다.
👉🏻 꼬리 호출 알고리즘을 사용하는 팩토리얼 구현
팩토리얼 연산의 누적자 역할을 하는 두 번째 인자가 필요
마지막 평가 식은 더 작은 수와 증가된 누적자를 이용해 자신 스스로를 호출
tailrec 변경자를 적용할 수 있는 함수의 자격 요건
해당 함수는 반드시 수행하는 마지막 연산으로서 자신을 호출해야 한다.
try/catch/finally 블록 안에서는 tailrec을 사용할 수 없다.
오직 JVM 백엔드에서만 꼬리 재귀가 지원된다.
Last updated