안녕하세요 Samuel 입니다.
클래스와 프로퍼티
클래스
- Java
- public class Person { private final String name; public Person(String name) { this.name = name ; } public String getName(){ return name; } }
- Kotlin위의 내용은 동일한 클래스로 "값 객체(value object)"라 부르며, 다양한 언어가 값 객체를 간결하게 기술할 수 있는 구문을 제공합니다.
- Java와의 차이점 public 가시성 변경자 (visibility modifier)가 사라졌습니다. 코틀린의 기본 가시성은 public 이므로 이런 경우 변경자를 생략해도 됩니다.
- class Person(val name: String)
프로퍼티
- Java와의 차이점 Java : 프로퍼티 = 필드 + 접근자
- 자바에서 데이터를 field에 저장, 멤버 필드의 가시성은 보통 private.
- 데이터에 대한 접근 방식은, 접근자 메소드(accessor method) - getter, setter
- Kotlin : 자바의 프로퍼티를 완전히 대체
- 데이터 호출
// new 키워드를 사용하지 않고 생성자를 호출한다. val person = Person("Bob, true) // 프로퍼티 이름을 직접 사용해도 코틀린에서는 자동으로 getter를 호출해준다. println(person.name) Bob println(person.isMarried) true
- 데이터 주입
// Java person.setMarried(false) // Kotlin person.isMarried = false
- class Person( // 읽기 전용 프로퍼티로, 코틀린은 (비공개) 필드와 필드를 읽는 단순한 (공개)getter를 만들어 냅니다. val name : String, // 쓸 수 있는 프로퍼티로, 코틀린은 (비공개) 필드, (공개)getter, (공개)setter를 만들어낸다. var isMarried : Boolean )
커스텀 접근자
class Rectangle (val height : Int, val width : Int){
val isSquare : Boolean
// 프로퍼티 getter 선언
get() = height == width
// val isSquare = height == width 도 가능
}
//Result
>>> val rectangle = Rectangle(41,43)
>>> println(rectangle.isSquare)
false
디렉터리와 패키지
- Java와의 차이점 Java
- 패키지의 구조와 일치하는 디렉터리 계층 구조를 만들고 클래스 소스코드를 그 클래스가 속한 패키지와 같은 디렉터리에 위치해야 합니다.
- Kotlin
- 클래스 임포트와 함수 임포트에 차이가 없으며, 모든 선언을 import 키워드로 가져올 수 있습니다. 최상위 함수는 그 이름을 써서 임포트 할 수 있습니다.
- 여러 클래스를 한 파일에 넣을 수 있고, 파일의 이름도 마음대로 정할 수 있습니다.
- 코틀린에서는 디스크상의 어느 디렉터리에 소스코드 파일을 위치시키든 관계없습니다.
- 따라서 원하는대로 소스코드를 구성할 수 있습니다.
- 하지만 대부분의 경우 자바와 같이 패키지별로 디렉터리를 구성하는 편이 좋습니다. 특히 자바와 코틀린을 함께 사용하는 프로젝트에서는 자바의 방식을 따르는 것이 중요합니다.
enum 과 when
enum
- **Java와의 차이점**Java에서는 enum을 사용하지만, Kotlin에서는 enum class라고 사용합니다.
- Kotlin에서의 enum 키워드는 class 앞에 있을때 특별한 의미를 지니고 다른 곳에서는 이름으로 사용할 수 있습니다.
- Kotlin에서 enum 클래스 안에도 프로퍼티나 메소드를 정의할 수 있습니다.
- enum클래스 안에 메서드를 정의하는 경우 반드시 enum상수 목록과 메서드 정의 사의에 세미콜론을 넣어야 합니다.
- enum class Color( //상수의 프로퍼티를 정의한다. val r :Int, val g: Int, val b: Int ){ //각 상수를 생성할때, 그에 대한 프로퍼티 값을 지정한다. RED(255,0,0),ORANGE(255,165,0), YELLOW(255,255,0), GREEN(0,255,0), BLUE(0,0,255), INDIGO(75,0,130),VIOLET(238,130,238); // 이 곳에서는 반드시 세미콜론을 사용해야한다. // enum 클래스 안에서 메서드를 정의한다. fun rgb() = (r*236 +g) * 256 + b } >>> println(Color.BLUE.rgb()) 255
when
- **Java와의 차이점**Java와 다르게 각 분기의 끝에 break를 넣지 않아도 됩니다.
- 각 키워드 별로 매칭값을 가져올때의 경우입니다.
fun getMnemonic(color: Color) =
when(color) {
Color.RED -> "Richard"
Color.ORANGE -> "Of"
Color.YELLOW -> "York"
Color.GREEN -> "Gave"
Color.BLUE -> "Blue"
Color.INDIGO -> "In"
Color.VIOLET -> "Vain"
}
- 다수 : 1 의 관계의 경우, 아래와 같이 컴마를 사용하여 작업합니다.
fun getWarmth(color: Color) =
when(color) {
Color.RED ,
Color.ORANGE ,
Color.YELLOW -> "warm"
Color.GREEN -> "neutral"
Color.BLUE ,
Color.INDIGO ,
Color.VIOLET -> "cold"
}
- when의 분기 조건에 여러 다른 객체 사용하기
fun mix (c1: Color, c2: Color) =
when(setOf(c1,c2)){
setOf(Color.RED, Color.YELLOW) -> Color.ORANGE
setOf(Color.YELLOW,Color.BLUE) -> Color.GREEN
setOf(Color.BLUE, Color.VIOLET) -> Color.INDIGO
else -> throw Exception("Dirty color")
}
setOf : 인자로 전달받은 여러 객체를 그 객체들을 포함한 집합인 Set 객체로 만들어 줍니다. Set(집합)은 원소가 모여있는 컬렉션으로 각 원소의 순서는 중요하지 않습니다.
<aside> 💡 분기 조건에 있는 객체 사이를 매치할때, 동등성(equility)을 사용합니다. 위에서 처음으로 RED, YELLOW인지 파악하고, 그 다음 차례대로 비교하는 식으로 작동하고 마지막으로 else로 들어갑니다.
</aside>
<aside> 💡 추가적으로 setOf와 같이 Set 인스턴스를 생성하는 작업은 비효율적인 부분으로 작용할 수 있습니다. 큰 문제는 아니지만 이 함수가 아주 자주 호출된다면 불필요한 가비지 객체가 늘어나는 것을 방지하기 위해 함수를 고쳐 쓰는 편이 좋습니다. "인자가 없는 when"을 사용하면불필요한 객체 생성을 막을 수 있습니다.
</aside>
// 반면 가독성은 현저히 떨어집니다.
fun mixOptimized(c1: Color, c2: Color) =
when{
(c1 == Color.RED && c2 == Color.YELLOW) || (c1 == Color.YELLOW && c2 == Color.RED) -> Color.ORANGE
// ... 생략
else -> throw Exception("Dirty color")
}
스마트 캐스트 : 타입 검사와 타입 캐스트를 조합
간단하게 (1+2)+4를 수행하는 기능을 만들어 봅니다.
interface Expr
class Num(val value : int) : Expr
class Sum(val left : Expr, val right : Expr) : Expr
Num은 입력을 받은 값을 반환하고, Sum은 왼쪽 오른쪽 값을 받아 더한 뒤에 그 값을 반환합니다.
fun eval(e:Expr) : Int{
if (e is Num) {
val n = e as Num // 불필요한 타입 변환
return n.value
}
if (e is Sum){
return eval(e.left) + eval(e.right) // 변수 e에 대한 스마트 캐스트
}
throw IllegalArgumentException("Unknown expression")
}
위와 같이 eval 이란 메서드를 만들었습니다.
2번째 줄을 e가 Num을 가질때, 값을 그대로 반환합니다. 6번째 줄을 보면 e가 Sum일때, 왼쪽과 오른쪽을 더한 값을 반환합니다.
이제 여기서 스마트 캐스트 기능이 나옵니다. 3번째 줄을 보면 e as Num이 있는데 e라는 인터페이스를 Num으로 타입 캐스팅을 하고 있습니다. 사실 코틀린에서는 위와 같은 상황에서는 캐스팅을 해줄 필요가 없습니다. is라는 키워드는 검사를 한 뒤에 자동으로 캐스팅을 해주기 때문입니다. (이 부분이 바로 스마트 캐스트 입니다.) 그렇기 때문에 코드 3번째 줄을 무의미한 코드가 됩니다.
한편, 7번째 줄을 보면 e is Sum으로 인해 e는 이미 Sum으로 스마트 캐스팅 되었습니다.
정리하자면, 코틀린에서 is로 검사한 뒤 "프로그래머 대신 컴파일러가 캐스팅을 해줍니다." 그렇기 때문에 is로 검사하고 나면 굳이 원하는 변수로 캐스팅을 할 필요가 없습니다.
스마트 캐스팅이 됐는지 확인하는 방법은 IDE를 이용하는 방법이 있습니다. e에 색상이 입혀져 있는것을 확인할 수 있습니다.
- if문을 이용해서 , 코틀린 답게 코드 짜본다면
fun eval2(e:Expr): Int =
if (e is Num) {
e.value
} else if (e is Sum) {
eval2(e.left) + eval2(e.right)
} else {
throw IllegalArgumentException("Unknown expression")
}
fun main(arg:Array<String>){
println(eval2(Sum(Sum(Num(3), Num(3)), Num(4))))
}
- When을 이용해서 코틀린 답게 코드를 짜본다면
fun eval3(e:Expr):Int =
when(e) {
is Num -> e.value
is Sum -> eval3(e.left) + eval3(e.right)
else -> throw IllegalArgumentException("Unknown expression") }
fun main(arg: Array<String>){
println(eval3(Sum(Sum(Num(10), Num(7)), Num(3))))
}
배열
코틀린에서는 배열을 Array 클래스로 정의되어 있습니다. 그리고 Array<String>처럼 배열에 저장되는 요소의 타입을 제네릭 타입으로 나타냅니다.
- Java와의 차이점 대괄호는 배열을 선언할때 사용하지 않고, 배열의 각 요소를 읽거나 쓸때만 사용합니다.
val arr = arrayOf(1,2,3)
// arr[0] = 1, arr[1] = 2
val arr2 = arrayOfNulls<Int>(5)
//null 값을 허용하는 경우
// size는 5, 각각의 요소들을 0,1,2,3,4로 초기화
val arr3 = Array<String>(5,{i->i.toString()})
val arr4 = Array(5,{i->i.toString()})
// forEach의 it은 키워드이며, 배열의 각 요소를 가르킨다.
fun main(args: Array<String>){
val arr3 = Array<String>(5){i->i.toString()}
arr3.forEach {print(it)}
}
>>> 01234
- 코틀린은 기본타입의 요소를 저장하는 배열을 별도의 클래스로 가지고 있습니다. ByteArray, ShortArray, IntArray...
'책을 읽어봅시다 > Kotlin in Action' 카테고리의 다른 글
람다로 프로그래밍(1) (0) | 2022.03.03 |
---|---|
JAVA와 차이점 그리고 클래스, 객체, 인터페이스 (0) | 2022.03.02 |
Why Kotlin (0) | 2022.02.19 |