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를 사용하기 때문에 서로 다른 형태를 가질 수 없습니다.
|  |  |