초보자를위한 Kotlin 200제(1)
69. 클래스를 상속하는 객체
1
2
3
4
5
6
7
8
9
10
11
12
13
14
open class Person(val name: String, val age: Int) {
open fun print() {
println("이름:$name, 나이:$age")
}
}
fun main(args: Array<String>) {
val custom: Person = object : Person("Alan", 23) {
override fun print() {
println("it's a object")
}
}
custom.print()
}
- 클래스 없이 object 표현식을 사용해 상속을 할 수 있다.
- 이때의 상속은 1회용이 된다.
70. Any 클래스
- 모든 코틀린 클래스들은 Any 클래스를 상속한다.
1
2
3
4
5
open class Any {
open operator fun equals(other: Any?): Boolean
open fun hashCode(): Int
open fun toString(): String
}
- equals() : == 연산자를 오버로딩하는 멤버 함수
- hashCode() : 객체 고유의 해시코드를 반환하는 멤버 함수
- toString() : 객체의 내용을 String 타입으로 반환하는 멤버 함수
1
2
3
4
5
6
7
8
class Person(val name: String, val age: Int) {
override fun toString() = "이름:$name 나이:$age"
}
fun main(args: Array<String>) {
val custom = Person("a", 23)
println(custom.toString())
}
71. 예외 (Exception)
1
2
3
4
5
6
fun main(args: Array<String>) {
val str = "abcd"
val num = str.toInt()
println(num)
}
1
2
3
4
5
Exception in thread "main" java.lang.NumberFormatException: For input string: "abcd"
at java.base/java.lang.NumberFormatException.forInputString(NumberFormatException.java:68)
at java.base/java.lang.Integer.parseInt(Integer.java:658)
at java.base/java.lang.Integer.parseInt(Integer.java:776)
at TestKt.main(Test.kt:7)
- str에 들어있는 문자열을 Int 타입으로 변환하려 해서 NumberFormatException이 발생한다.
72. 예외 처리
1
2
3
4
5
6
7
8
9
10
11
12
fun main(args: Array<String>) {
try {
val str = "abcd"
val num = str.toInt()
println(num)
} catch (e: NumberFormatException) {
println("문자열을 숫자로 변경하지 못함")
} finally {
println("프로그램 종료")
}
}
- 예외가 발생할 가능성이 있는 부분을 try 블록으로 감싸줘서 예외를 처리한다.
73. 예외 던지기
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
fun main(args: Array<String>) {
try {
something()
} catch (e: Exception) {
println(e.message)
}
}
fun something() {
val num1 = 10
val num2 = 0
div(num1, num2)
}
fun div(a: Int, b: Int): Int {
if (b == 0) throw Exception("0으로 나눌 수 없습니다.")
return a / b
}
- 예외를 던져서 자신을 호출했던 메소드로 예외 처리의 책임을 전가시킨다.
74. Nothing 타입
1
2
3
4
5
6
7
fun throwing(): Nothing = throw Exception()
fun main(args: Array<String>) {
println("start")
val i: Int = throwing()
println(i)
}
- Nothing은 리턴이라는 행위 자체를 하지 않음을 뜻한다.
1
2
3
4
fun validate(num: Int): Int {
return if (num > 0) num
else throw Exception("num이 음수입니다.")
}
- 위의 예제에서 if 블록은 Int 타입, else 블록이 Nothing 타입이면, if-else는 Int 타입을 따라간다.
- Nothing 타입은 throw를 표현식으로 쓸 수 있게 하기 위한 장치다.
75. Nullable 타입과 null
- Nullable이란, null 값을 지정할 수 있는 변수를 뜻한다.
- 타입 이름 뒤에 ?를 붙이면 변수를 Nullable하게 만들 수 있다.
1
2
3
fun main(args: Array<String>) {
val str : String? = if (true) "test" else null
}
- “test”는 String, null은 Nothing? 타입이므로, if-else 표현식의 타입은 이 둘이 합쳐진 String?이 된다.
76. 안전한 호출 연산자 - ?.
- Nullable한 참조 변수의 프로퍼티와 멤버 함수에 접근하려면 . 대신 ?. 연산자를 사용해야 한다.
1
2
3
4
5
6
7
8
9
10
11
12
13
class Building(var name: String){
fun print(){
println("name : $name")
}
}
fun main(args: Array<String>) {
var obj: Building? = null
obj?.print() // print() 함수가 호출되지 않는다.
obj = Building("백화점")
obj?.print() // print() 함수 호출된다.
}
77. Not-null 단정 연산자 - !!
- !! 연산자는 Nullable 타입을 Not-null 타입으로 강제로 캐스팅한다.
1
2
3
4
5
6
class Building(var name: String)
fun main(args: Array<String>) {
val obj: Building? = null
obj!!.name = "백화점"
}
- obj는 null이기 때문에 obj!!.name에서 KotlinNullPointerException이 발생한다.
78. 엘비스 연산자 - ?:
- 엘비스 연산자는 왼쪽의 피연산자가 null이 아니면 그 값을 그대로 쓰고, null이면 우측의 피연산자로 대체하는 연산자이다.
1
2
3
4
5
6
7
fun main(args: Array<String>) {
val num1: Int? = null
println(num1 ?: 0) // 0
val num2: Int? = 15
println(num2 ?: 0) // 15
}
79. 스마트 캐스팅
- 특정 조건을 만족하는 경우, 컴파일러는 변수의 타입을 다른 타입으로 자동 캐스팅하는 것을 스마트 캐스팅이라 한다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
fun main(args: Array<String>) {
val num1: Int? = null
val num2 = 1225
checkNull(num1)
checkNull(num2)
}
fun checkNull(any: Any?) {
if (any == null) {
println("null이 들어왔습니다.")
return
}
println(any.toString())
}
if (any == null)
에서 any가 null이면 return 하므로, 아래에서any?.toString()
이 아닌any.toString()
로 호출이 가능하다.
80. is 연산자
- is 연산자로 참조 변수가 실제로 가리키고 있는 객체의 타입을 알아낼 수 있다.
1
2
3
4
5
6
7
8
9
10
11
open class Person(name: String, age: Int)
class Student(name: String, age: Int): Person(name, age)
class Professor(name: String, age: Int): Person(name, age)
fun main(args: Array<String>) {
val person1: Person = Student("a", 1)
println(person1 is Person) // true
println(person1 is Student) // true
println(person1 is Professor) // false
}
- 코틀린의 is 연산자는 자바의 instanceof에 해당한다.
81. as 연산자와 다운캐스팅
- 다운캐스팅은 업캐스팅과는 반대로 슈퍼클래스의 타입을 서브클래스 타입으로 받는 것을 뜻한다.
1
2
3
4
5
6
7
8
9
10
open class Person(name: String, age: Int)
class Student(name: String, age: Int): Person(name, age)
fun main(args: Array<String>) {
val person: Person = Student("John", 32)
val person2: Person = Person("Jack", 27)
var person3: Student = person as Student
person3 = person2 as Student // 예외 발생
}
-
person2 참조 변수는 Person의 인스턴스를 가리키고 있으므로, ClassCastException 예외가 발생한다.
-
캐스팅에 실패했을 때 예외가 발생하는 것을 막고 싶으면 as? 연산자를 대신 사용하면 된다.
1
2
3
4
fun main(args: Array<String>) {
val person: Person = Person("Jack", 27)
var person2: Student? = person as? Student
}
82. 접근 지정자 (Access Modifier)
- public : 모든 곳에서 접근 가능, 접근 지정자를 생략하면 기본적으로 public이 된다.
- internal : 같은 모듈 안에서 접근 가능. (IntelliJ IDEA 모듈, Maven / Gradle 프로젝트 모듈)
- protected : 클래스 내부와, 서브클래스 안에서만 접근 가능.
- private : 프로퍼티와 멤버 함수일 경우, 해당 클래스 안에서만 접근 가능하고, 그 외의 경우, 같은 파일 내에서만 접근 가능하다.
83. 접근 지정자 : private
1
2
3
4
5
6
class Person(private var name: String)
fun main(args: Array<String>){
val person = Person("jack")
println(person.name) // 오류
}
84. 접근 지정자 : protected
1
2
3
4
5
6
7
8
9
10
11
12
13
open class Person(protected val name: String)
class Student(name: String) : Person(name) {
fun printName() {
println(name)
}
}
fun main(args: Array<String>) {
val person = Student("jack")
println(person.name) // 에러
person.printName()
}
- protected는 해당 클래스 안에서만 접근 가능하므로
person.name
은 에러가 생긴다. - 반면에
person.printName()
메소드는 클래스 내부에서의 접근이기 때문에 가능하다.
85. 접근 지정자 오버라이딩
- 오버라이딩을 통해 protected인 프로퍼티나 멤버 함수의 접근 지정자를 public으로 변경할 수 있다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
open class AAA(protected open val number: Int)
class BBB(number: Int) : AAA(number) {
public override val number: Int
get() = super.number
}
fun main(args: Array<String>) {
val b = BBB(26)
val a: AAA = b
println(a.number) // 에러
println(b.number)
}
86. 확장 함수 (Extension Function)
- String은 코틀린에 내장된 클래스이기 때문에 원하는대로 멤버 함수를 추가할 수 없다. 이럴 때는 확장 함수를 사용하여, 상속 없이 클래스 외부에서 멤버 함수를 추가할 수 있다.
- 여기서 함수를 주입할 클래스를 리시버 타입이라고 부른다.
1
2
3
4
5
6
7
8
9
10
11
fun String.containNumber(): Boolean { // 문자열에 숫자가 포함되어 있으면 true
this.forEach {
if (it in '0'..'9') return true
}
return false
}
fun main(args: Array<String>) {
println("123abc".containNumber()) // true
println("abc".containNumber()) // false
}
- this를 사용하면 리시버 타입의 프로퍼티나 멤버 함수에 접근할 수 있다.
87. 확장 프로퍼티 (Extension Property)
1
2
3
4
5
6
7
val String.isLarge: Boolean
get() = this.length >= 10
fun main(args: Array<String>) {
println("1234567890".isLarge) // true
println("abcd".isLarge) // false
}
88. 객체 선언
1
2
3
4
5
6
7
8
9
10
11
12
13
14
object Person{
var name: String = ""
var age: Int = 0
fun print(){
println("$name $age")
}
}
fun main(args: Array<String>) {
Person.name = "정상현"
Person.age = 27
Person.print()
}
- object 키워드를 사용하면 싱글톤 패턴 코드를 더 이상 사용하지 않아도 된다.
- 프로그램 전체에서 단 하나만 존재하는 객체를 만들 수 있다.
89. 동반자 객체 (Companion Object)
- 어떤 클래스의 모든 인스턴스가 공유하는 객체를 만들고 싶을 때 사용한다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Person private constructor() {
companion object {
fun create(): Person {
countCreated += 1
return Person()
}
var countCreated = 0
}
}
fun main(args: Array<String>) {
val a = Person.create()
val b = Person.create()
println(Person.countCreated)
}
- 코틀린에는 static 키워드가 존재하지 않는다. static의 효과를 얻고 싶으면 동반자 객체를 사용해야 한다.
90. Inline 함수
- Inline 함수는 함수 호출문을 함수의 몸체로 대체하기 때문에 성능을 조금이나마 개선할 수 있다.
1
2
3
4
5
6
7
8
9
10
inline fun hello(){
println("hello")
println("kotlin")
}
fun main(args: Array<String>) {
hello()
hello()
hello()
}
- Inline 함수를 호출하면 컴파일 되는 순간 아래와 같은 코드로 대체된다.
1
2
3
4
5
6
7
8
fun main(args: Array<String>) {
println("hello")
println("kotlin")
println("hello")
println("kotlin")
println("hello")
println("kotlin")
}
91. const
-
런타임에 할당되는 val와 달리 const가 붙은 변수는 컴파일 시간동안 할당이 되어야 한다.
-
const는 함수나 어떤 클래스의 생성자에게도 할당 될 수 없고 오직 문자열이나 기본 자료형으로 할당되어야 한다.
1
2
3
4
5
const val hello = "hello" + " world"
fun main(args: Array<String>) {
println(hello)
}
1
2
3
4
5
fun abc(): String {
return "yes"
}
const val abc = abc() // 오류
92. lateinit
- lateinit 키워드가 붙은 프로퍼티는 클래스 안에서 바로 초기화하지 않아도 된다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Person {
lateinit var name: String
fun print() {
println(name)
}
}
fun main(args: Array<String>) {
val person = Person()
person.name = "Jack"
person.print()
}
- lateinit은 var 프로퍼티에만 붙일 수 있다.
- lateinit 프로퍼티가 초기화되었는지 알려면 isInitialized 함수를 사용하면 된다.
- 만약 name 프로퍼티에 값을 지정하지 않은 채 프로퍼티에 접근하면 UninitializedPropertyAccessException 예외가 발생한다.
93. Nullable 리시버
- 확장 함수를 응용하면, 참조 변수에 null이 지정되어 있어도 함수 호출이 가능하게 할 수 있다.
1
2
3
4
5
6
7
8
9
10
fun String?.isNumber() {
if (this == null) {
println("문자열이 null입니다.")
}
}
fun main(args: Array<String>) {
val empty: String? = null
empty.isNumber()
}
- empty는 String? 타입이고, null이 지정되어 있다.
- isNumber 확장 함수는 리시버 타입이 Nullable이기 때문에, 표현식의 값이 null이어도 isNumber 확장 함수를 호출할 수 있다.
94. 동반자 객체의 확장 함수
- 확장 함수를 이용해 동반자 객체에도 확장 함수를 달 수 있다.
1
2
3
4
5
6
7
8
9
class Person {
companion object
}
fun Person.Companion.create() = Person()
fun main(args: Array<String>) {
Person.create()
}
95. 확장 함수의 리시버 타입이 상속 관계에 있을 때
1
2
3
4
5
6
7
8
9
10
11
open class AAA
class BBB : AAA()
fun AAA.hello() = println("AAA")
fun BBB.hello() = println("BBB")
fun main(args: Array<String>) {
val test: AAA = BBB()
test.hello() // AAA
}
- 타입은 AAA이나, 실제로는 BBB 객체를 가리키고 있다.
- 확장 함수는 멤버 함수와는 다르게 참조 변수가 실제로 가리키는 객체의 타입을 따르지 않고, 참조 변수의 타입을 그대로 따른다.
96. 추상 클래스
1
2
3
4
5
6
7
8
9
10
11
abstract class Person {
abstract fun getSalary(): Int
}
class Student(private val tuition: Int) : Person() {
override fun getSalary() = -tuition
}
class Professor(private val classCount: Int) : Person() {
override fun getSalary() = classCount * 120
}
- abstract 키워드는 그 자체로 open을 포함하고 있기 때문에 open 키워드는 따로 적지 않아도 된다.
97. 인터페이스
- 인터페이스는 클래스에 어떤 멤버 함수와 프로퍼티가 반드시 존재한다는 것을 보장하기 위한 장치이다.
1
2
3
4
5
6
7
8
9
10
11
12
13
interface Printable {
fun print(): Unit
}
class AAA : Printable {
override fun print() {
println("hello")
}
}
fun main(args: Array<String>) {
AAA().print()
}
98. 다이아몬드 문제
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
interface Parent {
fun follow(): Unit
}
interface Mother : Parent {
override fun follow() = println("follow his mother")
}
interface Father : Parent {
override fun follow() = println("follow his father")
}
class Child: Mother, Father{
override fun follow() {
println("A child decided to ")
super.follow()
}
}
- Child 클래스는 Mother, Father 인터페이스를 모두 구현하고 있을 때,
super.hello()
를 하면 Mother의 follow()가 호출될지 Father의 follow()가 호출될지 애매하다. - 이런 경우엔
super<Mother>.follow()
이런 식으로 호출하면 된다.
99. 중첩 클래스 (Nested Class)
- 클래스 안에 또 다른 클래스를 선언할 수 있다.
1
2
3
4
5
6
7
8
9
10
class Outer {
class Nested {
fun hello() = println("hello")
}
}
fun main(args: Array<String>){
val instance: Outer.Nested = Outer.Nested()
instance.hello()
}
- Nested 클래스의 멤버 함수는 Outer 클래스의 프로퍼티나 멤버 함수에 접근할 수 없다.
1
2
3
4
5
6
7
class Outer {
private val name = "Jack"
class Nested {
fun printName() = println(name) // 오류
}
}
100. 내부 클래스 (Inner Class)
- 중첩 클래스가 단순히 식별자만 바깥 클래스에 속해있는 것이었다면, 내부 클래스는 인스턴스가 바깥 클래스의 인스턴스에 완전히 소속된다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class Outer(private val value: Int) {
fun print() {
println(this.value)
}
inner class Inner(private val innerValue: Int) {
fun print() {
this@Outer.print()
println(this.innerValue + this@Outer.value)
}
}
}
fun main(args: Array<String>) {
val innerInstance: Outer.Inner = Outer(610).Inner(40)
innerInstance.print()
}
- 내부 클래스는
this@Outer
키워드를 이용하여 자신이 속한 바깥 클래스의 인스턴스에 접근할 수 있다.
101. 데이터 클래스 (Data Class)
- 코틀린은 데이터에 특화된 클래스를 선언할 수 있는 문법을 제공한다.
- 클래스를 데이터 클래스로 선언하면, 다음과 같은 이점이 생긴다.
- Any 클래스에 들어있는 equals, hashCode, toString 멤버 함수가 자동으로 오버라이딩 된다.
- equals 멤버 함수는 각 프로퍼티의 값이 서로 모두 같으면 true를 반환한다.
- 객체를 복사하는 copy 함수가 자동으로 선언된다.
- 클래스를 데이터 클래스로 선언하기 위해선 다음의 규칙들을 지켜야 한다.
- 적어도 하나의 프로퍼티를 가져야 한다.
- 프로퍼티에 대응하지 않는 생성자 매개변수를 가질 수 없다.
- abstract, open, sealed, inner 키워드를 붙일 수 없다.
- 인터페이스만 구현할 수 있다. 코틀린 1.1 버전부터는 sealed 클래스도 상속 가능하다.
- component1, component2, … 와 같은 이름으로 멤버 함수를 선언할 수 없다. 컴파일러가 내부적으로 사용하는 이름이기 때문이다.
1
2
3
4
5
6
7
8
9
10
11
12
13
data class Employee(val name: String, val age: Int, val salary: Int)
fun main(args: Array<String>) {
val first = Employee("a", 30, 2000)
val second = Employee("b", 24, 5000)
val third = first.copy()
println(first.toString())
println(third.toString())
println(first == second) // false
println(first == third) // true
println(first === third) // false
}
102. 객체 분해하기
- 데이터 클래스의 인스턴스에 한해, 객체를 여러 개의 변수로 쪼개는 것이 가능하다.
1
2
3
4
5
6
data class Employee(val name: String, val age: Int, val salary: Int)
fun main(args: Array<String>) {
val(name, _, salary) = Employee("a", 20, 2000)
println("$name $salary")
}
103. 함수 리터럴과 람다식
1
2
3
4
5
6
7
8
9
fun main(args: Array<String>) {
val instantFunc: (Int) -> Unit
instantFunc = { number: Int ->
println("hello $number")
}
instantFunc(33)
instantFunc.invoke(33)
}
- 위와 같이 함수를 저장할 수 있는 타입을 함수 타입이라고 한다.
- 함수 타입의 변수는 invoke 멤버 함수를 통해서도 호출할 수 있다.
instanceFunc?.invoke(33)
와 같이 쓸 수 있으므로 null 처리를 하기 편해진다.
104. 익명 함수 (Anonymous Function)
1
2
3
4
5
6
7
8
fun main(args: Array<String>) {
val instantFunc: (Int) -> Unit = fun(number: Int): Unit {
println("hello $number")
}
instantFunc(33)
instantFunc.invoke(33)
}
- 람다식으로 된 함수 리터럴을 익명 함수의 형태로도 표현할 수 있다.
105. it 식별자
1
2
3
4
5
6
7
fun main(args: Array<String>) {
val instantFunc: (Int) -> Unit = {
println("hello $it")
}
instantFunc(33)
}
- 매개변수를 생략하면 it이라는 특별한 식별자가 만들어진다. 여기서 it은 생략한 Int 타입의 매개변수를 대체한다.
106. 함수 참조 (Function Reference)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
fun plus(a: Int, b: Int) = println("plus 호출됨 ${a + b}")
object Object {
fun minus(a: Int, b: Int) = println("object minus 호출됨 ${a - b}")
}
class Class {
fun average(a: Int, b: Int) = println("class average 호출됨 ${(a + b) / 2}")
}
fun main(args: Array<String>) {
var instantFunc: (Int, Int) -> Unit
instantFunc = ::plus
instantFunc(60, 28) // 88
instantFunc = Object::minus
instantFunc(60, 28) // 32
instantFunc = Class()::average
instantFunc(60,28) // 44
}
- 함수 타입의 변수는 이미 선언되어 있는 함수나 객체의 멤버 함수를 가리킬 수도 있다.
107. 고차 함수
- 고차 함수란, 인수로 함수를 받거나, 함수를 반환하는 함수를 뜻한다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
fun decorate(task: () -> Unit) {
println("=== 작업 시작 ===")
task()
println("=== 작업 완료 ===")
}
fun main(args: Array<String>) {
decorate {
val a = 10
val b = 5
println("$a + $b = ${a + b}")
}
}
108. 클로저 (Closure)
- 클로저는 내부 scope를 뛰어 넘어서 하위 함수가 상위 함수의 접근할 수 있는 것을 말한다.
1
2
3
4
5
6
7
fun main(args: Array<String>) {
var name = "kim"
fun closureTest() {
println(name) //close over 접근
}
closureTest() //kim deokhwa 출력
}
- 리터럴이 만들어지는 순간, 함수 리터럴은 자기 주변의 상황을 함께 저장한다. 즉, 함수가 만들어질 때 name 매개변수의 값을 복사해 갖고 있는다.
109. 리시버가 붙은 함수 리터럴
- 함수 리터럴에 리시버를 적용하여 확장 함수처럼 만들 수 있다.
1
2
3
4
5
6
7
8
fun main(args: Array<String>) {
val min: Int.(Int) -> Int = { value ->
if (this < value) this
else value
}
println(25.min(20))
}
110. 제네릭 (Generic)
1
2
3
4
5
6
fun <T> toFunction(value: T): () -> T = { value }
fun main(args: Array<String>) {
val func: () -> Int = toFunction<Int>(1170)
println(func())
}
111. 여러 타입을 인수로 받기
1
2
3
4
5
6
fun <T, R> toFunction(value1: T, value2: R): () -> T = { value1 }
fun main(args: Array<String>) {
val func: () -> Int = toFunction<Int, String>(1170, "test")
println(func())
}
112. 구체화된(Reified) 타입 매개변수
- 타입 매개변수는 is 연산자의 피연산자로 사용할 수 없다.
1
2
3
4
5
fun <T> check() {
val number = 0
if (number is T) // 오류
println("T는 Int 타입입니다.")
}
- 타입 매개변수를 is 연산자의 피연산자로 사용하고 싶으면 함수는 inline으로 선언 한 뒤, 타입 매개변수 앞에 reified를 붙여주면 된다.
1
2
3
4
5
inline fun <reified T> check() {
val number = 0
if (number is T)
println("T는 Int 타입입니다.")
}
113. 클래스와 인터페이스에서 제네릭 사용하기
1
2
3
class Pair<A, B> (val first: A, val second: B){
override fun toString() = "$first $second"
}
114. 제네릭이 적용된 클래스 / 인터페이스 상속, 구현하기
1
2
3
4
5
6
7
8
9
10
interface Plusable<T> {
operator fun plus(other: T): T
}
class Rectangle(val width: Int, val height: Int) : Plusable<Rectangle> {
override fun plus(other: Rectangle): Rectangle {
return Rectangle(width + other.width, height + other.height)
}
}
115. 특정 타입을 상속, 구현하는 타입만 인수로 받기
- 특정 타입을 구현하는 타입만 인수로 받으려면, 상속을 할 때처럼 타입 매개변수 뒤에
:타입이름
을 적어준다. : 타입1, 타입2
와 같이 적으면 여러 개를 지정할 수도 있다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
interface ValueContainer {
fun getValue(): Int
}
class AAA : ValueContainer {
override fun getValue(): Int {
return 1102
}
}
fun <T : ValueContainer> T.printValue() {
println(this.getValue())
}
fun main(args: Array<String>) {
AAA().printValue()
}
116. in / out 키워드
- out T는 자바의 ? extends T와 같고, in T는 자바의 ? Super T와 같다.
- 타입 인수를 *로 지정하면, 타입 인수가 무엇이든 상관없이 AAA 타입을 대입할 수 있다.
1
2
3
4
5
6
7
8
9
10
11
12
class AAA<out T>
class BBB<in T>
fun main(args: Array<String>) {
val aaaSub = AAA<Int>()
val aaaSup: AAA<Any> = aaaSub
val bbbSub = BBB<Any>()
val bbbSup: BBB<Int> = bbbSub
val star: AAA<*> = aaaSub
}
117. ..
연산자와 범위 표현식
- .. 연산자는 범위를 표현하는 연산자이다.
1
2
3
4
5
6
7
8
9
fun main(args: Array<String>) {
val intRange: IntRange = 1..10
intRange.forEach { print("$it ") }
val charRange: CharRange = 'A'..'Z'
if ('B' in charRange) {
println("대문자입니다.")
}
}
- CharRange는 유니코드를 기반으로 범위를 표시한다.
118. 반복자 (Iterator)
1
2
3
4
5
6
7
8
fun main(args: Array<String>) {
val range = 1..10
val iter = range.iterator()
while(iter.hasNext()){
println(iter.next())
}
}
119. 흐름 제어 - 반복문 for
- 코틀린의 for문은 for-each 스타일만 지원한다. C 스타일의 전통적인 for문은 지원하지 않는다.
1
2
3
4
5
fun main(args: Array<String>) {
for(i in 1..10){
print("$i ")
}
}
120. 배열 (Array)
1
2
3
4
5
6
7
8
9
10
fun main(args: Array<String>) {
val integers: Array<Int> = arrayOf(10, 20, 30) // 배열 초기화
println(integers.size)
println(integers[2])
for (i in integers) {
print("$i ")
}
}
1
2
val size = 10
var array = Array(size) { 0 } // 0으로 초기화
-
배열을 가변 인수로 활용하기
- vararg 키워드를 사용하여 배열 속에 들어있는 원소들을 가변 인수로 활용할 수 있다.
1
2
3
4
5
6
7
8
9
10
11
12
fun printAll(vararg tokens: String) {
for (token in tokens) {
print("$token ")
}
}
fun main(args: Array<String>) {
val numbers = arrayOf("what's", "your", "name?")
printAll(*numbers)
printAll("hello", "kotlin")
}
- vararg 키워드는 java의 … 키워드와 동일한 역할을 한다.
1
2
void printAll(String... tokens){
}
-
열거 클래스 (Enum)
- 열거 클래스는 정해진 집합 내의 값을 표현하기 위해 사용한다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
enum class Mode {
SELECTION, PEN, SHAPE, ERASER
}
fun main(args: Array<String>) {
val mode: Mode = Mode.PEN
when (mode) {
Mode.SELECTION -> println("선택 모드")
Mode.PEN -> println("펜 모드")
Mode.SHAPE -> println("도형 모드")
Mode.ERASER -> println("지우개 모드")
}
}
- 열거 클래스에 들어가는 식별자를 열거 상수(Enum Constant)라고 한다.
-
열거 클래스에 프로퍼티와 멤버 함수 선언하기
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
enum class Mode(val number: Int) {
SELECTION(0),
PEN(1),
SHAPE(2),
ERASER(3);
fun printNumber() {
println("모드 $number")
}
}
fun main(args: Array<String>){
val mode: Mode = Mode.ERASER
println(mode.number) // 3
mode.printNumber() // 모드 3
}
- 마지막 열거 상수 끝에 세미콜론을 반드시 붙여야 한다.
-
열거 클래스 활용하기
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
enum class Mode {
SELECTION, PEN, SHAPE, ERASER
}
fun main(args: Array<String>) {
val shapeMode: Mode = Mode.SHAPE
println(shapeMode.name) // 열거 상수의 이름
println(shapeMode.ordinal) // 열거 상수의 순서
val modes: Array<Mode> = Mode.values() // 모든 열거 상수들을 배열로 반환
for(mode in modes){
println(mode)
}
println(Mode.valueOf("PEN").ordinal)
}
-
sealed 클래스
- sealed 클래스는 자신의 중첩 클래스에만 상속을 허용하는 클래스이다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
sealed class Outer {
class One : Outer()
class Two : Outer()
}
class Three : Outer() // 1.1 버전 이후로는 같은 파일 내에서 가능
fun main(args: Array<String>) {
val instance: Outer = Outer.One()
val text: String = when (instance) {
is Outer.One -> "첫 번째"
is Outer.Two -> "두 번째"
is Three -> "세 번째"
}
}
-
위임된 프로퍼티 (Delegated Property)
- 코틀린에서는 Getter / Setter 구현을 다른 객체에 맡길 수 있는 문법을 제공한다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
class Sample {
var number: Int by OnlyPositive()
}
class OnlyPositive {
private var realValue = 0
operator fun getValue(thisRef: Any?, property: KProperty<*>): Int {
return realValue
}
operator fun setValue(thisRef: Any?, property: KProperty<*>, value: Int) {
realValue = if (value > 0) value else 0
}
}
fun main(args: Array<String>){
val sample = Sample()
sample.number = -50
println(sample.number)
sample.number = 100
println(sample.number)
}
- 프로퍼티를 대리하는 객체는 아래의 두 함수를 멤버 함수로 갖고 있어야 한다.
1
2
operator fun getValue(thisRef: Any?, property: KProperty<*>): T
operator fun setValue(thisRef: Any?, property: KProperty<*>, value: T)
-
클래스 위임 (Class Delegation)
- 코틀린에서는 인터페이스의 구현을 다른 클래스에 맡길 수 있는 문법도 제공한다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
interface Interface {
fun abc()
}
class ClassDelegator : Interface {
override fun abc() {
println("기본 구현")
}
}
class Sample : Interface by ClassDelegator()
fun main(args: Array<String>) {
Sample().abc()
}
-
Pair 클래스 : 두 변수를 하나로 묶기
- Pair 클래스를 이용하면 두 변수를 하나로 묶을 수 있다.
1
2
3
4
5
6
fun divide(a: Int, b: Int): Pair<Int, Int> = Pair(a / b, a % b)
fun main(args: Array<String>) {
val (q, r) = divide(10,3)
println("몫: $q, 나머지: $r")
}
-
to 확장 함수 : 두 값을 간단히 Pair로 묶기
- to는 모든 타입에 적용되는 확장 함수이다.
- to 확장 함수를 이용하여 Pair 객체를 간단히 생성할 수 있다.
1
2
3
4
5
fun main(args: Array<String>) {
val test: Pair<Int, Double> = 10 to 3.14
println(test) // (10, 3.14)
}
-
Triple 클래스 : 세 변수를 하나로 묶기
- Triple 클래스는 제네릭을 이용하여 세 가지 타입의 값을 보관한다.
1
2
3
4
5
6
7
8
9
fun calculateCircle(r: Int): Triple<Int, Double, Double> =
Triple(2 * r, 2 * r * 3.14, r * r * 3.14)
fun main(args: Array<String>) {
val (diameter, _, area) = calculateCircle(5)
println("지름 : $diameter")
println("넓이 : $area")
}
-
Comparable 인터페이스 : 클래스를 비교 가능하게 만들기
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Node(var value: Int) : Comparable<Node> {
override fun compareTo(other: Node): Int =
when {
this.value < other.value -> -1
this.value > other.value -> 1
else -> 0
}
}
fun main(args: Array<String>) {
val node1 = Node(10)
val node2 = Node(20)
println(node1 < node2) // true
}
-
ClosedRange 인터페이스 : 닫힌 구간을 표현하는 인터페이스
1
2
3
4
5
6
7
8
9
10
11
fun main(args: Array<String>) {
val intRange = 1..10
val longRange = 1L..100L
val floatRange = 1.0..2.0
val charRange = 'A'..'Z'
println(intRange.start) // 1
println(longRange.endInclusive) // 100
println(floatRange.contains(1.1)) // true
println(charRange.isEmpty()) // false
}
- ClosedFloatRange와 ClosedDoubleRange 클래스는 in 연산자를 사용할 수 없다.
-
Iterable 인터페이스 : 클래스가 반복자를 지원하도록 하기
1
2
3
4
5
6
7
8
9
fun main(args: Array<String>) {
val prog: IntProgression = 3..7
println(prog.first) // 3
println(prog.last) // 7
println(prog.step) // 1
prog.forEach { print("$it ") } // 3 4 5 6 7
}
- step은 반복자의 next를 호출할 때, 몇 칸씩 건너뛸 것인지를 나타내는 프로퍼티이다.
-
Progression과 관련된 함수
1
2
3
4
5
6
7
8
9
fun main(args: Array<String>) {
val prog1 = 7 downTo 3 // 7 6 5 4 3
val prog2 = (3..7).reversed() // 7 6 5 4 3
println(prog1 == prog2) // true
val prog3 = (1..10) step 3 // 1 4 7 10
val prog4 = 10 downTo 2 step 3 // 10 7 4
val prog5 = 2 until 5 // 2 3 4
}
-
코틀린의 컬렉션 (Collection)
- 컬렉션이란 프로그램을 개발하는데 필요한 기본적인 자료구조들을 통칭하는 말이다.
- List : 순서가 있는 목록을 표현하는 자료구조
- Set : 집합을 표현하는 자료구조. 원소의 중복을 허용하지 않으며, 각 원소는 순서를 갖지 않는다.
- Map : 연관 배열을 표현하는 자료구조. 키와 값을 1:1로 대응시켜 저장한다.
-
List와 Set은 Collection 인터페이스를 상속하고, Map은 상속하지 않는다.
- 참고 도서