-
코틀린 기초 문법 ①KOTLIN 2022. 9. 26. 19:26
요즘 코드가 Java -> Kotlin으로 넘어가고 있고, 사용이 많고 동기 분들을 보니 모두 코틀린 공부를 하고 계셔서 Kotlin 공부를 시작했다.
노션에 따로 적으면서 공부하긴 했지만, 블로그에도 함께 적으면 좋을 것 같아서 옮긴다.
참고한 강의 및 책은 맨 밑에 함께 적어두었다!
함수와 변수
코틀린에서의 변수
- var : 변경 가능한 참조를 저장하는 변수
- val : 변경 불가능한 참조를 저장하는 변수
var a = 13 a = 15 val b = 13 // 변경이 불가능하기 때문에 다음의 구문은 실행될 수 없다 // b = 15
코틀린에서의 함수
- fun 키워드 사용
- 파라미터 → ‘변수명 : 타입’
- 리턴 타입 → 파라미터 뒤의 ’ : 타입’
- void인 경우 (리턴값이 없는 경우) 리턴타입은 생략 가능
fun add(a: Int, b: Int): Int { return a + b }
- 식이 본문인 함수
fun add(a: Int, b: Int): Int = a + b // 식이 본문인 함수의 경우 리턴 타입이 명확하기 때문에 생략이 가능하다 fun add(a: Int, b: Int) = a + b
문자열
코틀린에서 문자열 템플릿
val a = "jay" val b = "ho" println("hihi ${a + b}") // hihi jayho println("hihi $a $b") // hihi jay ho println("hihi ${1 == 0}") // hihi false println("hihi s\\$name") // hihi s$name
.length
- 문자열의 길이를 반환
"hello world".length //11
.toUpperCase(), .toLowerCase()
- .toUpperCase() : 대문자로 변환
- .toLowerCase() : 소문자로 변환
val s = "Hello World" println(s.toUpperCase()) // HELLO WORLD println(s.toLowerCase()) // hello world
.joinToString()
- 대상을 합쳐서 문자열로 반환
- prefix, postfix, separator를 이용하여 값을 꾸며줄 수 있음
예제 참고 사이트 : https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.collections/join-to-string.html
val numbers = listOf(1, 2, 3, 4, 5, 6) println(numbers.joinToString()) // 1, 2, 3, 4, 5, 6 println(numbers.joinToString(prefix = "[", postfix = "]")) // [1, 2, 3, 4, 5, 6] println(numbers.joinToString(prefix = "<", postfix = ">", separator = "•")) // <1•2•3•4•5•6> val chars = charArrayOf('a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q') println(chars.joinToString(limit = 5, truncated = "...!") { it.uppercaseChar().toString() }) // A, B, C, D, E, ...!
.subString(i .. j)
- 시작 ~ 끝 위치 만큼 문자열을 빼내어 나타냄
val s = "kotlin" println(s.substring(0 until 5)) // kotli
.isNullOrEmpty(), .isNullOrBlank()
- 문자열이 null 이거나 빈값(””)인 경우를 확인
- 문자열이 null 이거나 빈칸(” “)인 경우를 확인 (스페이스, tap 등 확인)
var empty = "" var blank = " " println(empty.isNullOrEmpty()) // true println(empty.isNullOrBlank()) // true println(blank.isNullOrBlank()) // true println(blank.isNullOrEmpty()) // false
.startsWith(), .endsWith()
- 문자열이 해당 문자열로 시작하는지 확인
- 문자열이 해당 문자열로 끝나는지 확인
var s = "kotlin" println(s.startsWith("k")) // true println(s.endsWith("n")) // true
.contains()
- 문자열이 다음의 문자열을 포함하는지 확인
val s = "hello world" println(s.contains("d")) // true
클래스
기본 생성자를 가진 클래스
- 코틀린의 기본 가시성 → public
- 기본 생성자(주 생성자)를 통해서 간단하게 작성이 가능
- 생성자의 기본값은 = 을 이용해서 설정이 가능 (기본값으로 name : "Anonymous")
- 변경이 불가능한 프로퍼티는 val, 변경이 가능한 프로퍼티는 var로 선언
- 클래스 생성시 생성자를 정의하지 않으면 컴파일러가 자동으로 아무일도 하지 않는 인자가 없는 디폴트 생성자를 만듦
class Human(val name: String = "Anonymous") { fun eatingCake() { println("$name is eating cake.") } }
Init
- constructor보다 init이 먼저 실행 (init-> constructor 순으로)
class Human(val name: String = "Anonymous") { init { println("New human has been born!!") } fun eatingCake() { println("$name is eating cake.") } }
부 생성자 (sub constructor)
- 다른 파라미터의 constructor를 만들고 싶다면 주 생성자(this(name))을 상속받아 작성
class Human(val name: String = "Anonymous") { constructor(name: String, age: Int) : this(name){ println("My name is $name, $age years old.") } init { println("New human has been born!!") } fun eatingCake() { println("$name is eating cake.") } }
내부클래스(Inner class) 및 중첩클래스 (Nested class)
- 강하게 연관된 도우미 클래스를 캡슐화 하거나 코드 정의를 사용처에 가까이 둘 때 사용
- 내부 클래스
- 클래스 안의 다른 클래스 선언
- 외부 클래스에 대한 참조를 저장
- 외부 클래스 객체가 존재해야만 생성 및 사용이 가능 (외부 클래스 함수 및 속성 사용 가능)
- 내부 클래스에서 외부 클래스에 참조하려면 this@Outer 사용
class Outer { inner class Inner { fun getOuterReference(): Outer = this@Outer } }
-중첩클래스
- 외부 클래스에 대한 참조를 저장하지 않음
- 명시적 요청이 없는 한 바깥쪽 클래스 인스턴스에 대한 접근 권한이 없음 (독립적 존재, 외부 클래스 함수 및 속성 사용 불가)
- 자바에서의 static class(정적 클래스)와 비슷
Enum 클래스
- 코틀린에서 enum은 소프트 키워드 → class앞에서 특별한 의미를 지니지만 다른 곳에서는 이름에 사용 가능
- enum 클래스 안에 프로퍼티나 메서드 정의가 가능
- 각 enum에서 상수를 정의할 때는 그 상수에 해당하는 프로퍼티를 지정해야 함
- enum 클래스 안에서 함수를 정의하는 경우 enum 상수 목록과 메서드 정의 사이에 세미콜론이 필수
enum class Color(var r: Int, val g: Int, val b: Int) { RED(255, 0, 0), ORANGE(255, 165, 0), BLUE(0, 0, 255); fun rgb() = (r * 256 + g) * 256 + b }
Data클래스
- 클래스 앞에 data 표시를 이용하여 정의
- toString, equals, hashCode를 알아서 오버라이딩
data class Ticket(val name: String, val seatNumber: Int, val companyName:String, val date: String) fun main() { var ticket = Ticket("joyce", 8, "KoreanAir", "2022-12-20") var secondTicket = Ticket("joyce", 8, "KoreanAir", "2022-12-20") println(ticket) // Ticket(name=joyce, seatNumber=8, companyName=KoreanAir, date=2022-12-20) println(ticket == secondTicket) // true }
- 추가로 copy(), componentX() 메서드를 가짐
- copy() : 객체를 복사하면서 일부 프로퍼티의 변경을 가능하게 함
- componentX() : 배열 또는 리스트에서 속성을 순서대로 반환
data class Data(val name: String, val id: Int) val list = listOf(Data("a", 1), Data("b", 2), Data("c", 3)) for ((a, b) in list) { println("$a $b") } // a 1 // b 2 // c 3
상속
- 기본적으로 final로 상속을 제한 (특별히 하위 클래스에서 오버라이드하게 의도한 클래스가 아니라면 제한)
- ‘클래스 이름 : 상속할 클래스 ‘ 의 형태
- 클래스의 경우 하나만 확장이 가능하지만 인터페이스의 경우 원하는 만큼 인터페이스 상속 가능
- 클래스의 경우 기본 생성자를 받아 상위 클래스 이름 옆에 괄호() 가 들어가지만 인터페이스의 경우 상위 클래스 이름 옆에 아무 기호가 없음
- open : 상속을 허락함을 의미
- sealed : 상위 클래스를 상속한 하위 클래스 정의를 제한
- final override : 하위의 클래스가 메소드를 다시 오버라이딩 하지 못하도록 제한
클래스 상속
open class Human(val name: String = "Anonymous") { constructor(name: String, age: Int) : this(name){ println("My name is $name, $age years old.") } init { println("New human has been born!!") } fun eatingCake() { println("$name is eating cake. This is so YUMMY!") } open fun singASong() { println("$name is singing. lalala~") } } class Korean : Human() { // 부모의 메소드를 오버라이딩하면서 하위 클래스가 다시 오버라이딩 하지 못하도록 ㅈ final override fun singASong() { super.singASong() println("$name is singing. 라라라~") } } fun main() { var human = Human("joyce") var stranger = Human() // init 메서드 실행 // New human has been born!! // New human has been born!! human.eatingCake() // joyce is eating cake. This is so YUMMY! println("This human name is ${human.name}") // This human name is joyce println("This stranger name is ${stranger.name}") // default 값을 이용한 출력 발생 // This stranger name is Anonymous val mom = Human("Kuri", 50) // init 메서드 및 부생성자 작동 // New human has been born!! // My name is Kuri, 50 years old. println("This human name is ${mom.name}") // This human name is Kuri mom.singASong() // Kuri is singing. lalala~ val daughter = Korean() // New human has been born!! daughter.singASong() // 상위 클래스의 singing(super상속) 및 override 한 메서드가 실행됨 // Anonymous is singing. lalala~ // Anonymous is singing. 라라라~ }
- 서브클래스(Child)는 슈퍼클래스(Parent)에 존재하는 속성과 같은 이름의 속성을 가질 수 없음
- 서브 클래스가 생성될 때는 반드시 슈퍼클래스의 생성자까지 호출 되어야 함
class Parent (name: String, age: Int, gender: String) { // ... } // 부모의 gender 파트를 추가하여 생성자 호출 class Child (name: String, age: Int) : Parent(name, age, "male") { // ... }
- 상위클래스를 다른 방식으로 초기화 가능
- 클래스에 주 생성자가 없다면 모든 부 생성자는 반드시 상위 클래스를 초기화 하거나 다른 생성자에게 생성을 위임해야 함
// 부 생성자만 2개를 가지는 부모 클래스 open class View { constructor(ctx: Context) {} constructor(ctx: Context, attr: AttributeSet) {} } // 상위 클래스의 생성자에게 생성을 위임 class MyButton1 : View { constructor(ctx: Context) : super(ctx) {} constructor(ctx: Context, attr: AttributeSet) : super(ctx, attr) {} } // ctx만 받는 생성자는 자기 자신의 생성자에게 생성을 위임하고, 두 파라미터를 받는 생성자가 상위 클래스에게 위임 class MyButton2 : View { constructor(ctx: Context) : this(ctx, MY_STYLE) {} constructor(ctx: Context, attr: AttributeSet) : super(ctx, attr) {} }
인터페이스 상속
- default 없이 디폴트 구현이 있는 메서드를 정의 가능
- 여러개의 인터페이스를 상속받는 경우 상위 인터페이스들에 중복되는는 메서드가 존재하면 하위 에서 이를 대체할 오버라이드 메서드를 제공해야 함 (안하면 컴파일러 오류 발생)
- 상위 타입의 구현을 호출할 때에는 super<상위 타입>.메서드()의 형식으로 구현
interface Clickable { fun showOff() = println("I'm clickable!") } interface Focusable { fun showOff() = println("I'm focusable!") } class Button : Clickable, Focusable { override fun showOff() { super<Clickable>.showOff() super<Focusable>.showOff() } }
추상클래스 상속
- 추상 클래스의 경우 하위클래스에서 추상멤버를 오버라이드해야 하므로 상속에 항상 열려있음
- 추상 클래스는 인스턴스화 불가
- 추상 클래스에 속하더라도 비추상 함수는 기본적으로 파이널
abstract class Animated { // 오버라이딩에 열려있음(abstract) abstract fun animated() // 오버라이딩이 제한됨(기본 final) fun stopAnimating() { // ... } }
sealed 클래스의 하위 클래스(중첩 클래스)
- 상위 클래스에 sealed 변경자 사용시 상위 클래스를 상속한 하위 클래스(중첩 클래스로 구현) 정의를 제한
- sealed 표시된 클래스는 자동으로 open
// 상위 클래스를 sealed로 봉인 sealed class Expr { // 중첩 클래스로 하위 클래스 정의 class Num(val value: Int) : Expr() class Sum(val left: Expr, val right: Expr) : Expr() } // 기존의 when 구문에 else 처리가 필요했던 것과 다르게 디폴트 분기 처리가 필요 없음 fun eval(e: Expr): Int = when (e) { is Expr.Num -> e.value is Expr.Sum -> eval(e.left) + eval(e.right) }
when
- 자바의 switch 구문의 역할
- 코틀린의 비교 연산자 중 when에서는 등호 부등호 사용이 제한
- when의 인자는 Any 타입으로 아무 객체나 사용이 가능
- when에 아무 인자도 없기 위해서는 각 분기의 조건이 불리언 결과를 계산하는 식이어야 함
fun checkNum(score: Int) { // score에 따라서 결과가 달라진다 when (score) { 0 -> println("This is zero") 1 -> println("This is one") else -> println("I don't know") } // 값을 정의할 때의 when var b = when (score) { 1 -> 2 2 -> 3 4 -> 7 else -> 10 } // 범위내의 여부를 확인하는 when when (score) { in 0..2 -> println("min num") in 3..6 -> println("mid num") else -> println("I don't know") } }
while, for
- 반복문 (iteration) 이 필요한 경우 사용됨
while 과 do-while
- while : 조건이 참이 경우 본문을 반복 실행
- do-while : 맨 처음 무조건 본문을 한번 실행하고 조건이 참인 경우 반복 실행
while (조건) { // ... } do { // ... } while (조건)
for 문
- 시작 숫자 .. 끝 숫자 : 시작 숫자 ≤ x ≤ 끝 숫자의 범위를 나타냄
- step : 증가 값을 조절
- downTo : 역방향 수열을 생성
- until : 시작 숫자 ≤ x < 끝 숫자의 범위를 나타냄
- in : 컬렉션에 대한 이터레이션, 범위의 원소 검사를 위해 사용
- 맵에서 키와 값을 이용한 iteration 가능
- Collection.withIndex를 이용하면 파이썬의 enumerate처럼 인덱스, 값을 사용할 수 있음
- 중첩 for문의 경우 loop를 이용해서 중첩문을 한번에 빠져나올 수 있음
fun forExample() { var sum = 0 for (i in 1..10) { sum += i } print(sum) // 55 for (i in 1..10 step 2) { print(i) } // 1 3 5 7 9 for (i in 10 downTo 1) { print(i) } // 10 9 8 7 6 5 4 3 2 1 *for (i in 1 until 10) { print(i) }* // 1 2 3 4 5 6 7 8 9 val students = arrayListOf("joyce", "james", "jenny", "jennifer") for (name in students) { print("$name") } // joyce james jenny jennifer for ((index, name) in students.withIndex()) { println("${index + 1}번째 학생은 ${name}입니다") } // 1번째 학생은 joyce입니다 // 2번째 학생은 james입니다 // 3번째 학생은 jenny입니다 // 4번째 학생은 jennifer입니다 loop@ for (i in 1 until 3) { for (j in 1 until 5) { if (i == 2 && j == 3) break@loop print(j) } } }
캐스팅
스마트 캐스팅
- 어떤 변수가 원하는 타입인지 일단 is로 검사하면 그 변수가 원하는 타입으로 선언된 것 처럼 사용이 가능
- 실제로는 컴파일러가 캐스팅을 수행
- 단, 원하는 타입(호환되는 타입)으로 명시적 타입 캐스팅을 하려면 as 키워드 사용
if (e is Sum) { // 변수 e에 대해 스마트 캐스팅 발생 return eval(e.right) + eval(e.left) }
as 캐스팅
- 어떤 값을 지정한 타입으로 캐스트 하는 것
- 대상 값을 as로 지정한 타입으로 바꿀 수 없으면 ClassCastException이 발생 하기 때문에 as변환 전에 is로 타입 체크
참고한 강의
- 코틀린 인프런 무료 강의 → https://www.inflearn.com/course/코틀린-끝내기
- 코틀린 + Spring 간단한 프로젝트 만들어보기 → https://www.youtube.com/watch?v=TJcshrJOnsE&list=PL6gx4Cwl9DGDPsneZWaOFg0H2wsundyGr&index=1
참고한 책
- 코틀린 인 액션 → http://www.yes24.com/Product/Goods/55148593
'KOTLIN' 카테고리의 다른 글
코프링(코틀린 + 스프링부트) + 구글 스프레드 시트로 슬랙봇 만들기 - ② 슬랙으로 요청받기 (0) 2022.10.22 코프링(코틀린 + 스프링부트) + 구글 스프레드 시트로 슬랙봇 만들기 - ① 슬랙앱 생성 및 사용 설정하기 (0) 2022.10.22 코틀린 기초 문법 ④ ( + DSL 학습중 ⌛️) (1) 2022.10.18 코틀린 기초 문법 ③ (0) 2022.09.28 코틀린 기초 문법 ② (0) 2022.09.27