1. 기본 요소 : 함수와 변수
(1) 함수
a. 코틀린 문법의 특성
fun main(args: Array<String>){
println("Hello world")
}
- fun 키워드로 함수를 선언한다.
- 파라미터는 (파라미터 변수명: 타입) 로 선언한다.
- 함수를 최상위 수준에서 정의할 수 있다.
- 배열도 일반적인 클래스와 같다. 자바처럼 배열처리를 해줄 필요가 없다.
- println()같은 래퍼(Wrapper)를 제공한다.
- Wrapper : 여러가지 표준 자바 라이브러리 함수를 간결하게 사용할 수 있게 코틀린 표준 라이브러리에서 감싸준다.
- System.out.println → println
- Wrapper : 여러가지 표준 자바 라이브러리 함수를 간결하게 사용할 수 있게 코틀린 표준 라이브러리에서 감싸준다.
- ; (세미클론) 붙이지 않아도 된다.
b. 함수
fun main(args: Array<String>){
println(max(1,2))
}
fun max(a: Int, b: Int) : Int {
return if (a > b) a else b
// 코틀린에서 if는 식이라서 값을 만들어내며, 그래서 리턴값에 들어갈 수 있다.
}
- 코틀린에서 if는 식(expression)이지 문(장)(statement)이 아니다.
- 식 : 값을 만들어내며, 다른 식의 하위요소로 계산에 참여할 수 있다. (리턴값에 대입가능, 식이니까!)
- 문 : 자신을 둘러싸고 있는 가장 안쪽 블록의 최상위 요소, 아무런 값을 만들어내지못한다.
- if를 문으로 쓰면 이렇게 된다. 조건에 따라 a,b를 선택 한 것일뿐 만들어낸게 아니다.
if (a > b) { a // a가 b보다 크면 a를 그대로 사용 } else { b // 그렇지 않으면 b를 사용 }
- 식이 본문인 함수
- 등호를 제거했다.
- 식이 본문인 함수에서만! 반환타입인 Int도 생략 가능하다.
- 코틀린은 정적타입 언어 → 컴파일 단계에서 타입추론을 해주기 때문에!
//식이 본문인 함수
fun max(a: Int, b: Int) : Int = if (a > b) a else b
//블록이 본문인 함수
fun max(a: Int, b: Int) : Int {
return if (a > b) a else b
}
(2) 변수
a. 변경 가능한 변수와 변경 불가능한 변수
<aside> 📌 권장사항 기본적으로 모든 변수를 val 키워드를 사용해 불변 변수를 선언하자. 필요시 var로 변경. → 변경 불가능한 참조와 변경 불가능한 객체를 부수효과가 없는 함수와 조합해 사용하면 코드가 함수형코드에 가까워진다.
</aside>
- 변수 선언 키워드
- var : 변경 가능한 참조. 자바의 일반변수에 해당.
- val : 변경 불가능한 참조. 자바의 final 변수에 해당. 초기화 후 재대입 불가.
- val로 선언했어도, 그 참조가 가르키는 객체의 내부값은 변경될 수 있다.
fun main(args: Array<String>){ val language = arrayListOf("java") //불변 참조를 선언 language.add("kotlin") //참조를 가리키는 객체 내부를 변경한다. print(language) // [java, kotlin] }
b. 문자열 템플릿
- 문자열 리터럴 안에 변수를 넣을 수 있다. ${변수}
- 자바의 문자열 접합 연산과 동일한 기능 , 좀 더 효율적이다.
- 코틀린은 정적언어, 즉 컴파일 단계에서 해당 변수가 파악되지 않으면 에러가 난다.
- 컴파일된 코드는 StringBuilder를사용하고, 문자열 상수와 변수의 값을 append로 문자열 빌더뒤에 추가한다.
fun main(args: Array<String>) {
val name = if (args.size > 0) args[0] else "Kotlin"
print("Hello ${name}")
}
2. 클래스와 프로퍼티
자바 코드로 간단한 Person class를 만들어보자.
//JAVA
public class Person {
private final String name;
public Person(String name) {
this.name = name;
}
public String getName() {
return name;
}
}
코틀린으로 변환하면?
- 필드 대입 로직을 훨씬 더 적은 코드로 작성가능하다.
- 이런 유형의 클래스를 값 객체라 부른다
- 값 객체 (value object) : 코드 없이 데이터만 저장하는 클래스
- 이런 유형의 클래스를 값 객체라 부른다
//Kotlin - 1줄로 끝난다.
class Person(val name: String)
(1) 프로퍼티
<aside> 📌 클래스를 만드는 이유는? 데이터를 캡슐화하고, 캡슐화한 데이터를 다루는 코드를 한 class 파일 아래 가두는것이다. 클래스는 자신을 사용하는 개발자가 그 데이터에 접근하는 통로로써 접근자 메소드를 제공한다. (getter, setter)
</aside>
a. 프로퍼티란?
- 코틀린 프로퍼티는 자바의 필드와 접근자 메소드를 완전히 대신한다.
- var, val로 선언한다.
class Person(
val name: String, // 읽기전용 프로퍼티 : private 필드와 필드를 읽는 단순한 public getter를 만들어낸다.
var isMarried: Boolean // 쓸 수 있는 프로퍼티 : private 필드와 public getter/setter를 만들어낸다.
)
- 게터와 세터의 예외 규칙
- getter : 필드를 읽게 해줌
- setter : 필드 변경을 허용하게 해줌
- 자바
- //getisMarried가 아니다. System.out.println(person.isMarried); //setter를 이용할 땐 is를 빼고 setMarried()메소드를 사용한다. person.setMarried(false)
- 코틀린
- println(person.isMarried) //자바와 달리 코틀린은 대입. 직관적이다. person.isMarried = false
b. 커스텀 접근자
- 대부분의 프로퍼티에는 그 프로퍼티의 값을 저장하기 위한 필드가 있다.
- 이를 프로퍼티를 뒷받침하는 필드(backing field)라고 부른다.
- 커스텀 게터 : 하지만 커스텀 게터를 만들면, 프로퍼티값을 그때그때 계산할 수 있다.
- isSquare 프로퍼티에는 자체 값을 저장하는 필드가 필요없다. 자체 구현을 제공하는 게터만 존재한다.
- 개발자가 접근 할 때 마다 게터가 프로퍼티 값을 매번 다시 계산한다.
- isSquare 프로퍼티에는 자체 값을 저장하는 필드가 필요없다. 자체 구현을 제공하는 게터만 존재한다.
- class Rectangle(val height: Int, val width: Int) { val isSquare: Boolean get() { //프로퍼티 게터 선언 return height == width } }
c. 코틀린 소스코드 구조 : 디렉터리와 패키지
- 자바 : 모든 클래스를 패키지 단위로 관리한다.
- 코틀린 : 모든 코틀린 파일 맨 앞에 package 문을 넣을 수 있다.
- 그 파일안에 들어있는 클래스, 함수, 프로퍼트 등등이 패키지 안에 들어간다.
- 같은 패키지안에 있다면 다른 파일에서 정의한 선언일지라도 직접 사용할 수 있다.
- 다른 패키지 안에 있으면 import로 가져 올 수 있다.
(2) 선택 표현과 처리 : enum과 when
a. enum 클래스 정의
enum class Color {
RED, ORAGNE, YELLOW, GREEN, BLUE, INDIGO, VIOLET,
}
- 자바 선언보다 코틀린 선언에 더 많은 키워드를 써야하는 흔치 않은 예
- 코틀린은 enum class이고 자바에서는 enum을 사용한다.
- enum은 class 앞에 있을때만 특별한 의미를 지닌다. (다른곳에서는 이름으로 사용가능) → 소프트 키워드 특징 : class 앞이 아니면 다른곳에서도 쓸수있다.
- 반면, class는 키워드라서 다른곳에서 사용불가
- 프로퍼티, 메소드도 정의 가능하다.
- 코틀린에서 유일하게 ; 찍는 곳 : enum class에서 프로퍼티와 메소드 사이에 구분자로 찍어준다.
enum class Colors(val r: Int, val g: Int, val b: Int) {
RED(255, 0, 0), ORAGNE(255, 165, 0), YELLOW(255, 255, 0),
GREEN(0, 255, 0), BLUE(0, 0, 255), INDIGO(75, 0, 130), VIOLET(238, 130, 238);
fun rgb() = (r * 256 + g) * 256 + b
}
fun main(){
println(Colors.BLUE.rgb())
println(Colors.GREEN.rgb())
}
b. when으로 enum 클래스 다루기
- when도 if와 마찬가지로 값을 만들어내는 식이다.
fun main() {
println(getMnemonic(Colors.RED)) //Richard
}
fun getMnemonic(color: Colors) =
when (color) {
Colors.RED -> "Richard"
Colors.ORAGNE -> "Of"
Colors.YELLOW -> "York"
Colors.GREEN -> "Gave"
Colors.BLUE -> "Battle"
Colors.INDIGO -> "In"
Colors.VIOLET -> "Vain"
}
- 한 when 분기안에 여러 값 사용하기
fun getWarmth(color: Colors) =
when (color) {
Colors.RED, Colors.ORAGNE, Colors.YELLOW -> "warm"
Colors.GREEN, Colors.BLUE, Colors.INDIGO -> "neutral"
Colors.VIOLET -> "cold"
}
- 다른 class에서 import해서 가져오기
//ImportColorsTest class를 따로 만들어서 실행
import com.anehta.athenakotlinlab.Colors.*
fun getWarmth2(colors: Colors) = when (colors) {
RED, ORAGNE, YELLOW -> "warm" //import한 상수를 이름만으로 사용가능
GREEN, BLUE -> "neutral"
INDIGO, VIOLET -> "cold"
}
c. when과 임의의 객체를 함께 사용
- 자바의 switch 분기조건에는 enum상수나 숫자리터럴만 사용할 수 있지만, 코틀린 when의 분기조건은 임의의 객체를 허용한다.
//두 색을 혼합했을 때 미리 정해지 팔레트에 들어있는 색이 될 수 있는지 알려주는 함수
fun mix(c1: Colors, c2: Colors) =
when (setOf(c1,c2)) {
setOf(RED, YELLOW) -> ORAGNE
setOf(YELLOW, BLUE) -> GREEN
setOf(BLUE, VIOLET) -> INDIGO
else -> throw Exception("Dirty color")
}
fun main() {
println(mix(RED,YELLOW))
}
- setOf() 기능 : 인자로 전달 받은 여러 객체를, 그 객체들을 포함하는 집합인 Set 객체로 만들어준다.
- ex : setOf(RED, YELLOW)은 RED와 YELLOW로 구성된 Set 컬렉션을 만든다. 순서는 중요하지 않음.
- setOf(c1,c2)과 i. setOf(RED, YELLOW)을 비교하고 아니면 다음 분기로 넘어간다.
- setOf(c1,c2)와 ii. setOf(YELLOW, BLUE)를 비교하고 아니면 다음 분기로 넘어간다…. 계속….
- 하지만! 이 방법은 굉장히 비효율적이다. 두 색을 비교하기 위해 Set 인스턴스를 생성한다. 가비지 객체가 늘어난다.
d. 인자 없는 when 사용
- 인자가 없으면 불필요한 객체 생성을 막을 수 있다. 하지만 코드를 읽기 어려워질 수도 있다.
- when에 아무 인자도 없으려면 각 분기의 조건이 불리언 결과를 계산하는 식이어야한다.
- fun mix 처럼 추가객체를 만들지 않지만, 가독성이 떨어진다.
fun mixOptimized(c1: Colors, c2: Colors) =
when {
(c1 == RED && c2 == YELLOW) || (c1 == YELLOW && c2 == RED) -> ORAGNE
(c1 == BLUE && c2 == YELLOW) || (c1 == BLUE && c2 == YELLOW) -> GREEN
(c1 == BLUE && c2 == VIOLET) || (c1 == VIOLET && c2 == BLUE) -> INDIGO
else -> throw Exception("Dirty color")
}
(3) 스마트 캐스트 : 타입 검사와 타입 캐스트를 조합
a. (num1 + num2) + num4 와 같은 간단한 산술식을 계산하는 함수를 만들자.
식을 트리 구조로 저장하자. SUM{ (NUM(1)+NUM(2)) + NUM(4) }
interface Expr
class Num(val value: Int) : Expr
class Sum(val left: Expr, val right: Expr) : Expr
// Expr` 인터페이스는 아무 메소드도 선언하지 않으며,
// 단지 여러 타입의 식 객체를 아우르는 **공통 타입 역할**만 수행한다.
fun eval(e: Expr): Int {
if (e is Num) { // is로 Num 인지 검사한 다음부터 컴파일러는 e를 Num으로 해석한다.
// 그래서 Num에 있는 value를 명시적 캐스팅없이 e.value로 바로 사용 가능하다.
val n = e as Num
return n.value
}
if (e is Sum) {
return eval(e.right) + eval(e.left)
}
throw IllegalArgumentException("Unknown Expression")
}
- 코틀린에서는 프로그래머 대신 컴파일러가 캐스팅 해준다.
- 자바에서는 어떤 변수의 타입을 instanceof로 확인한 다음, 그 타입에 속한 멤버에 접근하기 위해서는 명시적으로 변수 타입을 캐스팅해야한다. 이런 멤버접근을 여러번 수행해야한다면, 변수에 따로 캐스팅한 결과를 저장한 후 사용해야한다.
- SmartCastIDE는 스마트캐스트라는것을 배경색깔로 표시해준다.
- 어떤 변수가 원하는 타입인지 일단 is로 검사하고 나면 굳이 변수를 원하는 타입으로 캐스팅하지 않아도 마치 처음부터 그 변수가 원하는 타입으로 선언된 것처럼 사용할 수있다. (실제로는 컴파일러가 캐스팅을 수행함)
- 스마트 캐스트는 is로 변수에 든 값의 타입을 검사한 다음에 그 값이 바뀔 수없는 경우에만 작동한다.
- 즉, Num에 들어있는 value는 val로 선언해야한다.
- 커스텀 접근자도 불가. (항상 같은 값을 내놓지 않는다.)
- 명시적 캐스팅은 as 키워드를 사용한다.
b. (num1 + num2) + num4를 좀 더 코틀린스럽게! if를 when으로!
- 코틀린 if와 자바 if의 차이점
- 코틀린 if (a > b) a else b 와 자바 삼항연산자 int result = (a > b) ? a : b; 는 같다.
- 코틀린의 if는 식이므로 삼항연산자가 따로 없다.
fun eval(e: Expr): Int =
if (e is Num) { //is로 Num 타입인지 확인 한 이후로 컴파일러는 e를 Num타입으로 해석한다.
e.value //
} else if (e is Sum) {
eval(e.left) + eval(e.right)
} else {
throw IllegalArgumentException("Unknown Expression")
}
// 좀 더 코틀린스럽게 when을 이용하자.
fun eval(e: Expr): Int =
when (e) {
is Num -> e.value
is Sum -> eval(e.left) + eval(e.right)
else -> throw IllegalArgumentException("Unknown Expression")
}
c. if와 when의 분기에서 블록 사용
- eval 함수에 로그를 추가해보자. 각 분기는 블록처리 후, 블록의 맨 마지막에 그 분기의 결과값을 위치시키면 된다.
fun main(){
println(evalWithLogging(Sum(Sum(Num(1),Num(2)),Num(4))))
}
num : 1
num : 2
sum : 3
num : 4
sum : 7
7
fun evalWithLogging(e: Expr): Int =
when (e) {
is Num -> {
println("num : ${e.value}")
e.value
}
is Sum -> {
val left = evalWithLogging(e.left)
val right = evalWithLogging(e.right)
println("sum : ${left + right}")
left + right
}
else -> throw IllegalStateException("Unknown Expression")
}
- 식이 본문인 함수는 블록을 가질 수 없다.
- 블록이 본문인 함수는 리턴값이 반드시 필요하다.
(4) 이터레이션 Iteration : while과 for 루프
- 이터레이션 : 결과를 내기위해 프로세스를 반복하는 작업. “그 문장의 블록은 이터레이션 되고 있다” 말한다.
a. while , do-while 루프
while (조건) {
본문 // 조건이 참인 동안 반복실행
}
--------------------
do {
본문
}
while (조건) {
} //일단 무조건 1회는 본문을 실행한다. 그 뒤에 조건이 맞으면 반복실행.
b. 수에 대한 이터레이션 : 범위와 수열
- 자바의 for 루프 (int i=0;i<=10;i++)을 사용하고, 코틀린에서는 범위를 사용한다.
- 코틀린 폐구간은 .. 연산자로 나타낸다. val oneToTen = 1..10
- 어떤 범위에 속한 값을, 일정한 순서대로 이터레이션 하는 것을 수열이라한다.
<aside> 💡 피즈버즈게임
- 3으로 나누어 떨어지는 숫자는 “피즈”
- 5로 나누어 떨어지는 숫자는 “버즈”
- 3,5 둘다 나누어 떨어지는 숫자는 “피즈버즈”를 외친다.
</aside>
fun main() {
for (i in 1..10) {
println(fizzBuzz(i))
}
println("--------")
for (i in 30 downTo 1 step 2) {
println(fizzBuzz(i))
}
}
fun fizzBuzz(i: Int) = when {
i % 15 == 0 -> "FizzBuzz"
i % 3 == 0 -> "Fizz"
i % 5 == 0 -> "Buzz"
else -> "${i}" // 셋 다 아니면 그 수 자체를 반환한다.
}
//출력값
1
2
Fizz
4
Buzz
Fizz
7
8
Fizz
Buzz
--------
FizzBuzz
28
26
Fizz
22
Buzz
Fizz
16
14
Fizz
Buzz
8
Fizz
4
2
c. 맵에 대한 이터레이션
fun main() {
val binaryReps = TreeMap<Char, String>()
for (c in 'A'..'F') { //문자의 범위를 사용해 이터레이션한다.
val binary = Integer.toBinaryString(c.toInt())
//정수를 2진으로 바꿔준다.
binaryReps[c] = binary
}
for ((letter, binary) in binaryReps) {
println("${letter} = ${binary}")
}
}
// 출력값
A = 1000001
B = 1000010
C = 1000011
D = 1000100
E = 1000101
F = 1000110
d. in으로 컬렉션이나 범위의 원소 검사
fun isLetter(c: Char) = c in 'a'..'z' || c in 'A'..'Z' //이 범위에 속하니?
fun isNotDigit(c: Char) = c !in '0'..'9' //이 범위 제외 맞지?
println(isLetter('q')) //true
println(isNotDigit('x')) //true
fun recognize(c: Char) = when (c) {
in '0'..'9' -> "It's digit!"
in 'a'..'z', in 'A'..'Z' -> "It's a letter!"
else -> "I don't know!"
}
println(recognize('C')) //It's a letter!
- 비교 가능한 클래스라면(java.lang.Comparable 인터페이스를 구현한 클래스), 그 클래스의 인스턴스객체를 사용해 범위를 만들 수 있다.
(5) 코틀린의 예외 처리
- 함수는 정상적으로 종료할 수 있지만, 오류가 발생하면 예외(throw)를 던질 수 있다.
- 함수를 호출하는쪽에서 예외를 잡아 처리한다.
- 만약 발생한 예외를 함수호출단에서 처리하지 않으면..?
- 함수 호출 스택을 거슬러 올라가면서 예외를 처리하는 부분이 나올때까지 다시 던진다. (rethrow)
val percentage = if (number in 0..100) number else throw IllegalArgumentException("sorry")
//조건에 들어가면 초기화, 아니면 예외를 던진다.
a. try, catch, finally
fun readNumber(reader: BufferedReader): Int? {
try {
val line = reader.readLine() // 1.입력받는다
return Integer.parseInt(line) // 2.입력값을 정수로 바꿔서 리턴한다.
} catch (e: NumberFormatException) {
return null //이때 예외가 발생하면 catch 블록이 실행된다. null을 리턴한다.
} finally {
reader.close() //항상실행하는 블록. 예외가 발생해도 실행된다. BufferedReader를 닫아준다.
}
}
- 자바처럼 throws 절 코드가 없다.
- 자바는 throws IOException을 붙여야한다.
- IOException : 체크 예외, 자바에서는 체크예외를 명시적으로 처리해야한다.
- 코틀린은 체크예외와 언체크예외를 구별하지 않는다. (최신 JVM 언어 특징)
b. try를 식으로 이용해서 코틀린스럽게!
fun main() {
val reader = BufferedReader(StringReader("333333"))
readNumber(reader) //333333
val reader2 = BufferedReader(StringReader("hello"))
readNumber(reader2) //null
}
fun readNumber(reader: BufferedReader) {
val number = try {
Integer.parseInt(reader.readLine())
} catch (e: NumberFormatException) {
null // 예외처리로 여겨지면 null을 리턴한다.
}
println(number)
}
</aside>
1. 기본 요소 : 함수와 변수
(1) 함수
a. 코틀린 문법의 특성
fun main(args: Array<String>){
println("Hello world")
}
- fun 키워드로 함수를 선언한다.
- 파라미터는 (파라미터 변수명: 타입) 로 선언한다.
- 함수를 최상위 수준에서 정의할 수 있다.
- 배열도 일반적인 클래스와 같다. 자바처럼 배열처리를 해줄 필요가 없다.
- println()같은 래퍼(Wrapper)를 제공한다.
- Wrapper : 여러가지 표준 자바 라이브러리 함수를 간결하게 사용할 수 있게 코틀린 표준 라이브러리에서 감싸준다.
- System.out.println → println
- Wrapper : 여러가지 표준 자바 라이브러리 함수를 간결하게 사용할 수 있게 코틀린 표준 라이브러리에서 감싸준다.
- ; (세미클론) 붙이지 않아도 된다.
b. 함수
fun main(args: Array<String>){
println(max(1,2))
}
fun max(a: Int, b: Int) : Int {
return if (a > b) a else b
// 코틀린에서 if는 식이라서 값을 만들어내며, 그래서 리턴값에 들어갈 수 있다.
}
- 코틀린에서 if는 식(expression)이지 문(장)(statement)이 아니다.
- 식 : 값을 만들어내며, 다른 식의 하위요소로 계산에 참여할 수 있다. (리턴값에 대입가능, 식이니까!)
- 문 : 자신을 둘러싸고 있는 가장 안쪽 블록의 최상위 요소, 아무런 값을 만들어내지못한다.
- if를 문으로 쓰면 이렇게 된다. 조건에 따라 a,b를 선택 한 것일뿐 만들어낸게 아니다.
if (a > b) { a // a가 b보다 크면 a를 그대로 사용 } else { b // 그렇지 않으면 b를 사용 }
- 식이 본문인 함수
- 등호를 제거했다.
- 식이 본문인 함수에서만! 반환타입인 Int도 생략 가능하다.
- 코틀린은 정적타입 언어 → 컴파일 단계에서 타입추론을 해주기 때문에!
//식이 본문인 함수
fun max(a: Int, b: Int) : Int = if (a > b) a else b
//블록이 본문인 함수
fun max(a: Int, b: Int) : Int {
return if (a > b) a else b
}
(2) 변수
a. 변경 가능한 변수와 변경 불가능한 변수
<aside> 📌 권장사항 기본적으로 모든 변수를 val 키워드를 사용해 불변 변수를 선언하자. 필요시 var로 변경. → 변경 불가능한 참조와 변경 불가능한 객체를 부수효과가 없는 함수와 조합해 사용하면 코드가 함수형코드에 가까워진다.
</aside>
- 변수 선언 키워드
- var : 변경 가능한 참조. 자바의 일반변수에 해당.
- val : 변경 불가능한 참조. 자바의 final 변수에 해당. 초기화 후 재대입 불가.
- val로 선언했어도, 그 참조가 가르키는 객체의 내부값은 변경될 수 있다.
fun main(args: Array<String>){ val language = arrayListOf("java") //불변 참조를 선언 language.add("kotlin") //참조를 가리키는 객체 내부를 변경한다. print(language) // [java, kotlin] }
b. 문자열 템플릿
- 문자열 리터럴 안에 변수를 넣을 수 있다. ${변수}
- 자바의 문자열 접합 연산과 동일한 기능 , 좀 더 효율적이다.
- 코틀린은 정적언어, 즉 컴파일 단계에서 해당 변수가 파악되지 않으면 에러가 난다.
- 컴파일된 코드는 StringBuilder를사용하고, 문자열 상수와 변수의 값을 append로 문자열 빌더뒤에 추가한다.
fun main(args: Array<String>) {
val name = if (args.size > 0) args[0] else "Kotlin"
print("Hello ${name}")
}
2. 클래스와 프로퍼티
자바 코드로 간단한 Person class를 만들어보자.
//JAVA
public class Person {
private final String name;
public Person(String name) {
this.name = name;
}
public String getName() {
return name;
}
}
코틀린으로 변환하면?
- 필드 대입 로직을 훨씬 더 적은 코드로 작성가능하다.
- 이런 유형의 클래스를 값 객체라 부른다
- 값 객체 (value object) : 코드 없이 데이터만 저장하는 클래스
- 이런 유형의 클래스를 값 객체라 부른다
//Kotlin - 1줄로 끝난다.
class Person(val name: String)
(1) 프로퍼티
<aside> 📌 클래스를 만드는 이유는? 데이터를 캡슐화하고, 캡슐화한 데이터를 다루는 코드를 한 class 파일 아래 가두는것이다. 클래스는 자신을 사용하는 개발자가 그 데이터에 접근하는 통로로써 접근자 메소드를 제공한다. (getter, setter)
</aside>
a. 프로퍼티란?
- 코틀린 프로퍼티는 자바의 필드와 접근자 메소드를 완전히 대신한다.
- var, val로 선언한다.
class Person(
val name: String, // 읽기전용 프로퍼티 : private 필드와 필드를 읽는 단순한 public getter를 만들어낸다.
var isMarried: Boolean // 쓸 수 있는 프로퍼티 : private 필드와 public getter/setter를 만들어낸다.
)
- 게터와 세터의 예외 규칙
- getter : 필드를 읽게 해줌
- setter : 필드 변경을 허용하게 해줌
- 자바
- //getisMarried가 아니다. System.out.println(person.isMarried); //setter를 이용할 땐 is를 빼고 setMarried()메소드를 사용한다. person.setMarried(false)
- 코틀린
- println(person.isMarried) //자바와 달리 코틀린은 대입. 직관적이다. person.isMarried = false
b. 커스텀 접근자
- 대부분의 프로퍼티에는 그 프로퍼티의 값을 저장하기 위한 필드가 있다.
- 이를 프로퍼티를 뒷받침하는 필드(backing field)라고 부른다.
- 커스텀 게터 : 하지만 커스텀 게터를 만들면, 프로퍼티값을 그때그때 계산할 수 있다.
- isSquare 프로퍼티에는 자체 값을 저장하는 필드가 필요없다. 자체 구현을 제공하는 게터만 존재한다.
- 개발자가 접근 할 때 마다 게터가 프로퍼티 값을 매번 다시 계산한다.
- isSquare 프로퍼티에는 자체 값을 저장하는 필드가 필요없다. 자체 구현을 제공하는 게터만 존재한다.
- class Rectangle(val height: Int, val width: Int) { val isSquare: Boolean get() { //프로퍼티 게터 선언 return height == width } }
c. 코틀린 소스코드 구조 : 디렉터리와 패키지
- 자바 : 모든 클래스를 패키지 단위로 관리한다.
- 코틀린 : 모든 코틀린 파일 맨 앞에 package 문을 넣을 수 있다.
- 그 파일안에 들어있는 클래스, 함수, 프로퍼트 등등이 패키지 안에 들어간다.
- 같은 패키지안에 있다면 다른 파일에서 정의한 선언일지라도 직접 사용할 수 있다.
- 다른 패키지 안에 있으면 import로 가져 올 수 있다.
(2) 선택 표현과 처리 : enum과 when
a. enum 클래스 정의
enum class Color {
RED, ORAGNE, YELLOW, GREEN, BLUE, INDIGO, VIOLET,
}
- 자바 선언보다 코틀린 선언에 더 많은 키워드를 써야하는 흔치 않은 예
- 코틀린은 enum class이고 자바에서는 enum을 사용한다.
- enum은 class 앞에 있을때만 특별한 의미를 지닌다. (다른곳에서는 이름으로 사용가능) → 소프트 키워드 특징 : class 앞이 아니면 다른곳에서도 쓸수있다.
- 반면, class는 키워드라서 다른곳에서 사용불가
- 프로퍼티, 메소드도 정의 가능하다.
- 코틀린에서 유일하게 ; 찍는 곳 : enum class에서 프로퍼티와 메소드 사이에 구분자로 찍어준다.
enum class Colors(val r: Int, val g: Int, val b: Int) {
RED(255, 0, 0), ORAGNE(255, 165, 0), YELLOW(255, 255, 0),
GREEN(0, 255, 0), BLUE(0, 0, 255), INDIGO(75, 0, 130), VIOLET(238, 130, 238);
fun rgb() = (r * 256 + g) * 256 + b
}
fun main(){
println(Colors.BLUE.rgb())
println(Colors.GREEN.rgb())
}
b. when으로 enum 클래스 다루기
- when도 if와 마찬가지로 값을 만들어내는 식이다.
fun main() {
println(getMnemonic(Colors.RED)) //Richard
}
fun getMnemonic(color: Colors) =
when (color) {
Colors.RED -> "Richard"
Colors.ORAGNE -> "Of"
Colors.YELLOW -> "York"
Colors.GREEN -> "Gave"
Colors.BLUE -> "Battle"
Colors.INDIGO -> "In"
Colors.VIOLET -> "Vain"
}
- 한 when 분기안에 여러 값 사용하기
fun getWarmth(color: Colors) =
when (color) {
Colors.RED, Colors.ORAGNE, Colors.YELLOW -> "warm"
Colors.GREEN, Colors.BLUE, Colors.INDIGO -> "neutral"
Colors.VIOLET -> "cold"
}
- 다른 class에서 import해서 가져오기
//ImportColorsTest class를 따로 만들어서 실행
import com.anehta.athenakotlinlab.Colors.*
fun getWarmth2(colors: Colors) = when (colors) {
RED, ORAGNE, YELLOW -> "warm" //import한 상수를 이름만으로 사용가능
GREEN, BLUE -> "neutral"
INDIGO, VIOLET -> "cold"
}
c. when과 임의의 객체를 함께 사용
- 자바의 switch 분기조건에는 enum상수나 숫자리터럴만 사용할 수 있지만, 코틀린 when의 분기조건은 임의의 객체를 허용한다.
//두 색을 혼합했을 때 미리 정해지 팔레트에 들어있는 색이 될 수 있는지 알려주는 함수
fun mix(c1: Colors, c2: Colors) =
when (setOf(c1,c2)) {
setOf(RED, YELLOW) -> ORAGNE
setOf(YELLOW, BLUE) -> GREEN
setOf(BLUE, VIOLET) -> INDIGO
else -> throw Exception("Dirty color")
}
fun main() {
println(mix(RED,YELLOW))
}
- setOf() 기능 : 인자로 전달 받은 여러 객체를, 그 객체들을 포함하는 집합인 Set 객체로 만들어준다.
- ex : setOf(RED, YELLOW)은 RED와 YELLOW로 구성된 Set 컬렉션을 만든다. 순서는 중요하지 않음.
- setOf(c1,c2)과 i. setOf(RED, YELLOW)을 비교하고 아니면 다음 분기로 넘어간다.
- setOf(c1,c2)와 ii. setOf(YELLOW, BLUE)를 비교하고 아니면 다음 분기로 넘어간다…. 계속….
- 하지만! 이 방법은 굉장히 비효율적이다. 두 색을 비교하기 위해 Set 인스턴스를 생성한다. 가비지 객체가 늘어난다.
d. 인자 없는 when 사용
- 인자가 없으면 불필요한 객체 생성을 막을 수 있다. 하지만 코드를 읽기 어려워질 수도 있다.
- when에 아무 인자도 없으려면 각 분기의 조건이 불리언 결과를 계산하는 식이어야한다.
- fun mix 처럼 추가객체를 만들지 않지만, 가독성이 떨어진다.
fun mixOptimized(c1: Colors, c2: Colors) =
when {
(c1 == RED && c2 == YELLOW) || (c1 == YELLOW && c2 == RED) -> ORAGNE
(c1 == BLUE && c2 == YELLOW) || (c1 == BLUE && c2 == YELLOW) -> GREEN
(c1 == BLUE && c2 == VIOLET) || (c1 == VIOLET && c2 == BLUE) -> INDIGO
else -> throw Exception("Dirty color")
}
(3) 스마트 캐스트 : 타입 검사와 타입 캐스트를 조합
a. (num1 + num2) + num4 와 같은 간단한 산술식을 계산하는 함수를 만들자.
식을 트리 구조로 저장하자. SUM{ (NUM(1)+NUM(2)) + NUM(4) }
interface Expr
class Num(val value: Int) : Expr
class Sum(val left: Expr, val right: Expr) : Expr
// Expr` 인터페이스는 아무 메소드도 선언하지 않으며,
// 단지 여러 타입의 식 객체를 아우르는 **공통 타입 역할**만 수행한다.
fun eval(e: Expr): Int {
if (e is Num) { // is로 Num 인지 검사한 다음부터 컴파일러는 e를 Num으로 해석한다.
// 그래서 Num에 있는 value를 명시적 캐스팅없이 e.value로 바로 사용 가능하다.
val n = e as Num
return n.value
}
if (e is Sum) {
return eval(e.right) + eval(e.left)
}
throw IllegalArgumentException("Unknown Expression")
}
- 코틀린에서는 프로그래머 대신 컴파일러가 캐스팅 해준다.
- 자바에서는 어떤 변수의 타입을 instanceof로 확인한 다음, 그 타입에 속한 멤버에 접근하기 위해서는 명시적으로 변수 타입을 캐스팅해야한다. 이런 멤버접근을 여러번 수행해야한다면, 변수에 따로 캐스팅한 결과를 저장한 후 사용해야한다.
- SmartCastIDE는 스마트캐스트라는것을 배경색깔로 표시해준다.
- 어떤 변수가 원하는 타입인지 일단 is로 검사하고 나면 굳이 변수를 원하는 타입으로 캐스팅하지 않아도 마치 처음부터 그 변수가 원하는 타입으로 선언된 것처럼 사용할 수있다. (실제로는 컴파일러가 캐스팅을 수행함)
- 스마트 캐스트는 is로 변수에 든 값의 타입을 검사한 다음에 그 값이 바뀔 수없는 경우에만 작동한다.
- 즉, Num에 들어있는 value는 val로 선언해야한다.
- 커스텀 접근자도 불가. (항상 같은 값을 내놓지 않는다.)
- 명시적 캐스팅은 as 키워드를 사용한다.
b. (num1 + num2) + num4를 좀 더 코틀린스럽게! if를 when으로!
- 코틀린 if와 자바 if의 차이점
- 코틀린 if (a > b) a else b 와 자바 삼항연산자 int result = (a > b) ? a : b; 는 같다.
- 코틀린의 if는 식이므로 삼항연산자가 따로 없다.
fun eval(e: Expr): Int =
if (e is Num) { //is로 Num 타입인지 확인 한 이후로 컴파일러는 e를 Num타입으로 해석한다.
e.value //
} else if (e is Sum) {
eval(e.left) + eval(e.right)
} else {
throw IllegalArgumentException("Unknown Expression")
}
// 좀 더 코틀린스럽게 when을 이용하자.
fun eval(e: Expr): Int =
when (e) {
is Num -> e.value
is Sum -> eval(e.left) + eval(e.right)
else -> throw IllegalArgumentException("Unknown Expression")
}
c. if와 when의 분기에서 블록 사용
- eval 함수에 로그를 추가해보자. 각 분기는 블록처리 후, 블록의 맨 마지막에 그 분기의 결과값을 위치시키면 된다.
fun main(){
println(evalWithLogging(Sum(Sum(Num(1),Num(2)),Num(4))))
}
num : 1
num : 2
sum : 3
num : 4
sum : 7
7
fun evalWithLogging(e: Expr): Int =
when (e) {
is Num -> {
println("num : ${e.value}")
e.value
}
is Sum -> {
val left = evalWithLogging(e.left)
val right = evalWithLogging(e.right)
println("sum : ${left + right}")
left + right
}
else -> throw IllegalStateException("Unknown Expression")
}
- 식이 본문인 함수는 블록을 가질 수 없다.
- 블록이 본문인 함수는 리턴값이 반드시 필요하다.
(4) 이터레이션 Iteration : while과 for 루프
- 이터레이션 : 결과를 내기위해 프로세스를 반복하는 작업. “그 문장의 블록은 이터레이션 되고 있다” 말한다.
a. while , do-while 루프
while (조건) {
본문 // 조건이 참인 동안 반복실행
}
--------------------
do {
본문
}
while (조건) {
} //일단 무조건 1회는 본문을 실행한다. 그 뒤에 조건이 맞으면 반복실행.
b. 수에 대한 이터레이션 : 범위와 수열
- 자바의 for 루프 (int i=0;i<=10;i++)을 사용하고, 코틀린에서는 범위를 사용한다.
- 코틀린 폐구간은 .. 연산자로 나타낸다. val oneToTen = 1..10
- 어떤 범위에 속한 값을, 일정한 순서대로 이터레이션 하는 것을 수열이라한다.
💡 피즈버즈게임
- 3으로 나누어 떨어지는 숫자는 “피즈”
- 5로 나누어 떨어지는 숫자는 “버즈”
- 3,5 둘다 나누어 떨어지는 숫자는 “피즈버즈”를 외친다.
fun main() {
for (i in 1..10) {
println(fizzBuzz(i))
}
println("--------")
for (i in 30 downTo 1 step 2) {
println(fizzBuzz(i))
}
}
fun fizzBuzz(i: Int) = when {
i % 15 == 0 -> "FizzBuzz"
i % 3 == 0 -> "Fizz"
i % 5 == 0 -> "Buzz"
else -> "${i}" // 셋 다 아니면 그 수 자체를 반환한다.
}
//출력값
1
2
Fizz
4
Buzz
Fizz
7
8
Fizz
Buzz
--------
FizzBuzz
28
26
Fizz
22
Buzz
Fizz
16
14
Fizz
Buzz
8
Fizz
4
2
c. 맵에 대한 이터레이션
fun main() {
val binaryReps = TreeMap<Char, String>()
for (c in 'A'..'F') { //문자의 범위를 사용해 이터레이션한다.
val binary = Integer.toBinaryString(c.toInt())
//정수를 2진으로 바꿔준다.
binaryReps[c] = binary
}
for ((letter, binary) in binaryReps) {
println("${letter} = ${binary}")
}
}
// 출력값
A = 1000001
B = 1000010
C = 1000011
D = 1000100
E = 1000101
F = 1000110
d. in으로 컬렉션이나 범위의 원소 검사
fun isLetter(c: Char) = c in 'a'..'z' || c in 'A'..'Z' //이 범위에 속하니?
fun isNotDigit(c: Char) = c !in '0'..'9' //이 범위 제외 맞지?
println(isLetter('q')) //true
println(isNotDigit('x')) //true
fun recognize(c: Char) = when (c) {
in '0'..'9' -> "It's digit!"
in 'a'..'z', in 'A'..'Z' -> "It's a letter!"
else -> "I don't know!"
}
println(recognize('C')) //It's a letter!
- 비교 가능한 클래스라면(java.lang.Comparable 인터페이스를 구현한 클래스), 그 클래스의 인스턴스객체를 사용해 범위를 만들 수 있다.
(5) 코틀린의 예외 처리
- 함수는 정상적으로 종료할 수 있지만, 오류가 발생하면 예외(throw)를 던질 수 있다.
- 함수를 호출하는쪽에서 예외를 잡아 처리한다.
- 만약 발생한 예외를 함수호출단에서 처리하지 않으면..?
- 함수 호출 스택을 거슬러 올라가면서 예외를 처리하는 부분이 나올때까지 다시 던진다. (rethrow)
val percentage = if (number in 0..100) number else throw IllegalArgumentException("sorry")
//조건에 들어가면 초기화, 아니면 예외를 던진다.
a. try, catch, finally
fun readNumber(reader: BufferedReader): Int? {
try {
val line = reader.readLine() // 1.입력받는다
return Integer.parseInt(line) // 2.입력값을 정수로 바꿔서 리턴한다.
} catch (e: NumberFormatException) {
return null //이때 예외가 발생하면 catch 블록이 실행된다. null을 리턴한다.
} finally {
reader.close() //항상실행하는 블록. 예외가 발생해도 실행된다. BufferedReader를 닫아준다.
}
}
- 자바처럼 throws 절 코드가 없다.
- 자바는 throws IOException을 붙여야한다.
- IOException : 체크 예외, 자바에서는 체크예외를 명시적으로 처리해야한다.
- 코틀린은 체크예외와 언체크예외를 구별하지 않는다. (최신 JVM 언어 특징)
b. try를 식으로 이용해서 코틀린스럽게!
fun main() {
val reader = BufferedReader(StringReader("333333"))
readNumber(reader) //333333
val reader2 = BufferedReader(StringReader("hello"))
readNumber(reader2) //null
}
fun readNumber(reader: BufferedReader) {
val number = try {
Integer.parseInt(reader.readLine())
} catch (e: NumberFormatException) {
null // 예외처리로 여겨지면 null을 리턴한다.
}
println(number)
}
'Kotlin in Action' 카테고리의 다른 글
4. kotlin 클래스, 객체, 인터페이스 (0) | 2024.07.13 |
---|---|
3. kotlin 컬렉션, 확장함수, 정규식 (0) | 2024.07.13 |
1. 코틀린이란? (0) | 2024.07.13 |