초보자를위한 Kotlin 200제(1)
1장. 코틀린 기초 문법
1. hello kotlin
2. 표현식
- 하나의 값으로 수렴하는 수식 뭉치
- 자바에서는 표현식이 단독으로 오는 것을 허용하지 않기 때문에 아래와 같은 코드는 오류를 일으킨다
1
2
3
| fun Example() {
53+62-126
}
|
3. 변수
1
2
3
4
5
6
| // 변수를 선언하고 그와 동시에 초기화
var total: Int = 0
val a: Int = 10 + 53 - 7
val b: Int = 32 + 45 - a
total = a + b
|
4. 리터럴
1
| val variable = 10 + 12 - 5
|
- 리터럴에 타입이 있기 때문에 표현식의 결과 값에도 자연스럽게 타입이 생기게 된다
- 뒤의
10 + 12 - 5
는 표현식의 결과가 Int
이므로 variable 뒤에 :int
가 생략되어 있는걸 알 수 있다
5. 산술연산자
1
2
3
| val num: Double = 15 - 4 * 3 (x) // 자바에서는 3.0이라는 결과값이 나옴
val num2: Int = 15 - 4 * 3
|
6. 증감연산자
7. 비트연산자
형태 |
의미 |
자바에서는 ? |
15 and 7 |
15와 7을 비트 and 연산 |
15 & 7 |
5 or 2 |
5와 2를 비트 or 연산 |
5 | 2 |
15 xor 2 |
15와 2를 비트 xor 연산 |
15 ^ 2 |
32767.inv() |
32767을 비트 단위로 반전(not) |
~32767 |
1 shl 3 |
1을 왼쪽으로 3칸 시프트 |
1 « 3 |
8 shr 1 |
8을 오른쪽으로 1칸 시프트 |
8 » 1 |
-17 ushr 2 |
부호를 유지한채 -17을 2칸 시프트 |
-17 »> 2 |
8. 정수타입과 실수타입
1
2
3
| var e: Float = 67.6f
val f: Double = 658.456
e = (e + f).toFloat()
|
9. 실수 타입의 함정
1
2
3
4
5
6
7
| println(0.1f + 0.1f + 0.1f)
println(0.1f + 0.1f + 0.1f + 0.1f + 0.1f + 0.1f + 0.1f + 0.1f + 0.1f + 0.1f)
println(0.1f * 10)
>>> 0.3
>>> 1.0000001
>>> 1.0
|
10. 문자타입
1
2
3
4
5
6
7
| var ch: Char = 'A'
>>> A 1
ch = '\uAC00'
>>> 가 2
ch = '한'
ch.toInt()
>>> 54620 3
|
-
문자 A가 Char 타입으로 ch에 저장
-
컴퓨터는 0,1만 알아 듣기 때문에 사실 1번에서 저장된 A도 A가 저장된게 아닌 A의 문자 코드가 저장된다
AC00 에 해당하는 문자를 저장하고 있다
-
Char 타입의 표현식에 .toInt()를 적으면 해당 문자의 유니코드 값을 갖는 Int 타입으로 변화한다
11, 12 문자열, 문자열 안에 표현식의 값을 집어넣기
1
2
3
| val a = 20
val b = 30
println("a의 값: $a, b의 값: $b")
|
13. 타입 별명
1
2
3
| typealias Number = Int
val a: Number = 10
|
- 타입 이름이 너무 길 때 타입 이름을 줄이는 용도로 사용한다
14. 주석
1
2
3
4
5
| // 한 줄 전체 주석
/* 구간 주석
해당 사이에 있는 부분은
모두 주석 처리
*/
|
15. 배정 연산자(Assignment Operator)
=
16. 문장
17. 비교연산자
==
/ !=
연산자는 자바에서의 equals
메서드를 호출한 것과 같다
18. 논리연산자
1
2
3
| var check = (5>3) && (5>2)
check = (5>3) || (2>5)
check = !(5>3)
|
19~22 조건문 if, if-else
23, 24. 조건문 when
1
2
3
4
5
6
7
8
9
| val score = 64
val grade: Char = when (score / 10) {
6 -> println("D")
7 -> println("C")
8 -> println("B")
9, 10 -> println("A")
else -> println("F")
}
|
25~28 반복문 while, do while, continue, break
29. Label
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
| var x = 0
var y = 0
outer@ while (x <= 20) {
y = 0
while (y <= 20) {
if (x + y == 15 && x - y == 5) {
break@outer
}
y += 1
}
x += 1
}
println("x: $x, y: $y")
>>> x: 10, y: 5
|
30,31 함수,매개변수와 인수
1
2
3
| fun getAverage(a: Int, b: Int): Double {
return (a + b) / 2.0
}
|
32. Unit 타입
1
2
3
| fun noReturnFunction(a: Int): Unit {
println("a는 $a")
}
|
- Unit 타입을 반환하는 함수는 return을 생략한다고 해도 암묵적으로 Unit 타입의 객체를 return 하도록 되어있다
33. 디폴트인수
1
2
3
4
5
6
7
8
9
10
11
12
| fun getAverage(a: Int = 0, b: Int = 0, print: Boolean = false): Double {
val result = (a + b) / 2.0
if (print) {
println(result)
}
return result
}
getAverage(89, 96)
getAverage(a = 66, print = true)
getAverage(print = true)
getAverage(print = true, a = 10, b = 30)
|
34. 가변인수
1
2
3
4
5
6
7
8
9
10
11
12
13
14
| fun getSumOf(vararg numbers: Int): Int {
val count = numbers.size
var i = 0; var sum = 0
while (i < count) {
sum += numbers[i]
i += 1
}
return sum
}
getSumOf(1,2,3,4,5,6,7)
getSumOf(32, 57, 12)
getSumOf()
|
35. 함수 오버로딩
- 이름이 같은 함수를 여러개 선언하는 것
- 코틀린에서는 함수 시그니쳐가 다르기만 하면 허용한다
36~38 지역변수 전역변수
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
| val a = 5
fun main(args: Array<String>) {
val a = 52
println(a)
printA()
printA2()
func()
}
fun printA() {
val a = 17
println(a)
}
fun printA2() {
val a = 120
println(a)
}
fun func() {
println(a)
}
|
39. 지역함수
- 블록 안에 선언된 함수를 말한다
- 특정 함수에서만 사용될 때 사용
1
2
3
4
5
6
7
8
| fun main(args: Array<String>) {
fun printFomular(a: Int, b: Int) {
println(a * b + a - b)
}
printFomular(73, 1)
printFomular(4,6)
}
|
40. 메모리 스택영역
1
2
3
4
5
6
7
8
9
10
11
| fun main(args: Array<String>) {
val a = -36
val result = absolute(a)
println(result)
}
fun absolute(number: Int): Int {
return if (number >= 0) {
number
} else - number
}
|
41, 42 소스파일 여러 개로 분리하기, 패키지
1
2
3
4
5
6
| // Math.kt
package ex_package
fun max(a: Int, b: Int): Int = if (a > b) a else b
fun abs(num: Int): Int = if (num >= 0) num else -num
|
1
2
3
4
5
6
7
8
9
| // Ex_package.kt
package ex_package
fun main(args: Array<String>) {
val a = 20
val b = -30
println(max(a, abs(b)))
}
|
43, 44 다른 패키지의 함수 호출하기, import
1
2
3
4
5
6
| // Main.kt
package ex_func_in_another_package
fun main(args: Array<String>) {
println(ex_package.max(30, 10))
}
|
1
2
3
4
5
6
7
8
9
10
| // Main.kt
package ex_import
import ex_package.max
import ex_package.min as ex_min
fun main(args: Array<String>) {
println(max(55,47))
println(ex_min(3, 10))
}
|
45. 객체
- 서로 연관있는 변수(속성)들을 묶어 놓은 데이터 덩어리를 뜻한다
1
2
3
4
5
6
7
8
9
10
11
| package ex_object
fun main(args: Array<String>) {
val person = object {
val name: String = "홍길동"
val age: Int = 36
}
println(person.name)
println(person.age)
}
|
46. 메모리의 힙 영역
1
2
3
4
5
6
7
8
9
10
11
| package ex_memory_heap
fun main(agrs: Array<String>){
val person = object {
val name: String = "홍길동"
val age: Int = 36
}
println(person.name)
println(person.age)
}
|
- 힙은 임의의 위치에 저장되며 객체는 힙 영역에 저장된다
- 참조 변수와 참조 값
47. 클래스
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
| class Person {
var name: String = ""
var age: Int = 0
}
fun main(args: Array<String>) {
val person: Person
person = Person()
person.name = "홍길동"
person.age = 36
val person2 = Person()
person2.name = "김미영"
person2.age = 29
val person3 = Person()
person3.name = "JOhn"
person3.age = 52
}
|
48. 힙 영역의 존재 이유
- 이렇게 힙 영역을 통해서 하나의 객체를 여러 참조 변수에서 공유하는 형태로 사용할 수 있어서 메모리 공간을 훨씬 절약할 수 있다
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
26
27
| class Product {
var name = ""
var price = 0
}
fun main(agrs: Array<String>){
val product: Product
product = creatProduct() // #1
printProductInfo(product) // #4
processProduct(product) // #6
printProductInfo(product)
}
fun createProduct(): Product{
val apple = Product() // #2
apple.name = "Apple"
apple.price = 1000 // #3
}
fun processProduct(product: Product) { // #7
product.price += 500 // #8
}
fun printProductInfo(product: Product) { // #5
println("이름: ${product.name}")
println("가격: ${product.price}")
}
|
49, 50 문자열간 + 연산 시 주의점, 가비지 컬렉션
- String 타입의 변수는 스택영역에 저장되지 않는다
- 실제 문자열은 힙 영역에 생성된다
1
2
3
4
5
| fun main(args: Array<String>) {
var first = "Hello"
var second = "World"
first += second
}
|
1
2
3
4
5
6
7
8
9
10
| fun main(args: Array<String>) {
var result = ""
var i = 1
while (i <= 100) {
result += "$i"
i += 1
}
println(result)
}
|
- 미아 객체들이 많이 생기게 된다(연결이 끊긴)
- kotlinlang.org 에서는 가비지 컬렉션이 참조 회수 카운팅 방식을 취하고 있다고 한다
- 다른 객체가 보유한 객체에 대한 강한 참조 수를 추적하는 것을 기반으로 한다
51. ===, !== 연산자
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
| fun main(args: Array<String>) {
var a = "one"
var b = "one"
prinln(a === b)
>>> true
b = "on"
b += "e"
println(a !== b)
>>> true
b = a
println(a === b)
>>> true
}
|
52. 멤버 함수
1
2
3
4
5
6
7
8
9
10
11
| class Building {
var name = ""
var date = ""
var area = 0
fun print() {
println("이름: " + this.name)
pritnln("건축일자: " + this.date)
println("면적: ${this.area}m2")
}
}
|
53. 프로퍼티와 멤버 함수의 매개변수 이름이 중복될 때
1
2
3
4
5
6
7
8
| class AAA {
var num = 15
fun memberFunc(num: Int) {
println(num)
println(this.num)
}
}
|
54. 생성자(Constructor)와 초기화(Initialier)블록
- 생정자 : 객체를 초기화 하는 특수 멤버 함수
- 객체를 생성하면서 원하는 값으로 한번에 초기화 하기 위해서
1
2
3
4
5
6
7
8
9
| class Person constructor(name: String, age: Int) {
val name: String
val age: Int
init {
this.name = name
this.age = age
}
}
|
55. init 블록 나누어 쓰기
1
2
3
4
5
6
7
8
9
10
11
12
13
14
| class Size(width: Int, height: Int) {
val width = widht
val height: Int
init {
this.height = height
}
val area: Int
init {
area = width * height
}
}
|
56. 생성자와 프로퍼티 한번에 쓰기
1
| class Car(val name: String, val speed: Int = 0)
|
- 매개변수 앞에 val, var 키워드를 붙이면 동일한 이름의 프로퍼티가 같이 선언된다
- 생성자 매개변수에 들어온 인수가 프로퍼티의 초기값이 된다
57. 보조 생성자(Secondary Constructor)
- 클래스 내부에 오는 생성자, 여러 개가 올 수 있다
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
26
27
28
29
30
31
32
33
| class Time(val second: Int) {
init {
println("Init 블록 실행중")
}
constructor(minute: Int, second: Int) : this(minute * 60 + second){
println("보조 생성자 1 실행중")
}
constructor(hour: Int, minute: Int, second: Int) : this(hour * 60 + minute, second) {
println("보조 생성자 2 실행중")
}
init {
println("또 다른 init 블록 실행중")
}
}
fun main(args: Array<String>) {
println("${Time(15, 6).second}초")
println("${Time(6, 3, 17).second}초")
}
>>> init 블록 실행중
>>> 또 다른 init 블록 실행중
>>> 보조 생성자 1 실행중
>>> 906초
>>> init 블록 실행중
>>> 또 다른 init 블록 실행중
>>> 보조 생성자 1 실행중
>>> 보조 생성자 2 실행중
>>> 21797초
|
58. 프로퍼티와 Getter / Setter
1
2
3
4
5
6
7
8
9
| class Person {
var age: Int = 0
get() {
return field
}
set(value) {
field = if (value >= 0) value else 0
}
}
|
- 프로퍼티는 실제로 저장되는 공간(field), 저장된 값을 읽으려고 할 때 호출되는 Getter
- 값을 저장하려고 할 떄 호출되는 Setter로 이루어져 있다
1
2
3
4
5
6
7
8
9
10
| class Person {
var age: Int = 0 {
get() {
return field
}
set(value) {
field = value
}
}
}
|
- 코틀린에서는 프로퍼티에 디폴트 getter/setter가 포함이 되어있기 때문에 따로 만들 필요가 없다
- getter/setter의 동작을 커스터마이징 하고 싶다면 별도로 정의를 해야 한다
59. 연산자 오버로딩
- 객체의 인스턴스간에 연산자를 사용했을 때 해당 연산자를 오버로딩한 멤버 함수를 호출한다
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
26
27
28
29
30
31
32
33
34
| class Point(private var x: Int = 0, private var y: Int = 0) {
operator fun plus(other: Point): Point {
return Point(x + other.x, y + other.y)
}
operator fun minus(other: Point): Point {
return Point(x - other.x, y - other.y)
}
operator fun times(number: Int): Point {
return Point(x * number, y * number)
}
operator fun div(number: Int): Point {
return Point(x / number , y / number)
}
fun print() {
println("x: $x, y: $y")
}
}
fun main(args: Array<String>) {
val pt1 = Point(3,7)
val pt2 = Point(2, -6)
val pt3 = pt1 + pt2
val pt4 = pt3 * 6
val pt5 = pt4 / 3
pt3.print()
pt4.print()
pt5.print()
}
|
-
오버로딩이 가능한 연산자
-
단항 연산자
표현식 |
함수이름 |
컴파일 시 실제로 적용되는 형태 |
함수 반환 타입 |
+a |
unaryPlus |
a.unaryPlus() |
자유 |
-a |
unaryMinus |
a.unrayMinus() |
자유 |
!a |
not |
a.not() |
자유 |
-
이항 연산자
표현식 |
함수 이름 |
컴파일 시 실제로 적용되는 형태 |
매개변수 타입 |
함수 반환 타입 |
a + b |
plus |
a.plus(b) |
자유 |
자유 |
a - b |
minus |
a.minus(b) |
자유 |
자유 |
a * b |
times |
a.times(b) |
자유 |
자유 |
a / b |
div |
a.div(b) |
자유 |
자유 |
a % b |
rem |
a.rem(b) |
자유 |
자유 |
a += b |
plusAssign |
a.plusAssign(b) |
자유 |
자유 |
a -= b |
minusAssign |
a.minusAssign(b) |
자유 |
자유 |
a *= b |
timesAssign |
a.timeAssign(b) |
자유 |
자유 |
a /= b |
divAssign |
a.divAssign(b) |
자유 |
자유 |
a %= b |
remAssign |
a.remAssign(b) |
자유 |
자유 |
a > b |
compareTo |
a.compareTo(b) > 0 |
자유 |
Int |
a < b |
compareTo |
a.compareTo(b) < 0 |
자유 |
Int |
a >= b |
compareTo |
a.compareTo(b) >= 0 |
자유 |
Int |
a <= b |
compareTo |
a.compareTo(b) <= 0 |
자유 |
Int |
a == b |
equals |
a?.equals(b) ?: (b === null) |
Any? |
Boolean |
a != b |
equals |
!(a?.equals(b) ?: (b === null)) |
Any? |
Boolean |
60. 번호 붙은 접근 연산자(indexed) []
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
| class Person(var name: String, var birthday: String) {
operator fun get(position: Int): String {
return when (position) {
0 -> name
1 -> birthday
else -> "알수 없음"
}
}
operator fun set(position: Int, value: String) {
when (position) {
0 -> name = value
1 -> birthday = value
}
}
}
var person = Person("Kotlin", "2016-02-15")
println(person[0])
println(person[1])
println(person[-1])
person[0] = "java"
println(person.name)
|
[]
연산자를 오버로딩하는 멤버함수 get과 set
61. 호출 연산자 ()
- 함수를 호출하는 연산자도 코틀린에서는 오버로딩이 가능하다
1
2
3
4
5
6
7
8
9
10
11
12
13
| class Product(val id: Int, val name: String) {
operator fun invoke(value: Int) {
println(value)
println("id: $id\nname:$name")
}
}
val product = Product(762443, "코틀린 200제")
product(108)
>>> 108
id: 762443
name: 코틀린200제
|
62. in 연산자
- 어떤 값이 객체에 포함되어 있는지 여부를 조사하는 역할
1
2
3
4
5
| println('o' in "Kotlin")
println("in" !in "Kotlin")
>>> true
>>> false
|
'o' in "kotlin"
은 컴파일 시 "Kotlin".contains('o')
로 번역된다
"in" !in "Kotlin"
은 컴파일 시 !"Kotlin".contains("in")
으로 번역된다
63. 멤버 함수의 중위 표기법
피연산자 연산자 피연산자
순서로 표현식을 구성하는 방식
- 멤버 함수의 매개변수가 하나뿐이면 함수 호출을 중위 표기법으로 할 수 있다
1
2
3
4
5
6
7
8
9
10
11
12
13
| class Point(var x: Int = 0, var y: Int = 0) {
// base를 원점으로 생각했을 때 좌표를 반환
infix fun from(base: Point): Point {
return Point(x - base.x, y - base.y)
}
}
val pt = Point(3,6) from Point(1,1)
println(pt.x)
println(pt.y)
>>> 2
>>> 5
|
Point(3,6) from Point(1,1)
은 Point(3,6).from(Point(1,1))
이다
64. 상속(Inheritance)
1
2
3
4
5
6
| open class Person(val name: String, val age: Int)
class Student(name: String, age: Int, val id: Int) : Person(name, age)
val person = Person("홍길동", 35)
val student = Student("김길동", 23, 20171217)
|
- 상속을 할 때는 반드시 슈퍼클래스의 생성자를 호출해야 한다
65. 업캐스팅
- 캐스팅 : 특정 타입을 다른 타입으로 변환하는 것
1
2
3
4
5
| open class Person(val name: String, val age: Int)
class Student(name: String, age: Int, val id: Int) : Person(name, age)
val person: Person = Student("john", 32, 201721217)
|
- 업캐스팅이란 서브클래스의 인스턴스를 슈퍼클래스 타입으로 가리키는 것을 말한다
- person의 참조 변수는 Student의 인스턴스를 가리키고 있다
- 하지만 타입이 Person이기 때문에 name과 age 프로퍼티 밖에 접근하지 못한다
- 슈퍼클래스 타입은 항상 슈퍼클래스 자체나 서브클래스의 인스턴스만 가리킬 수 있다
66, 67 오버라이딩, 프로퍼티 오버라이딩
- 슈퍼클래스의 멤버 함수의 동작을 덮어쓰기 하는 것
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
| open class AAA {
open val number: Int = 0
open fun func() = println("AAA")
}
open class BBB : AAA() {
override var number: Int = 0
final override fun func() {
super.func()
println("BBB")
}
}
class CCC : BBB() {
override fun func()
// 에러
}
AAA().func()
BBB().func()
>>> AAA
>>> AAA
>>> BBB
|
68. 다형성(Polymorphism)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
| open class AAA {
open fun hello() = println("AAA")
}
class BBB : AAA() {
override fun hello() = println("BBB")
}
val one = AAA()
val two = BBB()
val three: AAA = two
one.hello()
two.hello()
three.hello()
>>> AAA
>>> BBB
>>> BBB
|
- 멤버 함수를 호출할 때, 참조 변수가 실제로 가리키고 있는 객체의 멤버 함수가 호출된다
- 호출은 참조변수.hello()형태의 한가지 이지만 문맥에 따라서 호출되는 함수가 다르다
- 참고 도서
초보자를 위한 Kotlin 200제