" 최근 IT 업계에서 높은 관심을 받고 있는 '코틀린'은 구글이 지원하는 프로그래밍 언어로서, 기존 Java에 비해 훨씬 간결하고 쉽게 작성할 수 있는 장점을 가지고 있다. 코틀린은 다양한 플랫폼에서 사용 가능하도록 설계되어 있어, 활용 범위가 매우 넓다. 안드로이드 개발과 관련된 유튜브 영상에서도 Kotlin을 기반으로 한 설명이 점차 증가하고 있다. 이에 따라 나도 안드로이드 앱 개발에 대한 이해를 높이기 위해 코틀린 문법을 공부할 예정이며, 배운 내용을 차근차근 정리해보겠다. "
package ojy.coding.kotlin;
//컴파일타임 상수 : 컴파일 과정에서 초기화
const val num = 20
fun main() {
//변수
var i = 10; //변수 추론 능력이 있어서 자료형을 명시하지 않아도 된다.
var name : String = "준석" //이렇게 명시해줄 수도 있음.
var point : Double = 3.3 //자료형은 다 대문자로 시작하는 클래스 타입.
//상수
val num = 20 //상수는 num=30 으로 재대입 불가함. java의 final과 동일.
}
"일반 상수는 런타임에 초기화되며, 클래스 내부에도 선언할 수 있고, 인스턴스를 통해 접근할 수 있다.
컴파일타임 상수는 컴파일 과정에서 초기화되며, 클래스 내부에 선언할 수 없고, 인스턴스 없이 접근할 수 있다."
컴파일타임 상수는 컴파일 과정에서 초기화되어 런타임에 변경되지 않는다. 이로 인해 프로그램의 시작 시간이 단축되고, 메모리 사용이 최적화되며, 코드 실행 속도가 빨라진다. 이는 프로그램의 성능과 안정성을 향상시킨다.
var i = 10
var l = 20L
l = i.toLong() //(Long) i -> 형식 안 됨.
i = l.toInt()
var name = "10"
i = name.toInt()
var name = "hello"
print(name.uppercase()) //HELLO
print(name.lowercase()) //hello
print(name[0]) //h
name = "지윤"
//java의 + name + 와 동일
print("제 이름은 $name 입니다") //제 이름은 지윤 입니다.
print("제 이름은 ${name}입니다") //제 이름은 지윤입니다.
//중괄호 안 수식 적용 가능
print("제 이름은 ${name + 10}입니다") //제 이름은 지윤10입니다.
import java.lang.Integer.max
fun main() {
var i = 10
var j = 20
print(max(i,j)) //20
//Math.max(i,j) = kotlin.math.max(i,j)
val randomNumber = Random.nextInt()
print(randomNumber) //840539650
val randomNumber2 = Random.nextInt(0,100) //0~99
val randomNumber3 = Random.nextDouble(0.0,1.0) //0.0~0.9
val reader = Scanner(System.`in`)
reader.nextInt() //숫자 받음
reader.next() //글자 받음
in은 코틀린에서 특별한 키워드다. 그래서 이 키워드를 변수나 식별자로 사용하려면 충돌을 피하기 위해 ` 백틱(backtick) 기호를 사용하여 묶어줘야 한다.
System.in은 자바에서 표준 입력 스트림을 나타내는 변수다. 코틀린에서는 자바와의 상호 운용성(interoperability)을 유지하기 위해 이를 그대로 사용할 수 있게 한다. 하지만 코틀린에서 in은 범위(range)를 나타내는 키워드로 사용되기 때문에, 이를 식별자로 사용하려면 백틱으로 감싸주어야 한다.
코틀린에서는 if 문을 식(expression)처럼 사용할 수 있다. 이를 통해 코드를 더 간결하게 작성할 수 있다. 이와 달리, Java에서는 if 문이 값을 반환할 수 없기 때문에 이러한 방식이 불가능하다. 대신 Java에서는 삼항 연산자를 사용해야 한다.
fun main() {
var i =5
var result = if(i > 10) { "10보다 크다." }
else if (i > 5) { "5보다 크다" }
else {"!!!!"}
//when문으로 똑같이 치환 가능!!
var result = when {
i > 10 -> "10보다 크다."
i > 5 -> "5보다 크다"
else -> "!!!!"
}
}
Java에 익숙한 개발자들은 여러 조건을 처리하기 위해 switch 문을 사용했다. 하지만 코틀린은 Java의 switch 문보다 더 강력하고 유연한 조건 처리 기능을 제공하는 when 문을 도입했다.
코틀린의 when 문은 여러 가지 강력한 기능을 제공하여 조건 처리를 더 간결하고 가독성 높게 만든다.
val i = 5
val result1 = when (i) {
1, 3, 5 -> "홀수"
2, 4, 6 -> "짝수"
else -> "알 수 없는 숫자"
}
val score = 85
val result2 = when {
score in 90..100 -> "A등급"
score in 80..89 -> "B등급"
else -> "F등급"
}
fun isEven(n: Int) = n % 2 == 0
val number = 4
val result3 = when {
isEven(number) -> "짝수"
else -> "홀수"
}
fun describe(obj: Any): String = when (obj) {
is String -> "문자열"
is Int -> "정수"
is Double -> "실수"
else -> "알 수 없는 타입"
}
val x: Any = "코틀린" //Any 타입의 변수 x에 문자열 "코틀린"을 할당
val result4 = describe(x) //describe 함수에 x를 전달하여 반환되는 문자열을 result4에 할당.
// result4 변수는 "문자열"이라는 값을 가짐
Any 타입은 코틀린에서 모든 타입의 상위 클래스로, 어떠한 타입의 객체도 저장할 수 있는 변수 타입이다. Java에서 Object 클래스와 유사한 역할을 수행한다.
Java에서 비슷한 기능을 사용하려면 복잡한 if-else 구문이나 조건 연산자를 사용해야 했다. 이런 복잡한 처리를 해결하기 위해 코틀린은 when 문을 도입했다. 이를 사용하면 코드가 더 간결해지고 가독성이 높아진다.
val items = listOf(1,2,3,4,5)
items.forEach { //forEach 함수는 람다를 인자로 받아 컬렉션의 각 요소에 대해 람다를 실행
item -> print(item) //람다
}
// for (int i=0; i<items.length; i++)
for (i in 0..(items.size - 1)) {
print(items[i]) // 결과 : 12345
}
//범위 이용
for (i in 1..5) {
print("$i ") // 결과: 1 2 3 4 5
}
for (i in 1 until 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
}
val names = listOf("철수", "영희", "민수")
for (name in names) {
print("$name ") // 결과: 철수 영희 민수
}
코틀린에서는 리스트를 다루기 위해 List와 MutableList 두 가지를 제공한다. List는 변경이 불가능한(읽기 전용) 리스트이고, MutableList는 변경이 가능한 리스트이다.
// 변경이 불가능한 List 생성
val items = listOf(1, 2, 3, 4, 5)
// 변경이 가능한 MutableList 생성
val itemsMutable = mutableListOf(1, 2, 3, 4, 5)
// 타입 추론으로 인해 생략 가능한 타입 선언
// val itemsMutable: MutableList<Int> = mutableListOf(1, ...)
itemsMutable.add(6) // 리스트에 요소 추가
itemsMutable.remove(3) // 리스트에서 요소 제거
// 인덱스를 이용해 요소 접근
try {
val item = itemsMutable[6]
} catch (e: Exception) {
print(e.message) // Index 6 out of bounds for length 5
}
코틀린은 Null Pointer Exception에 대비하기 위해 Null 안전성을 기본적으로 제공한다. 변수를 선언할 때 타입 뒤에 물음표(?)를 붙여 해당 변수가 null 값을 허용하도록 지정할 수 있다.
var name: String? = null // `?`를 사용하여 null 값이 허용되는 변수로 선언
name = "지윤"
name = null
var name2: String = ""
name2 = name //오류 : 별개의 타입이라 대입 안 됨
if (name != null) {
name2 = name //name이 String 타입으로 변환되서 오류 안 남
}
//name2 = name!! 개발자가 널이 아님을 보증하는 !! 연산자를 사용할 수 있지만, 권장되지 않는다.
//let을 사용하여 널이 아닌 경우에만 코드를 실행
name?.let { //name이 널이 아니라면 실행하자!
name1 = name
}
코틀린에서 함수를 작성하는 방법은 매우 간결하다. 기본적인 함수 정의 및 호출 방법을 살펴보자.
fun main() {
var c = sum(10,20)
}
//Top-Level 함수 : 파일의 맨 바깥에 작성함. 어느 파일에서나 사용 가능
fun sum(a: Int, b: Int) : Int {
return a+b
}
//한 줄짜리 함수는 더 간단히 가능
fun sum(a: Int, b: Int) : Int = a + b
//return 타입 생략 가능 <- 타입 추론 가능하기 때문
fun sum(a: Int, b: Int) = a + b
코틀린에서 Top-Level 함수란 클래스나 객체에 종속되지 않는 함수를 말한다. 이 함수들은 파일의 최상위에 정의되며, 어느 파일에서든 사용이 가능하다.
Top-Level 함수는 전역적으로 사용할 수 있는 기능을 쉽게 구현할 수 있도록 도와준다. 이를 통해 클래스를 생성하지 않고도 독립적인 함수를 만들어 코드 구조를 간결하게 만들 수 있다.
// TopLevelFunction.kt
fun sayHello() {
println("안녕하세요!")
}
// 다른 파일에서 사용하기
import TopLevelFunction.sayHello
fun main() {
sayHello() // 안녕하세요!
}
fun main() {
var c = sum(10,20)
var d = sum(b=10, a=20)
}
// 인자 3개짜리 필요하다고 오버로드할 필요 없음
fun sum(a: Int, b: Int, c: Int = 0) = a + b + c
코틀린에서는 클래스를 간편하게 생성할 수 있다. 생성자를 따로 만들 필요 없이 매개변수처럼 직접 작성하고, val이나 var를 사용하여 getter와 setter를 자동으로 구현한다.
fun main() {
val john = Person("John", 20)
print(john.name)
print(john.age)
john.age = 23
}
class Person(val name: String, var age: Int)
class Person(private val name: String, var age: Int)
이 경우 main 함수에서 print(john.name)은 오류가 발생한다.
데이터 클래스는 주로 데이터를 담기 위한 클래스로, 자동으로 일반적인 함수들을 생성해준다.
fun main() {
val john = Person("John", 20)
val john2 = Person("John", 20)
println(john)
println(john2)
print(john == john2)
}
class Person(val name: String, var age: Int)
data class Person(val name: String, var age: Int)
이제 == 연산자로 비교한 결과가 true로 나온다. 이렇게 데이터 클래스는 데이터를 담는데 편리한 기능을 제공한다.
코틀린에서는 getter와 setter를 쉽게 사용할 수 있다. 주로 클래스의 프로퍼티에 적용된다
fun main() {
val john = Person("John", 20)
john.hobby = "독서"
}
class Person(val name: String, var age: Int) {
var hobby = "축구"
private set // 외부에서 값을 변경할 수 없게 한다
get() = "취미: $field" // getter를 재정의하여 반환값을 수정한다
init { // 클래스 생성 시 실행되는 초기화 블록
print("init")
}
fun some() {
hobby = "농구"
}
}
코틀린에서 클래스의 생성자는 주 생성자와 보조 생성자로 구분된다. 주 생성자의 본문에는 로직을 넣을 수 없기 때문에, 초기화 블록(init)을 사용하여 클래스 생성 시 실행되는 로직을 작성할 수 있다. 필요한 경우 여러 개의 초기화 블록을 사용할 수 있, 이들은 선언된 순서대로 실행된다.
//추상 클래스
abstract class Animal {
fun move() {
print("이동")
}
//오버라이딩 가능하게 하기 위해선 open 해줘야 함
open fun run() {
print("뛴다")
}
}
class Dog : Animal() {
override fun run() {
print("달린다")
}
class Cat : Animal() {
override fun run() {
print("점프한다")
}
코틀린에서 클래스를 상속받기 위해서는 부모 클래스가 open 키워드로 선언되어야 한다.
open class Person
class SuperMan : Person()
인터페이스는 클래스와 비슷한 개념이지만, 추상 메서드를 포함한다. 클래스는 인터페이스를 상속받아 해당 인터페이스의 메서드를 구현해야 한다.
interface Drawable {
fun draw()
}
class Dog : Animal(), Drawable {
override fun draw() { //인터페이스 상속 받았기 때문에 오버라이딩 해줘야 함.
print("그린다.")
}
fun main() {
val dog: Animal = Dog()
val cat = Cat()
// 타입 체크를 통해 dog가 Dog 타입인지 확인
if (dog is Dog) {
dog.draw() // Dog 타입이므로 사용 가능
println("멍멍이")
}
// 타입 체크를 통해 dog가 Animal 타입인지 확인
if (dog is Animal) {
// dog.draw() // 사용 불가능, Animal 타입에는 없음. move는 가능.
}
// cat을 강제로 Dog 타입으로 변환 (실행 시 오류 발생)
cat as Dog
}
제네릭을 사용하면, 클래스나 함수에서 다양한 타입을 처리할 수 있도록 만들어 준다.
fun main() {
val box = Box(10) // Int 타입의 Box
val box2 = Box("dfdfd") // String 타입의 Box
println(box.value) // 10 출력
}
// 제네릭을 사용한 클래스 정의
class Box<T>(var value: T)
T라는 타입 매개변수 사용해 사용자 원하는 타입 지정 가능하게 했다. 이를 통해 Box 클래스 인스턴스 생성 시 각기 다른 타입 값 저장 가능하게 됐다.
콜백 함수는 함수를 매개변수로 받아 실행하는 고차함수를 이용하여 비동기 처리나 특정 이벤트에 대한 응답을 처리할 수 있다.
fun main() {
// 콜백 함수 사용, 출력: "함수 호출"
myFunc { println("함수 호출") }
}
// 고차함수 정의: 콜백 함수를 매개변수로 받는다.
fun myFunc(callBack: () -> Unit) {
println("함수 시작!!!")
callBack() // 콜백 함수 실행
println("함수 끝!!!")
}
myFunc 함수는 고차함수로써, 매개변수로 콜백 함수(callBack)를 받는다. 이 콜백 함수는 main 함수에서 정의된 람다식으로 "함수 호출"을 출력한다. myFunc 함수 안에서 callBack()을 호출하면 전달받은 람다식이 실행되어 원하는 동작을 수행한다. 이런 방식으로 함수 호출 순서나 이벤트 처리를 유연하게 조작할 수 있다.
suspend 함수는 코루틴에서 사용되며, 비동기 작업을 수행할 때 블로킹 없이 대기할 수 있게 해준다.
import kotlinx.coroutines.*
fun main() = runBlocking {
val result = fetchData()
println("결과: $result")
}
// suspend 함수 fetchData
suspend fun fetchData(): String {
delay(1000L) // 네트워크 호출 등 시간이 걸리는 작업
return "데이터"
}
코루틴은 비동기 작업을 쉽게 처리할 수 있도록 도와주는 프레임워크이다. 코루틴을 사용하여 동시에 여러 작업을 수행할 수 있다.
https://www.youtube.com/watch?v=OtHkb6wAI5U - Kotlin 문법 총 정리 - 1시간