Kotlin 다시 보기 (1)
‘빅 너드 랜치의 코틀린 프로그래밍’ 을 읽고 정리
타입
코틀린 타입 특징
정적 타입 시스템
- 소스 코드에 정의된 타입을 컴파일러가 알고 있다.
프로그램을 컴파일하기 전에 타입 체킹 : 정적 타입 체킹
built-in, 내장 타입
String
Char
Boolean
Int
Double
List - 컬렉션 타입
Set - 컬렉션 타입
Map - 컬렉션 타입
변수
val
: read-only, 변경 불가능, 읽기 전용var
: 값이 변경 될 수 있음, writeable 쓰기 가능, 가변성
타입 추론
- 코틀린 컴파일러가 타입을 추론 해줌
- 따라서, 타입을 생략 가능, 컴파일러가 소스 코드의 타입을 알고 있음
- 코틀린은 기본 타입을 포함해서, 모든 타입이 객체다.
- 코틀린 컴파일러가 자바의 기본 타입과 가장 유사한 것과 매핑
컴파일 시점 상수(constant)
- val 변수는
상수(constant)
가 아니다. const val MAX_LIMIT = 50000
- 상수는 프로그램 실행 전에 생성 및 초기화 됨
- 초기화된 값을 절대로 변경하지 않는 값
- 프로그램 실행 전 컴파일러가 알 수 있어야 함으로, ‘built-in 타입’ 중 하나가 되어야 함
- String, Int, Double, Float, Boolean, Char
- 모든 함수 외부에 정의 되어야 함, 컴파일 될때 값이 지정되야 함
- 상수와 달리, 변수들은 runtime에 생성되어 값이 지정된다.
코틀린의 자바 기본 타입
자바는 참조 타입과 기본 타입 2개가 존재
기본 타입: 내장, 소문자
int
참조 타입: 별도의 소스 코드 파일로 정의, Ex) 클래스, 항상 대문자
Interger
모든 기본 타입은 그것과 대응되는 참조 타입이 있다. (역은 불가)
기본 타입이 참조 타입보다 성능이 좋음
기본 타입 배열(
int[]
,byte[]
,boolean[]
)
자바와 달리 코틀린은 참조 타입만 제공 (int가 아닌 Int만 제공) → 선택지가 하나로 됨, 코드 쉽게 작성 가능
기본(primitive, 원시) 타입도 참조 타입(reference) 으로 사용
코틀린 컴파일러는 가능한 한 자바 바이트코드의 기본(primitive) 타입을 사용한다. ( 더 좋은 성능을 내기에 )
코틀린은 자바와 달리 원시 타입(Primitive Type) 과 참조 타입(Reference Type) 을 엄격하게 구분하지 않는다.
Nullable type(ex.
Int?
)은 자바의 Wrapper Class(Interger
)로 컴파일된다.
- 원시, 기본 타입, primitive
- 값을 직접 저장하는 데이터 타입입니다.
- 종류
- 정수 타입:
Byte
,Short
,Int
,Long
- 부동 소수점 타입:
Float
,Double
- 문자 타입:
Char
- 불리언 타입:
Boolean
- 정수 타입:
- 특징
- 컴파일 시 자바의 기본 타입으로 변환됩니다. (예:
Int
->int
) - 숫자 타입은 객체처럼 사용할 수 있습니다. (박싱/언박싱 자동 처리)
- 기본형을 참조형으로 변환: 이 기능을
박싱(boxing)
오토박싱(Autoboxing)
은 Java 컴파일러가 원시 타입과 해당 객체 래퍼 클래스 간에 수행하는 자동 변환을 말한다. (예시: int => Integer)
- 기본형을 참조형으로 변환: 이 기능을
- 컴파일 시 자바의 기본 타입으로 변환됩니다. (예:
- 참조 타입, reference
- 객체의 메모리 주소를 저장하는 데이터 타입입니다.
- 종류
- 클래스 (Class)
- 인터페이스 (Interface)
- 배열 (Arrays)
- 문자열 (String)
- 컬렉션 (Collection)
- 기타 객체 타입
특징 | 기본 타입 (Primitive Type) | 참조 타입 (Reference Type) |
---|---|---|
메모리 할당 | JVM의 스택(Stack) 영역에 직접 값 할당 | JVM의 힙(Heap) 영역에 객체 생성 후 스택에 참조 값 저장 |
값 표현 | 실제 값 (정수, 실수, 문자, 논리 값 등) | 객체의 메모리 주소 (참조 값) |
크기 | 고정된 크기 (타입별로 정의됨) | 가변적인 크기 (객체의 상태에 따라 달라짐) |
연산 | 값 자체를 이용한 연산 | 참조 값을 이용한 연산 (객체의 메서드 호출 등) |
비교 | == 연산자로 값 비교 | == 연산자로 참조 값 비교, .equals() 메서드로 객체 내용 비교 |
제네릭 | 사용 불가 | 사용 가능 |
종류 | byte, short, int, long, float, double, boolean, char | 클래스, 인터페이스, 배열, 열거형(enum), 문자열 등 |
메모리 사용량 | 적음 | 많음 |
속도 | 스택에서 값을 직접 사용, 빠름 | 스택에 저장된 참조 값을 통해 힙에 있는 객체에 접근, 느림 |
GC | 없음 | 있음 |
OOP | 불가 | 가능 |
조건문과 조건식
문과 식
문(Statement): 프로그램의 실행 흐름을 제어하는 명령어 또는 명령어들의 집합입니다.
식(Expression): 값을 생성하는 코드 조각, 값을 반환
식은 문의 일부가 될 수 있다.
특징 | 문 (Statement) | 식 (Expression) |
---|---|---|
값 생성 | X | O |
실행 결과 반환 | X | O |
상태 변경 | O | X |
세미콜론(;) | O (일반적으로) | X |
예시 | int x = 10 | x + 5 |
조건 표현식 (conditional expression)
if/else
문(Statement)
대신조건 표현식
을 사용할 수 있다.식(Expression)
은 값을 반환한다.1 2 3 4 5 6 7 8 9 10 11
val tmp1 = if () { "yes" } else { "no" } val tmp2 = when (para) { 100 -> "1" in 1..90 -> "2" else -> "3" }
when 문
- else 없어도 상관 없음
when 표현식
- else 가 꼭 있는지 컴파일러가 검증
- 결과를 반환하기때문
- 간결하고 다양하게 로직을 나타냄
when
- 임의의
조건
들을 검사할 수 있다 - 풀 스루가 아님
- 임의의
Java switch 문
- 값만 검사
- 풀 스루 (break 없으면, 아래의 조건들도 검사)
함수
기본적으로 가시성 제한자가 public
함수의
매개변수(===인자)
는 var이 아닌 val 이다.함수 내에 있는 지역 범수는 함수 범위에만 존재, 함수가 return 되면 소멸된다. (매개변수도 마찬가지)
- 매개변수 또한 함수 종료시 소멸됨
파일 수준 변수는 프로젝트 어디서든 사용 가능
- 자바 클래스의
static
메서드가 된다,~.kt 파일
을Java byteCode(.class)
를 decompile(역컴파일)로 확인 가능
- 자바 클래스의
파일 수준 변수는 초깃값이 지정되어야 한다. 아니면 컴파일 에러
기본 인자 ( default argument ) 지원
- 함수 인자가 기본값을 가짐
단일 표현식,
함수 대입 연산자 =
으로 정의overload 함수들은 컴파일러가 함수 호출시에 전달되는 인자의 타입과 갯수로 무슨 함수인지 알 수 있다.
Unit 함수
아무 값도 return 하지 않는 함수 타입
void 로는 제네릭 함수 를 구현할 방법이 없음, Unit 으로 해결
- void는 타입이 아니며, 타입 정보가 의미가 없으므로, 생략해라 라는 의미
Unit으로 함수의 반환 타입을 나타냄 => 제네릭 함수에도 사용 가능
Nothing 타입
- 함수의 실행이 끝나더라도, 호출 코드로 제어가 복귀되지 않는다
- 의도적으로 exception 예외를 발생시킬 때 사용
public inline fun TODO(): Nothing = throw NotImplementedError()
- 컴파일러는 이 TODO 코드를 에러로 처리하지 않는다.
- 제어가 복귀되지 않아서, 컴파일러가 TODO 코드의 다음 코드는 절대 실행되지 않는 것을 알게됨
자바의 파일 수준 함수
- 코틀린의 파일 수준 함수는 자바 클래스의 static 메서드가 된다.
- 코틀린 파일 이름의 Kt를 붙임
Game.kt
=>public final class GameKt
함수 Overloading
- 이름은 같지만 매개변수의 개수가 타입이 다른 여러 개의 함수로 구현하는 것
|
|
- 코틀린 컴파일러는 함수 호출시에 전달되는 인자의 개수와 타입의 일치되는 것을 알기 때문에 어떤 오버로딩 함수를 실행할지 알게됨
익명 함수와 함수 타입
익명 함수
말그대로, 이름이 없는 함수
익명 함수 == 람다(lambda)
다른 함수의 인자와 반환 가능
익명 함수도 타입을 가질 수 있고, 이것을
함수 타입
이라고 한다- 함수의 타입을 컴파일러에게 알려 준다.
익명 함수를 변수화 할 수 있다.
익명 함수는 익명 클래스 인스턴스로 생성된다.
암시적 반환
- 암시적으로 or 자동으로 함수 정의의 마지막 코드 결과를 반환한다.
- 람다에서 return 키워드는 사용이 금지되어 있음
- 어디로 복귀 되어야 하는지, 컴파일러는 알 수 없기 때문, 어디에서든 람다가 호출 될 수 있기 때문
하나의 인자만 받는 익명 함수의 매개변수 이름을 지정하는 대신
it
키워드를 사용할 수 있다.함수 타입 역시 타입 추론(type inference) 지원
- 익명 함수가 값으로 지정되면 타입을 필수로 지정하지 않아된다. (당연히 해도 됨)
람다 (lambda)
- 익명 함수를 람다(lambda)라 부름
- 람다 표현식, 람다식: 익명 함수 정의
- 익명 함수의 반환 결과 : 람다 결과(lambda result)
- 어떤 함수에서
마지막
매개변수로 함수 타입을 받을때는 ()를 생략할 수 있다."Mississippi".count ({ it == 's' })
"Mississippi".count { it == 's' }
{ it == 's' }
가 람다, 익명 함수 이다.- 이것은 람다가 마지막 인자로 함수에 전달될 때만 가능, 따라서 함수 타입 매개변수를 마지막 매개변수로 선언하는 것이 좋다.
inline 함수로 만들기
람다(익명 함수) 는 JVM에서 객체로 생성
- 메모리 부담
인라인
을 사용하면 람다의 객체 사용과 변수의 메모리 할당을 JVM이 하지 않아도 된다.inline 키워드를 추가하면, 람다가 객체로 전달 되지 않는다.
컴파일러가 바이트 코드를 생성할 때, 람다 코드가 포함된 함수 몸체의 전체 코드를 복사한후, 함수를 호출하는 곧에 붙여 넣기 하여 교체 하기 때문
하지만 재귀 함수는 무수히 많이 복사 됨으로, 코틀린 컴파일러는 재귀함수를 단순히 인라인 처리하지 않고, 루프 형태로 변경한다.
함수 참조
- 람다를 사용해서 다른 함수의 인자로 함수를 전달하는 방법도 있지만,
- 다른 방법으로
함수 참조
를 인자로 전달 - 함수 참조를 얻을 때는 함수 이름 앞에
:: 연산자
를 사용한다. - 매개변수에 적합한 이름 있는 함수가 있다면 람다 대신 함수 참조를 사용할 수 있다.
|
|
반환 타입으로 함수 타입 사용하기
코틀린의 람다는 클로저(closure, close over) 이다.
다른 함수에 포함한 함수(예를 들어, 람다) 에서 자신을 포함하는 함수의 매개변수와 변수를 사용할 수 있는 것을 말한다.
람다(익명 함수)가 외부 변수의 참조를 갖는다.
따라서 람다식은 자신이 포함된 외부 함수에 선언된 매개변수와 변수를 그냥 사용할 수 있다.
외부 함수에 val 변수는 람다식에서 그 값이 바로 저장
var은 별도의 객체로 저장되며, 그 객체의 참조값이 람다식 코드에 저장되어 값을 변경할 때 사용
다른 함수를 인자로 받거나 반환하는 함수를 고차 함수(higher-order function) 이라고 한다.
filter, map
등
코틀린에서는 익명 함수가 자신의 범위 밖에 정의된 변수를 변경하고 참조할 수 있다.
- 람다가 외부의 변수를 포획한다는 의미
람다 vs 익명 내부 클래스
함수 타입을 사용하면 진부한 코드가 줄어들고 유연성이 증가한다.
- 예를 들어, 자바 8과 같이 함수 타입을 제공하지 않는 언어
자바 8은 객체지향 프로그래밍과 람다 표현식을 모두 지원한다.
그러나 함수의 매개변수나 변수 에 함수를 정의할 수 있는 기능이 없다. (함수의 변수화가 불가)
대신에 자바는 익명 내부 클래스(anonymous inner cass) 를 제공
이것은 단일 메서드(method)를 구현하기 위해 다른 클래스에 정의된 이름이 없는 클래스다.
그리고 람다처럼 익명 내부 클래스를 인스턴스로 생성하여 전달할 수 있다.
자바에서는 람다를 정의하는 함수를 나타내기 위해(변수화) 이름이 있는 타입(인터페이스나 클래스)의 정의가 추가로 필요하다
null 안전과 예외
코틀린의 null 처리 개요
nullable / non-nullable
자바는 어떤 타입의 변수도 null 값을 가질 수 있다.
코틀린은 non-nullable 변수는 null을 가질 수 없다. 따라서 런타임이 아닌 컴파일 시점에 방지 할 수 있다.
null이 필요 없다면 non-nullable 타입을 사용하는 것이 가장 안전하다.
- 가능한 한, non-nullable 타입을 사용하자
에러 검출 시점(컴파일 vs 런타임)
- 코틀린은 컴파일러라는 특별한 프로그램에 의해, 기계어로 변환되는 컴파일 언어 이다.
- 런타임 에러는 프로그램이 실행된 이후에 발생된 에러임으로, 사전에 알려 줄 수 없다.
null 안전 처리
안전 호출 연산자 →
?.
?.
는 객체가 null일 경우에는 null을 반환하고, 그렇지 않으면 해당 객체의 속성이나 메서드를 호출
non-null 단언(assertion) 연산자 → !!
- null이면 런타임에 NPE 예외 발생
- 컴파일러가 null 발생을 미리 알 수 없는 상황이 생길 수 있을 때 사용된다.
- 프로그램의 오류가능성을 runtime에 예측하는 것은 거의 불가능하니, 조심해서 사용해야 함
값이 null인지 if 문 검사
엘비스 연산자(null 복합 연산자) →
?:
val beverageServed: String = beverage ?: "맥주"
엘비스 연산자는 null이 될 수 있는 값을 바로잡는 데 사용될 수 있다.
자바에서 @NotNull 어노테이션 → null 일 수 없다.
- 코틀린 소스 코드가 컴파일되어 JVM의 자바 코드로 생성될 때,
checkParameterIsNotNull
이라는 메서드가 사용됨. - 자바로 역컴파일된 바이트코드를 보면 알 수 있다.
- 코틀린 소스 코드가 컴파일되어 JVM의 자바 코드로 생성될 때,
예외 던지기, Exception Throw
- 예외는 프로그램이 잘못되었다는 것을 나타낼때 사용
- 예외가 발생할 때는 그것을 처리해야 하며, 그렇지 않으면 실행이 중단(crash) 된다.
- 미처리 예외(unhandled exception): 처리되지 않은 예외
|
|
throw : 예외를 발생 시킨다. === 예외를 던진다
실행전에 처리되어야 하는 문제를 알려 주기 위해 예외를 던진다.
Crash 되는 이유를 명확히 알 수 있음
stackTrace, message
예시에서, swordsJuggling 변수가 null 이되는 경우가 생긴다면, 개발하는 시점에 알 수 있는 가능성이 커진다.
커스텀 예외 : 예외를 상속 받은 클래스, 코드 유연성 증가, 재사용성 증가, 에러 메서지 출력 가능
예외 처리 가이드
논리 오류: 프로그램이 강제 종료되지도 않고 의도대로 동작하지도 않는 상황
예외가 던져지지 않으므로 런타임에 발생하는
RuntimeException
과는 다른 상황- 기존에는 예외가 던져지지 않았기에, 이런 경우에 예외를 던져보자
기본적으로 일단, 최대한 논리 오류가 태초에 발생 하지 않도록 만들어야 함
그럼에도 불구하고, 논리 오류가 발생하는 경우에 예외를 던져야 함
논리 오류는 에러 원인을 예측하기 어렵기 때문에 디버깅이 굉장히 어렵다.
Ex) 양수여야 하는 값이 음수인 경우
논리 오류(‘예외적인 상황’) 일 때만 예외를 던지세요.
논리 오류가 아니면 예외를 던지지 말고, null을 반환하세요.
실패하는 경우가 복잡해서 null로 처리할 수 없으면
sealed class
를 반환하세요.- 성공, 실패들을 모두
sealed class
로 선언해보세요.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
class IdentityNumber(private val numbers: List<Int>) { companion object { fun from(front: List<Int>, back: List<Int>): IdentityNumberResult = when { front.size != 6 -> IdentityNumberResult.InvalidFrontSize back.size != 7 -> IdentityNumberResult.InvalidBackSize else -> IdentityNumberResult.Success(IdentityNumber(front + back)) } } } sealed class IdentityNumberResult { data class Success(val identityNumber: IdentityNumber) : IdentityNumberResult() object InvalidFrontSize : IdentityNumberResult() object InvalidBackSize : IdentityNumberResult() }
- 성공, 실패들을 모두
일반적인 코틀린 코드에서
try-catch, runCatching()
를 사용하지 마세요.예외는 반드시 예외적인 상황에서만 던져야 합니다.
‘논리 오류일 때만 예외를 던지세요.’
논리 오류일 때 던지는 예외를 포함해서, 프로그램 전체적으로 발생하는 예외들을 전역적으로 처리해주는 예외 처리기를 통해 보고해야 합니다.
- 예시) 예상 가능한 네트워크 예외 처리에 대한,
CoroutineExceptionHandler
- 예시) 예상 가능한 네트워크 예외 처리에 대한,
|
|
생성자를 사용할 수 없도록 private으로 숨기고 from() 팩토리 함수를 추가하였습니다.
- 이렇게 하면 외부에서는 반드시
from()
함수를 통해서만PositiveNumber
를 만들어야 하고 만약 음수라면null
이 반환됩니다. - 이 방식에서는 음수로 양수(PositiveNumber)를 만든다는 논리 오류 자체가 발생할 수가 없습니다.
- 이렇게 하면 외부에서는 반드시
다음 링크의 글을 참고
예외 처리
try / catch
try문에서는 예외가 발생될 수 있는 코드를 넣는다.
catch문은 예외가 생길 때만 실행
catch 문에서는 처리할 예외의 특정 타입을 인자로 받는다
코틀린에서의 예외는 unchecked 예외,
- 예외를 생길 수 있는 모든 코드를 컴파일러가 try / catch문으로 처리하도록 강제하지 않는다. (자바는 checked, unchecked가 구분되어 있다.)
전제 조건
- 전제 조건 함수: 일부 코드가 실행되기 전에 충족되어야 하는 전제 조건을 정의한 함수
checkNotNull, require, requireNotNull, error, assert
안전하게 코드 작성 가능
null 무엇이 좋을까?
값을 지정하지 않고 변수를 초기화 할 수 있다.
- null으로 상태(State) 를 표시함
- 변수의 초깃값에 많이 사용
NPE를 피하는 방법
empty 값을 통해 더 좋고 안전한 코드 초기화를 할 수 있다.
- null 불가 타입으로 지정하고,
빈 문자열("")
로 초기화
- null 불가 타입으로 지정하고,
null이 될 수 있다는 것을 인정하고, null 가능 타입의 처리 방법들 을 사용
var personName: String? = ""
checked 예외와 unchecked 예외
코틀린에서는 모든 예외가 unchecked 예외다.
- 즉, 예외가 생길 수 있는 모든 코드를 우리가 try / catch로 반드시 처리하도록 강요하지 않음
자바는 checked와 unchecked 예외 타입이 구분되어 있다.
- checked 예외의 경우, 예외 처리를 했는지를 컴파일러가 검증함
대부분의 checked 예외(예를 들어, 파일을 저장, IOException) 는 발생했더라도 우리가 특별히 할 것이 없다.
따라서 개발자들이 해당 예외의 catch 블록 안에 처리 코드를 작성하지 않는 경우가 있다
- 예를 들어,
catch (e: IOException) { }
- 예를 들어,
해당 checked 예외가 무시되고(경보가 울렸는데 무시하는 것과 같다) 프로그램은 정상적으로 수행된다. 원인을 찾기 어려워짐
checked 예외는 문제를 해결하기 보다는 오히려 더 많은 문제를 야기할 수 있다.
- 코드 중복, 어려운 에러 복구 로직, 예외 무시
문자열
해체 선언 (destructing declaration)
val (type, name, price) = menuData.split(',')
문자열은 불변이다.
- 코틀린의 모든 문자열(String 타입)은 val or var 중 어느 것으로 정의되든 자바처럼 불변이다.
replace
처럼 값을 변경하는 것 처럼 보이는 어떤 함수도 실제로는 새로운 문자열로 변경값을 생성한다.
문자열 비교
- 참조 동등 : 특정 타입 인스턴스의 참조를 똑같이 갖는지 검사한다.
- heap 메모리 영역에 같은 객체를 참조하는지 검사한다. (
=== 연산자
)
- heap 메모리 영역에 같은 객체를 참조하는지 검사한다. (
- 자바에서는
== 연산자
가 두 문자열의 참조를 비교하므로 코틀린과 다르다.- 자바에서 문자열의 값을 비교할때는 equals 메서드를 사용해야 한다.
비교 연산
Kotlin | Java | |
---|---|---|
동등성(Equality, Structural equality) | == | equals |
동일성(Identify, Referential equality) | === | == |
자바, 코틀린 모두 원시 타입인 경우 동등성 비교는
==
으로 같은 연산자를 사용참조 타입일 때만, 달라짐
구조적 동등성, Equality 비교 (
==
,!=
)기본 타입인 경우: 값 비교, 값의 동등성 비교
참조 타입인 경우:
equals()
로 객체의 내용 비교==
는 내부적으로equals
를 호출한다. (연산자 오버라이딩 기능)
참조 동등성, Identify 비교 (
===
,!==
))객체의 메모리 주소를 비교
코틀린은 자바에는 없는
===
연산자를 지원한다.즉, 자바의 주소 값 비교인
==
와 코틀린의===
가 동일한 역할을 한다.
|
|
|
|
숫자
Byte (8비트)
Short (16비트)
Int (32비트)
Long (64비트)
Float (32비트)
Double (64비트)
숫자에는 정수와 소수가 있다.
문자열을 숫자 타입으로 변환하기
- toFloat, toDouble, toLong, toIntOrNull
val gold: Int = "5.91".toIntOrNull ?: 0
소수
소수점값을 구하려면 코틀린이 부동 소수점 연산을 해야 한다.
위치가 달라질 수 있는 소수점을 의미하는 부동 소수점은 실수의 근사치이며, 정밀도와 성능 모두를 지원하기 위해 근사치를 사용한다.
%.2f.format(~~~)
: 소수점 이하 두 자리 형식- Double 타입에 toInt 를 호출하면, 소수점 이하 값이 절삭되어 정수로 변환된다. (4.91.toInt → 4)
표준 함수 (범위 지정 함수)
코틀린의 표준 함수(범위 지정 함수) 는 내부적으로 확장 함수이며,
확장 함수를 실행하는 주체를
수신자 또는 수신자 객체, 수신 객체, receiver
라고 한다.표준 함수(범위 지정 함수) 는 람다를 인자로 받아 동작한다.
- 인자로 받은 block 변수(함수)를 실행
수신 객체라는 용어는 Kotlin의 확장 함수에서 등장한다.
확장 함수에서
this
는 확장된 클래스의 객체, 즉 확장 함수를 사용하는 그 객체가 된다.그 객체가 바로
수신 객체(Receiver object)
이고,확장할 클래스가
수신 객체 타입(Receiver Type)
수신: ‘(수신) 객체가 코드를 받는다.’
수신 객체 지정 람다 (Lambdas with Receivers)
수신 객체 지정 람다
는 람다에 확장 함수 처럼 수신 객체를 사용한 것이다.T.() -> R
확장 함수에서 수신 객체를 사용하여 블록 내에 객체를 전달했듯이,
수신 객체 지정 람다(Lambdas with Receivers)
또한 수신 객체를 이용하여 객체를 전달한다.
따라서
수신 객체 지정 람다
에서는 수신 객체를 this로 대신할 수 있다.또한 this를 생략하고 해당 객체의 멤버에 바로 접근이 가능하다.
반면
일반 람다
는 객체를 인자(파라미터) 로 전달한다.(T) -> R
- 람다의 매개변수가 하나뿐이고, 컴파일러가 타입을 추론할 수 있을 때 객체는 기본 매개변수인
it
으로 받을 수 있다.
- 람다의 매개변수가 하나뿐이고, 컴파일러가 타입을 추론할 수 있을 때 객체는 기본 매개변수인
|
|
apply
수신자 객체의 참조 가능
- this로 참조, this 생략 가능
람다의 실행이 끝나면 현재의 수신자 객체가 반환된다.
run
- apply와 다르게 run은 수신자 객체를 반환하지 않는다.
- 람다의 결과, 마지막 코드 줄의 실행 결과를 반환한다.
- 함수 호출이 여러 개 있을때 함수 중첩 보다 run을 사용하면 편리하다.
"~~~".run(::myFun)
=myFun("~~~")
with
run 과 동일하게 동작하지만 호출 방식이 다르다.
수신자 객체를 첫 번째 매개변수의 인자로 받는다.
with 대신 run을 사용할 것을 권한다.
- 잘 사용 안됨
also
- let 처럼 자신을 호출한 수신자 객체를 람다의 인자로 전달한다.
- let과 달리, 람다의 결과를 반환하지 않고, 수신자 객체를 반환한다.
let
수신 객체를 람다의 인자(파라미터) 로 전달
전달된 람다를 실행한 후, 마지막 코드 줄의 실행 결과를 반환해준다.
null check 후 코드를 실행해야 하는 경우
- 안전 호출 연산자 →
?.
- ‘?.let’을 사용 하게 되면 let의 block은 수신객체가 null이 아닐 때만 수행된다.
- 따라서 let block에서의 it의 타입은 nullable하지 않은 타입이 된다. (스마트 캐스팅)
- 안전 호출 연산자 →
nullable한 수신객체를 다른 타입의 변수로 변환해야 하는경우
|
|
List, Set, Map
- 컬렉션은 연관된 값들을 모아서 저장하고 사용하는 자료구조
- List, Map, Set이 있다.
- 컬렉션에 저장된 각각의 데이터를 element (요소) 라고 한다.
- 코틀린에서는 mutable(변경 가능한) 타입과 read-only(읽기 전용) 타입이 있다.
- 코틀린 컬렉션은 가변성 개념을 제공하면서, 강력한 기능을 제공
List
List<myType>
에서myType
: 매개변수화 타입, 요소의 타입이 myType이라는 것을 알려줌.
List은 제네릭 타입 이다. ⇒ List는 어떤 타입의 데이터도 저장할 수 있다
listOf 함수
: 컬렉션 자체를 생성 하고, 데이터 추가 하는 일을 둘 다 하는 함수안전한 인덱스 사용 : getOrElse, 예외 대신 기본값을 반환 / getOrNull 와 :? 사용
containsAll : 요소가 존재하는 지 한 번에 확인할때 사용
List의 변경 가능 여부는 List의 타입에 의해 결정
- ‘변경 가능(mutable)’ 은 저장된 요소를 변경할 수 있다는 것을 의미한다.
List
타입은 read-only 이다.
변경 가능 ⇒
MutableList 타입
을 사용해야 함, add, remove 사용 가능toList
<=>toMutableList
으로 상호 변경 가능
반복 처리
in 키워드 : for 루프에서 반복 처리되는 객체를 나타낸다.
itertation(반복 처리) 가능
- for 루프로 요소의 이름을 지정하면, 코틀린 컴파일러가 타입을 알아서 처리
Iterable 타입
은 반복 처리를 지원한다.- List, Set, Map, IntRange
해체 선언
- List는 또한, 맨 앞의 다섯 개 요소까지 변수로 해체 할 수 있는 기능을 제공한다.
- 해체를 원하는 않는 요소에
_(밑줄)
을 사용해서 선택적으로 해체할 수도 있다.
Set
수학의 집합처럼 요소가 고유한 것을 보장해 주는 컬렉션
인덱스와 인덱스 연산자([])를 사용해서 요소를 처리할 수 없다.
elementAt 함수
를 사용해서 특정 인덱스의 요소를 요청 할 수 있다.‘논리적’으로 인덱스처럼 접근
한 번에 하나의 요소를 반복해서 읽는다. (반복 처리 가능, itertation)
따라서, 인덱스 연산자 보다 느리다
순서를 갖지 않는다.
‘값’ 이 고유하다.
인덱스로 접근하는 자료구조는 아님
Array, 배열 타입
코틀린은 참조 타입밖에 없다. (컬렉션도 참조 타입)
자바에서는
Array
을 기본(원시) 타입으로 지원코틀린에서 기본 타입이 아닌
Arrays
라는 참조 타입으로 배열을 지원한다.val playerAges: IntArray = IntArrayOf(34, 23, 12)
- Kotlin에서
Arrays
는 참조 타입이지만, 특별한 배열 타입을 통해 기본 타입 배열과 유사한 기능을 제공 - 내부적으로 원시 타입 값을 저장하여 메모리 사용량을 줄이고 성능을 향상
- 코틀린의 (특별한) 배열 타입(
IntArray
,ByteArray
,BooleanArray
)
IntArray
타입은 자바의 기본 타입(배열 타입) 으로 컴파일 된다.코틀린 컬렉션을 자바의 기본 배열 타입으로 변환 가능
toIntArray
val array1 = arrayOf(1, 2, 3)
,val array2 = Array(5) { it }
- 자바로 디컴파일 후 확인:
@NotNull final Interger[] array1;
- 자바로 디컴파일 후 확인:
val array1 = intarray(1, 2, 3)
,val array2 = IntArray(5) { it }
- 자바로 디컴파일 후 확인:
@NotNull final int[] array1;
- 자바로 디컴파일 후 확인:
|
|
읽기 전용 vs 변경 불가능
- 불변은 ‘변경 불가능’ 을 의미
- 따라서 코틀린 컬렉션은 ‘읽기 전용’이 더 어울림.
|
|
Map
- 키와 값의 쌍의 데이터 (entry 라고 한다.)
- iteration 반복 처리 지원
- 정수 인덱스 기반 처리 대신 키를 기반으로 데이터 처리
to 함수
로 키와 값을 정의한다.- getValue, [ ], getOrElse, getOrDefault
- ‘키’ 가 고유하다.
- Map이 Iterable을 직접 구현하지 않음
- 하지만,
keys, values, entries
가 Iterable을 구현함
- 하지만,
클래스 정의하기
클래스는 사물이나 개념을 추상화 한 프로그래밍 요소이다.
속성(property)과 기능(function) 을 갖는다.
행동(역할)과 데이터를 정의한다.
- OOP의 핵심 요소
default가 public
인스턴스 생성하기
- 생성자(constructor) 를 호출하여 인스턴스를 생성한다.
- 함수 호출과 비슷
가시성 제한자
클래스 함수나 속성의 가시성을 제한 하는 개념을 OOP에서 정보은닉, 캡슐화라 한다.
public : 클래스 외부에서 클래스 요소 사용가능
private : 클래스 내부에서만 요소 사용 가능
protected : 클래스 내부 or 클래스의 서브 클래스에서만 사용될 수 있다
internal : 클래스가 포함된 ‘모듈(module) ’에서 사용될 수 있다
속성(프로퍼티, property)
프로퍼티는 클래스의 데이터 즉, 상태나 특성을 나타낸다.
변수와 다르게 클래스 속성은 반드시 초깃값이 지정되어야 한다.
- 인스턴스가 생성될 때 모든 속성이 값을 가져야 한다.
- 다른 함수나 프로그램에서 이 클래스의 속성에 접근할 수 도 있기 때문
속성(프로퍼티)의 getter와 setter
프로퍼티를 외부에서 사용할때, 코틀린은 자동으로 getter를 통해 가져오고, setter를 통해 값을 지정한다.
정의한 각 속성에 대해 field와 getter or setter 가 생성된다.
getter 에서는 속성값을 읽는 방법이 명시된다.
커스텀 getter, setter를 정의할 수 있다. ⇒ getter, setter를
overriding
- 커스텀(override) 하지 않으면, 기본으로 생성되는 속성값을 있는 그대로 반환 및 지정
field 키워드
는 프로퍼티에 대해 코틀린이 자동으로 관리해주는backing field
를 참조한다.field 키워드
는 getter, setter에서만 사용할 수 있다.backing field
는 getter, setter가 사용하는 프로퍼티의 데이터다.
getter는 backing field를 변경하지 않는다. setter는 backing field를 변경한다.
프로퍼티는 외부에 노출시키되(public), setter는 노출시키지 않으려면
private set
으로 따로 정의도 가능하다.- 기본적으로 getter, setter 의 가시성은 속성 자체의 가시성과 일치
getter는 프로퍼티를 참조할 때 자동 호출
setter는 대입 연산자(ex.
=
)를 사용해서 속성에 값을 지정할 때 자동 호출
|
|
산출 속성(computed property)
- 다른 속성이나 변수 등의 값을 사용해서 자신의 값을 산출하는 속성, backing field 생성하지 않음.
- 초깃값이나 기본값이 없다
|
|
- 이런 경우에서도 볼 수 있듯, ‘변경 불가능’ 보다 ‘읽기 전용’ 이라는 표현이 더 적합
패키지 사용하기
Ex)
com.myProject.presentation.music
패키지(package) 는 폴더 처럼 비슷한 요소들을 분류하고 모아 놓은 것이다.
프로그램에서 직접 패키지를 지정할 때는
package
키워드를 사용한다.지정된 .kt 파일이 컴파일되면, 생성된 바이트코드 파일(.clsas) 는 정의한 패키지 경로에 위치하게 된다.
같은 패키지에 있는 클래스들은 기본적으로 같이 사용할 수 있다.
- 단, 코틀린 표준 라이브러리의 모든 클래스나 함수 등은 import를 지정하지 않아도 바로 사용 가능
다른 패키지에 있는 클래스나 함수 등을 사용하려면
import 문
을 사용해서 그것들의 위치를 컴파일러에게 알려주어야 한다.
경합 상태(race condition)
|
|
컴파일러는 변수가 null이 아님이 if문 등으로 확인되더라도,
- 경합 상태가 생길 수 있기 때문에(여러 개의 스레드로 실행될 가능성 존재)
- 스마트 캐스팅이 할 수 없고, 컴파일 에러를 알려준다.
weapon의 값이 if로 확인된 시점과 println으로 weapon의 name을 출력하는 시점 사이에 weapon 이 null로 변경될 가능성이 여전히 있다. => 스마트 캐스팅 불가
|
|
- also를 사용하면 해결 가능
- also 익명 함수 내에서만 존재하는 지역변수 it 으로 weapon 인스턴의 name 속성이 참조되기 때문
- ?.로 null이 아님이 확인 되었고,
- it의 값은 프로그램의 다른 코드에서 변경불가능 함
- 컴파일러가 Weapon 타입으로 스마트 캐스팅 가능
패키지 가시성
자바는 기본적으로 패키지 가시성을 사용한다.
- 같은 패키지에 있는 클래스에서만 사용 가능하다.
코틀린에서는 패키지 가시성이 없다.
- 같은 패키지에 있는 클래스, 함수, 속성 등은 기본적으로 상호 사용가능하기 때문에 굳이 별도의 패키지 가시성을 가질 필요가 없기 때문이다.
모듈은 독자적으로 실행 및 테스트될 수 있는 프로그래밍 구성 단위
코틀린은
internal 가시성
을 지원한다. 자바는 지원하지 않음바이트코드 파일에서 internal은 public이 된다.
초기화
- 클래스의 인스턴스를 생성하는 것은, 클래스에 정의된 속성을 구조로 갖는 객체를 메모리에 할당하는 것
기본 생성자
커스텀하지 않고, 자동으로 생성되는 기본 getter와 setter를 사용하는 속성의 경우에는,
- 클래스 내부에 속성을 따로 정의하지 않고 기본 생성자에만 정의해도 된다.
기본 생성자에 정의된 변수는 클래스 속성과 생성자 매개변수 두 가지 역할을 하게 된다.
기본 생성자에 속성을 정의할 때는
var, val
을 추가해야 한다.
|
|
보조 생성자
|
|
말 그대로, 기본 생성자에 정의된 속성을 다양하게 초기화하는 보조 역할을 수행
보조 생성자에서는 클래스 속성(프로퍼티) 를 정의할 수 없다.
- 속성은 기본 생성자 or 클래스 몸체에서 정의되어야 한다.
this 키워드
는 보조 생성자를 사용해서 생성되는 클래스 인스턴스의 기본 생성자를 뜻한다.
초기화 블록 (init)
전제 조건 검사는 초기화 블록에서 주로 한다.
- 생성자에 전달되는 인자가 적합한지 확인
require
문을 주로 사용
초기화 블록은 어떤 생성자가 호출되든 클래스 인스턴스가 생성될 때 마다 자동으로 호출되어 실행된다.
속성을 초기화하는 코드가 복잡하다면, init 에 초기화 코드를 넣는 것도 가능하다.
속성(프로퍼티) 초기화
- 속성(프로퍼티)은 정의된 타입으로 반드시 초기화되어야 한다.
초기화 순서
기본 생성자에 정의된 속성에 인자값 지정
클래스 내부에 지정된 속성에 초깃값 저장
초기화 블록(init) 실행
보조 생성자 실행
초기화 지연시키기
클래스 속성은 non-nullable 변수가, null이 아닌 값으로 초기화된 다는 것을 보장하기 때문에, 초기화는 중요하다.
기본 타입(ex. Int) 이 아니고, 다른 객체를 참조하는 속성의 경우 ‘지연 초기화(lateinit)‘가 가능하다.
- 다른 객체를 참조하는 속성의 경우, 생성자가 호출되는 방법과 시점은 우리가 제어할 수 없는 경우가 있기 때문에
- Ex) 외부 프레임워크에서 초기화되는 경우, 안드로이드의 뷰 속성
lateinit 키워드
는 개발자가 해당 속성을 사용하기 전에 초기화해야 된다는 것을 의미한다.개발자가 스스로 책임지고, 해당 속성을 사용하기 전에 초기화해야 된다는 것을 뜻함
‘지연 초기화’
isInitialized 함수
는 속성이 초기화 되었는지 검사해준다.if (::alignment.isInitialized)
- 속성의 값이 아닌 참조를 전달해야 함으로
::
를 붙임
클래스 인스턴스의 생성 시점에서 속성을 초기화 할 수 없다면, 지연 초기화를 사용해야 하고,
lateinit 키워드
를 사용해서 이를 나타낸다.lateinit
- 기본 타입이 아니여야 함
- 다른 타입의 객체 참조 때문에
- var
- non-nullable이어야 한다.
- 커스텀 getter, setter 정의 불가하고, 기본으로 생성되는 getter, setter 사용해야 함
- 기본 타입이 아니여야 함
lateinit 대신 nullable 타입으로 변수 선언하고, 변수에 null으로 초기화도 가능하긴 한데, null 체크를 개발자가 계속 해야 한다.
var tmp: String? = null
lateinit은 클래스 속성 뿐만 아니라 최상위 수준 속성과 함수의 지역 변수에서도 사용 가능하다.
늦 초기화 (lazy initalization)
‘지연 초기화’ 만이 초기화를 지연시킬 수 있는 유일한 방법은 아니다.
변수나 속성이 ‘최초’ 사용될 때까지 초기화를 ‘연기’ 할 수도 있다.
속성을 즉시로 사용할 필요가 없다면 ‘늦 초기화’가 좋은 선택이 된다.
- 늦 초기화는 코틀린에서 delegation(위임) 패턴을 사용해서 구현한다.
lazy 함수
를 대리자로 위임 처리 한다.lazy 함수
는 람다를 인자로 받아 실행 시켜준다.- lazy 함수와 람다로 초기화 후에 이후에는 다시 초기화되지 않고, 캐시에 저장되 결과가 사용된다.
val homtTown by lazy { myFun() }
by 키워드를 사용해서, 위임받은 일을 처리하는 대리자(delegate) 를 지정한다
- 대리자로 커스텀 함수 또는 코틀린 표준 라이브러리의 함수를 사용할 수 있다.
컴파일러는 소스 코드상의 초기화 순서를 검사하지 않는다.
- 속성을 사용하는 함수의 순서를 비교하지는 않는다.
상속
서브 클래스(자식 클래스) 는 상속해 주는 클래스의 모든 속성과 함수를 공유한다.
- 상속해 주는 클래스를 부모 클래스 또는 슈퍼 클래스라고 한다.
open 키워드
를 붙혀서 서브 클래스를 가질 수 있게 해야 한다.상속 받은 속성과 함수를 그대로 사용하지 않고, overrideing 할 수도 있다.
- 이때 override 하는 함수에도
open 키워드
사용해야 한다.
- 이때 override 하는 함수에도
super 키워드
를 사용해서, 부모 클래스 함수 or 속성을, 자식(서브) 클래스에서 호출 or 접근 할 수 있다.- 슈퍼 클래스의
public, protected
속성과 함수를 사용할 수 있다.
- 슈퍼 클래스의
슈퍼 클래스로 타입을 선언하면, 어떤 서브 클래스 인스턴스도 참조 할 수 있다. ⇒ 다형성
서브 클래스는 기본적으로 open이 되므로, 서브 클래스의 서브 클래스는 언제든 override 할 수 있다.
코틀린에서는 클래스가 정의될 때 기본적으로 서브 클래스를 만들지(상속 되게) 못하게 되어 있다.
final 키워드
: 키워드를 붙인 함수 or 속성만, override 될 수 없게 함.
타입 검사
is 연산자
로 객체가 특정 타입인지 검사할 수 있다.자식 클래스의 인스턴스는 해당 자식 클래스의 타입이면서 동시에 부모 클래스의 타입도 된다.
- 사자 인스턴스는 사자 타입이면서 동시에 동물 타입이다.
코틀린 타입의 상속 계층
코틀린의 모든 non-nullable 클래스는 자동으로
Any
라는 최상위 슈퍼 클래스로 부터 상속 받는다.Any
는 자바의 모든 클래스가java.lang.Object
의 서브 클래스인 것과 비슷하다.
as 연산자
: 상속 관계가 있을 때, 타입 변환에 사용- 변환된 타입의 속성 참조나 함수 호출을 할 수 있는 것이지 해당 객체가 갖는 값을 변환하는 것은 아니다.
두 타입 간에 상속 관계가 없으면 타입 변환은 불가능
스마트 캐스팅
- 코틀린 컴파일러가 특정 조건에서 명시적 캐스팅이 아닌, 자동으로 타입 변환을 수행하는 것
- 간결성, 가독성, 안전성
- 직접 변환하지 않아도 스마트 캐스팅이 일어나면, 컴파일러는 해당 타입으로 간주한다. (컴파일 에러 안남)
- non-nullable 타입은 nullable 타입의 자식(서브) 타입이다.
- JVM 애플리케이션으로 컴파일하면 Any 클래스는
java.lang.Object
로 바이트 코드에 구현되지만, 다른 플랫폼을 대상으로 컴파일하면 해당 플랫폼에 맞게 다른 형태로 구현된다.
Any 클래스
|
|
모든 클래스의 부모 클래스
기본 메서드 제공:
Any
클래스는 다음과 같은 기본 메서드를 제공합니다.equals(other: Any?): Boolean
: 객체의 동등성을 비교합니다.hashCode(): Int
: 객체의 해시 코드를 반환합니다.toString(): String
: 객체의 문자열 표현을 반환합니다.
해당 타입에 맞게 오버라이딩해서 구현하라는 의미
확장 함수 활용:
Any
클래스는 확장 함수를 통해 다양한 기능을 추가할 수 있습니다. 예를 들어,to()
함수를 사용하여Pair
객체를 생성할 수 있습니다.다양한 플랫폼에 독립적인 애플리케이션을 생성할 수 있게 해주는 방법 중 하나다.
즉, 각 플랫폼에 공통적으로 사용할 수 있는 최상위 슈퍼 클래스인 것이다.
코틀린 프로그램을 JVM 애플리케이션으로 컴파일하면 Any 클래스가
java, Lang.object
로 바이트 코드에 구현됨- PC 운영체제의 JVM에서 실행되는 애플리케이션
다른 플랫폼을 대상으로 컴파일하면 해당 플랫폼에 맞게 다른 형태로 구현된다.
- JVM 없이 실행되는 네이티브 App, 웹 브라우저에서 실행되는 자바스크립트, 안드로이드 App
따라서 우리 코드에서는 최상위 슈퍼 클래스가 Any라고 생각하고 사용하면 된다.
코드가 실행된 각 플랫폼에서 Any가 어떻게 다르게 구현되는지 자세히 알 필요 없기 때문이다.
객체
object 키워드
싱글톤은 하나의 인스턴스(객체)만 메모리에 생성되는 것을 말한다.
코드의 정의된 곳에 생성되어 동작하며, 다른 클래스 내부에 포함시켜 사용할 수 있다.
멀티 스레드로 실행될때는 반드시 하나의 객체만 생성되도록 동기화 처리를 해야 한다.
최초 사용 시점에 하나만 생성되어, 계속 유지됨
일반 클래스처럼 속성과 함수가 포함될 수 있고, 최초로 사용될 때 초기화 된다.
object 키워드
를 사용하여 정의된 객체는 JVM에서 로드될 때 즉시 초기화되며, 이 때 쓰레드 안전(thread-safe) 하게 초기화됩니다. => 동기화 문제 해결객체 선언(object 키워드) 에도 일반 클래스처럼 속성과 함수가 포함될 수 있다. 그리고 이런 속성이나 함수가 최초로 사용될 때 비로소 해당 객체가 생성되고 초기화된다.
객체 표현식
- 기존 클래스의 서브 클래스를 원하는 코드 안에 ‘이름 없이’ 정의 하고 바로 인스턴스를 생성해서 사용하는 경우 편하게 사용된다.
- annonymous 클래스 (익명 클래스) 라고 한다.
|
|
익명 클래스는 ParentClass 의 자식 클래스임으로, 속성과 함수를 상속 받는다.
- override 및 새로운 속성 및 함수 추가 가능하다.
'anyClassInstance'
인스턴스는 싱글톤 객체가 됨으로,- 함수 내부에서 사용될 때는, 매번 인스턴스가 생성될 수 있기 때문에 사용시 유의해야 한다.
동반 객체 (companion object)
- 최상위 수준에서는 사용할 수 없고, 클래스 내부에서 정의하여 사용한다.
- 클래스 내부에 정의된
객체 선언(object)
- 하나의 클래스에서는 하나의 동반 객체만 포함될 수 있다.
- 클래스의 인스턴스가 얼마나 많이 생성되던, 동반 객체의 인스턴스는 하나만 생긴다.
중첩 클래스
- 다른 클래스 내부 안에 정의된 클래스, nested class
- 중첩된 클래스의 인스턴스는 외곽 클래스의 인스턴스가 생성되어야 사용할 수 있다.
- 외곽 클래스는 중첩 클래스의 속성과 함수를 사용할 수 있다.
- Nested Class는 외부 클래스의 인스턴스에 대한 참조를 가지지 않으며, 외부 클래스의 멤버에 직접 접근할 수 없습니다.
|
|
내부 클래스 (inner class)
- Kotlin의
inner
키워드를 사용하여 선언된 클래스를 Inner Class(내부 클래스)라고 합니다. - 자신을 감싸고 있는 외부 클래스(Outer Class)의 인스턴스에 대한 참조를 암시적으로 가지고 있습니다.
- nested class와 다름
- 암시적으로 참조하므로, 메모리 누수에 주의해야 합니다.
- Inner Class는 외부 클래스의 인스턴스 없이 생성할 수 없습니다.
|
|
data class
주로 데이터를 표현하는 객체를 간편하게 생성, 저장, 표현하기 위해 사용됩니다.
일반 클래스와 달리 데이터 클래스는 컴파일러가 자동으로 몇 가지 유용한 메서드를 생성해줌
JVM은 객체를 고유하기 관리하기 위해 해시 코드 값을 생성함
인스턴스끼리 각 속성(프로퍼티) 의 값을 비교 (equals 함수)
- Equality, 동등성 연산에서 사용
인스턴스를 컬렉션(Ex. Map)에 저장할 때 사용할 키 값인 해시 코드를 생성 (hashCode 함수)
- hash와 관련된 연산을 할 때 사용
객체를 문자열로 나타내는 기능 (toString 함수)
해체 선언 함수 (componentN 함수)
기존 인스턴스(객체)의 속성값을 변경하여 새로운 인스턴스를 생성하는 (copy 함수)
얕은 복사; Shallow Copy:
copy()
메서드는 기본적으로 얕은 복사를 수행합니다. 원본 객체와 같은 참조를 공유합니다. 따라서 참조 타입 프로퍼티를 변경하면 원본 객체에도 영향을 미칠 수 있습니다.copy()
를 사용하면 원시 타입 프로퍼티는 값 복사(Value Copy) 를 통해 새로운 객체에 복사됩니다.copy()
를 사용하면 참조 타입 프로퍼티는 참조 복사(Reference Copy) 를 통해 새로운 객체에 복사됩니다.
깊은 복사; Deep Copy: 새로운 객체 생성, 이는 코틀린에서 개발자가 직접 구현해야 합니다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
data class Address(var city: String) data class Person(val name: String, val address: Address) // 참조 타입 복사 val address = Address("Seoul") val person1 = Person("Alice", address) val person2 = person1.copy() person2.address.city = "Busan" // person2의 address 변경 println(person1.address.city) // Busan (person1의 address도 변경됨) // 원시 타입 복사 val person1 = Person("Alice", 25) val person2 = person1.copy() person2.age = 26 // person2의 age 값만 변경 println(person1.age) // 25 (person1의 age는 변경되지 않음)
위 5개의 함수를 코틀린 컴파일러가 자동으로 생성한다.
- Any 클래스의 (equals, toString, hashCode) 함수들을 오버라이딩 해준다.
반드시 ‘기본 생성자’ 에 속성들을 지정해야 한다.
open
으로 피상속 불가, 슈퍼 클래스 불가- 상속을 허용한다면, 부모 클래스에서 자동 생성된 메서드들이 자식 클래스의 모든 프로퍼티를 고려하여 정확하게 동작하도록 보장하기가 어렵기 때문
- data class의 목적과 부합하지 않음
Data Class는 다른 클래스를 상속할 수 없으며, 인터페이스만 구현할 수 있습니다.
해시 코드 값은 인스턴스를 고유하기 식별하기 위해 생성된 값이다.
enum 클래스
‘상수값’을 정의하는 열거형 클래스를 정의할때 사용
상수보다 더 서술적이라서 무엇을 의미하기 알기 쉽다.
enum 의 항목은 단순한 상수가 아니라, 서브 타입이다.
- 각 상수(항목들) 는 자체적으로 프로퍼티와 메서드를 가질 수 있는 객체
항목들은 enum class 내부에서만 생성되며, 외부에서 임의로 생성할 수 없습니다.
enum 는 모든 상수가 컴파일 타임에 결정되므로, 각 상수는 JVM 내부적으로 단일 인스턴스로 표현됩니다. (메모리 사용 측면에서 효율적)
enum 클래스 내부적으로
‘name’(항목 이름, String 타입)
,‘ordinal’(항목 위치, Int 타입, 0부터 시작)
EnumClass.values
: 모든 항목 이름을 ‘배열’로 생성enum 클래스의 함수 호출은
‘enum 클래스명.항목명.함수명’
의 형태로 호출해야 한다.컴파일러가 모든 타입을 처리했는지 검사할 수 있다는 장점
싱글턴 패턴과 유사한 특징을 가지고 있어, 싱글턴처럼 사용할 수 있습니다.
- 하지만 상속 불가 및 값 표현 등의 차이점이 있으므로, 엄밀히 말하면 싱글턴은 아닙니다.
연산자 오버로딩
연산자 오버로딩: 피연산자의 타입이 무엇이든 같은 연산자를 사용해서 동일한 기능을 구현할 수 있게 해주는 것
코틀린에서는 각 연산자를 사전에 약속된 함수로 구현하여 연산자 오버로딩을 지원한다.
- 코틀린 컴파일러는
a+b
를 컴파일하여,a.plus(b)
를 실행하도록 바이트코드로 생성 + 연산자
를 사전에 약속된 plus 함수로 싱행하게 되면, 피연산자의 타입이 다르더라도 덧셈은 항상+
로 표기가능- 피연산자의 타입마다 서로 다른 덧셈 연산자를 사용하면, 불편하고 연산자 수가 매우 많아짐
- 코틀린 컴파일러는
코틀린에서
== 비교 연산자
가 내부적으로equals 함수
로 호출되는 이유가 연산자 오버로딩 때문이다. (ex.+, plus()
)
객체의 값 비교하기
- equals 함수를 override 할 때는 haseCode 함수도 같이 override 해야 한다.
- 동등성, Equality 연산
hash와 관련된 자료구조(hashMap, hashTable)는 동등성 연산(equals) 전에 먼저, Hash Value비교를 수행한다. 즉,
hashCode()
의 값이 같은 경우에만 **동등성 연산(equals)**이 수행된다.두 개의 다른 인스턴스에 대해 같은 Hash Value가 나오는 경우를 Hash 충돌(Hash Collision) 이라 한다.
Hash Value가 같으므로 동등성 연산(equals) 가 수행된다.
같은 해시 값을 갖는 인스턴스들이 LinkedList 형태로 이어져있어 하나하나씩 Iteration이 돌아가면서 동등성 연산이 수행
따라서 N개의 값 객체가 있고 해당 값 객체들이 모두 같은 Hash Value를 갖는다면 동등성 연산 수행에 O(N)의 시간 복잡도가 필요하다.
- 하지만 만약 모든 값 객체들이 다른 Hash Value를 갖는다면 동등성 연산 수행에 O(1)의 시간 복잡도
- 따라서 hashCode()값을 Hash 충돌을 최대한 피할 수 있도록 짜야한다.
|
|
JVM에서 Hash와 메모리 주소 값 비교
특징 | Hash (해시 코드) | 메모리 주소 값 |
---|---|---|
의미 | 객체의 데이터를 기반으로 생성된 정수 값 | 객체가 저장된 메모리 상의 실제 위치 |
목적 | 객체 비교, 해시 기반 컬렉션 활용 | 객체 식별, 참조 비교 |
고유성 | 동일 객체는 항상 같은 값, 다른 객체는 다른 값을 가질 수 있음 (해시 충돌 가능) | 각 객체마다 고유한 값 |
가변성 | 객체의 상태가 변하지 않으면 불변 | 가비지 컬렉션 등에 의해 변경될 수 있음 |
메모리 주소와의 관계 | 일반적으로 무관 | 직접적인 관계 |
JVM에서 hash와 메모리 주소 값은 둘 다 객체를 식별하는 데 사용되지만, (공통)
- Hash는 주로 객체의 논리적 동등성 비교와 해시 기반 컬렉션에서 활용되며,
- 메모리 주소 값은 객체의 고유성 판별과 참조 비교에 사용됩니다.
Hash
- 객체의 데이터를 기반으로 생성된 정수 값입니다.
Object
클래스의hashCode()
메서드를 통해 얻을 수 있습니다.- 해시 기반 컬렉션: 해시 기반 컬렉션에서 객체를 효율적으로 저장하고 검색하는 데 사용됩니다.
- 해시 코드를 통해 객체를 버킷에 분류하고, 충돌 발생 시
equals()
로 최종 비교
- 해시 코드를 통해 객체를 버킷에 분류하고, 충돌 발생 시
메모리 주소 값
- 객체가 저장된 메모리 상의 실제 위치를 나타내는 값입니다.
- 자바는
== 연산자
, 코틀린은=== 연산자
sealed class
- enum 항목이 복잡한 로직을 가질 경우, 각 항목을 클래스로 정의하고, 이를 sealed class로 묶어서 사용 가능
- 유연한 데이터 저장, 복잡한 구조 가능
- 자신의 서브(자식) 클래스 종류를 제한하기 위해 사용된다.
- enum 클래스의 각 항목은 하나의 인스턴스만 생성되지만, sealed class에 속하는 서브 클래스들은 일반 클래스 이므로, 인스턴스 개수에 제한이 없다.
- 컴파일러는 해당 수퍼 클래스의 서브 클래스들을 컴파일 타임에 파악할 수 있게됨
when
표현식과 함께 사용될 때 모든 하위 클래스를 검사하므로,else
절이 필요하지 않습니다.- enum도 마찬가지
|
|
- 말 그대로 인터페이스이기 때문에 다중 상속을 통한 타입의 다형성을 부여할 수 있습니다.
- 다시 말해, 특정 타입의 성질을 하위 타입에게 전달하기가 쉽습니다.
- 또한,
when
문을 사용하여 타입을 참조하여 분기해야할 상황에서 장점이 명확해짐
enum의 한계
- 각 요소당 하나의 single instance를 사용하기 때문에 서로 다른 형태를 가질 수 없습니다.
|
|