val pi : Double = 3.14 // val 변수명 : 자료형 = 값
val name = "gil-dong // 형추론 (String)
val pi = 3.14
pi = 3.141592 // val은 상수이므로 값을 재할당하는 경우 컴파일 에러 발생
var age = 21 // 형추론 (Int)
age = 25 // 재할당 값을 변경하고 싶을 때는 var를 사용해야 한다.
변수는 var, 상수값은 val로 선언한다.
형추론 - 문맥상 추론이 가능하면 자료형을 생략할 수 있다
kotlin에는 extends 키워드로 상속하지 않고 ':'를 이용하여 상속해준다
?: 를 90도 회전하면 elvis(엘비스) 가수가 된다고 해서 붙은 "Elvis 연산자"
val str : String? = "abc"
str?.length ?: 0
val result = value ?: defaultValue
str?.length가 null이면 ?: 뒤에 0을 반환하고
str?.length값이 있으면 그 자체를 반환한다
value가 null이 아니라면 result에 value가 할당되고, null이라면 defaultValue가 할당
이름이 없는 함수
val add: (Int, Int) -> Int = { a, b -> a + b }
val result = add(3, 5)
// 고차 함수
fun calculate(a: Int, b: Int, operation: (Int, Int) -> Int): Int {
return operation(a, b)
}
val result = calculate(3, 4) { x, y -> x + y }
println(result)
함수처럼 호출할 수 있는 객체를 다룰 때 사용되는 특별한 메소드
람다 함수나 함수형 객체에서 invoke()를 사용하여 내부의 동작을 실행
Kotlin에서는 invoke()를 생략해도 함수처럼 호출할 수 있다
// 람다형
val myLambda: () -> Unit = { println("Hello from lambda!") }
myLambda.invoke() // Hello from lambda!
myLambda() // Hello from lambda!
// 함수형 객체
class Greeter {
operator fun ***invoke(name: String): String {
return "Hello, $name!"
}
}
val greeter = Greeter()
println(greeter("Alice")) // "Hello, Alice!"
println(greeter.invoke("Alice")) // "Hello, Alice!"
?.invoke
람다가 null일 경우, 안전하게 호출할 수 있도록 ?.invoke를 사용한다
val nullableLambda: (() -> Unit)? = null
nullableLambda?.invoke() // null이므로 아무 일도 발생하지 않음
nullableLambda() // NullPointerException 발생
mapOf
변경이 불가능한(immutable) 맵을 생성하는 함수입니다.
생성된 맵은 요소의 추가, 삭제, 수정이 불가능합니다.
mapOf를 사용하는 경우는 맵의 내용이 실행 도중 변경되지 않아야 할 때입니다.
val map: Map<String, Int> = mapOf("one" to 1, "two" to 2)
mutableMapOf
변경이 가능한 맵(mutable map)을 생성하는 함수입니다.
생성된 맵은 요소의 추가, 삭제, 수정이 가능합니다.
mutableMapOf를 사용하는 경우는 맵의 내용이 실행 도중 변경될 수 있어야 할 때입니다.
val mutableMap: MutableMap<String, Int> =
mutableMapOf("one" to 1, "two" to 2)
mutableMap["three"] = 3 // 요소 추가
함수를 호출할 때 사용하는 특별한 방법으로 두개의 변수 가운데 오는 함수이다.
infix fun Int.multiply(a: Int): Int {
return this * a
// this는 multiply 함수를 호출한 객체를 참조 (아래의 2)
}
// infix
val result = 2 multiply 3
// 일반 메소드
val result = multiply(2, 3)
variable arguments
함수에 여러 개의 인자를 가변적으로 전달할 수 있게 해준다.
전달된 인자들은 배열로 처리된다.
// 일반
fun add(a: Int, b: Int): Int {
return a + b
}
// vararg 사용
fun add(vararg numbers: Int): Int {
var sum = 0
for (number in numbers) {
sum += number
}
return sum
}
println(add())
println(add(1, 2))
println(add(4, 5, 6, 7))
// java
class Person {
String name;
Person(String name) {
this.name = name;
}
void printName() {
System.out.println(this.name);
}
}
// Kotlin 속성을 클래스 내부에서 별도로 정의
class Person {
var name: String = ""
private lateinit var name: String // 도 가능
// 생성자
constructor(name: String) {
this.name = name
}
fun printName() {
println(name)
}
}
// 생성자에서 직접 정의하면서
// 속성을 한줄로 정의하고 초기화 가능
class Person(val name: String) {
fun printName() {
println(name)
}
}
생성자에서 직접 정의하게 되면, 매개변수를 통해 자동으로 초기화되므로 별도의 초기값 설정이 필요 없다.
public: 모든 곳에서 접근 가능. (kotlin에서는 명시하지 않음)
private: 해당 클래스 또는 파일 내에서만 접근 가능.
protected: 해당 클래스와 그 자식 클래스에서 접근 가능.
internal: 같은 모듈 내에서만 접근 가능.
Kotlin의 internal은 모듈 단위로 접근을 제한하며, 모듈은 여러 패키지를 포함할 수 있다.
Java의 package-private는 패키지 단위로 접근을 제한한다.
Kotlin은 Java와 다르게 Gradle 같은 빌드 도구를 사용하여 모듈을 관리한다.
패키지와 클래스의 집합으로, 별도의 파일 없이도 쉽게 관리할 수 있다
Java에서는 모듈을 명시적으로 정의하고 의존성을 관리하는 반면,
Kotlin은 빌드 도구를 통해 더 유연하게 모듈을 관리한다
kotlin에서 semicolon은 생략가능하므로, 대신 줄바꿈이 중요하다.
단, 상수 목록과 추가 메서드/속성 사이를 구분하기 위해 enum 항목들 끝에는 꼭 붙여야한다.
java의 switch 문과 비슷한 형식으로 조건 분기 처리가 가능하다.
break가 필요없다, 값을 반환할 수 있다
private fun getTabIcon(position: Int): Int {
return when (position) {
MY_GARDEN_PAGE_INDEX -> R.drawable.garden_tab_selector
PLANT_LIST_PAGE_INDEX -> R.drawable.plant_list_tab_selector
else -> throw IndexOutOfBoundsException()
}
}
// java
private int getTabIcon(int position) {
switch (position) {
case MY_GARDEN_PAGE_INDEX:
return R.drawable.garden_tab_selector;
case PLANT_LIST_PAGE_INDEX:
return R.drawable.plant_list_tab_selector;
default:
throw new IndexOutOfBoundsException();
}
}
Kotlin에서 모든 변수는 기본적으로 null 값을 가질 수 없다
?를 사용하여 변수를 nullable로 선언하면, 해당 변수는 null 값을 가질 수 있으며, 이를 통해 유연성을 부여할 수 있다
String?는 "이 값은 String이거나 null일 수 있다"
var Name: String = "A"
nonNullableName = null // 컴파일 오류 발생
var Name: String? = "B"
nullableName = null // 문제 없음
val parent: Parent = Child() // Parent parent = new Child();
val child: Child = parent as Child // Child child = (Child) parent;
child.printName() // "Child"
if (parent is Child) { // if(parent instanceof Child)
val safeChild: Child = parent
safeChild.printName() // "Child"
} else {
println("parent is not Child")
}
val number: Any = 42
val intNumber: Int = number as Int
val safeInt: Int? = number as? Int // 안전한 캐스팅
if (number is Int) {
//
}
모든 클래스의 최상위 타입으로 Java의 Object와 유사하다.
타입 검사를 통해 객체의 실제 타입을 확인하고, 그에 맞는 안전한 처리를 할 수 있어, 다형성을 효과적으로 활용할 수 있다.
fun Anytest(value: Any) {
when (value) {
is String -> println("This is a String: $value")
is Int -> println("This is an Int: $value")
is Double -> println("This is a Double: $value")
else -> //
}
}
컴파일 시점에 값이 결정되는 상수로 기본 타입(예: Int, Double, String)만 지원 한다.
클래스의 정적 멤버를 정의할 수 있는 특별한 객체, 인스턴스 없이 접근할 수 있는 변수와 메서드를 포함한다.
java의 static과 유사하다
클래스를 만들 때 호출되는 특별한 메소드다. 객체가 생성될 때 필요한 값(매개변수)을 전달한다.
// java
public class Car {
private String make;
private String model;
// 생성자
public Car(String make, String model) {
this.make = make;
this.model = model;
}
public void displayInfo() {
System.out.println("Car make: " + make + ", model: " + model);
}
}
public class Main {
public static void main(String[] args) {
Car myCar = new Car("Toyota", "Camry"); // 생성자 호출
myCar.displayInfo();
}
}
// kotlin
class Car(val make: String, val model: String) { // 생성자
fun displayInfo() {
println("Car make: $make, model: $model")
}
}
fun main() {
val myCar = Car("Toyota", "Camry") // 생성자 호출
myCar.displayInfo()
}
// 보조 생성자
class Car(val make: String, val model: String) {
constructor(make: String, model: String, year: Int) : this(make, model) {
println("Car year: $year")
}
}
fun main() {
val myCar = Car("Toyota", "Camry", 2020) // 보조 생성자 호출
}
보조 생성자는 반드시 constructor 키워드를 사용하여 별도로 정의된다.
주 생성자 외에 추가적인 초기화 로직이 필요할 때 사용한다.
"특정 객체에 대해 잠깐 작업을 할 때 쓰는 메소드"
val result = myObject?.let { obj ->
obj.someMethod()
}
val name = "cy"
name.let {
println(it.uppercase()) // "CY"
}
"이 객체로 여러 작업을 한 번에 할 때 쓰는 메소드"
val sharedPreferences = getSharedPreferences("my_prefs", Context.MODE_PRIVATE)
with(sharedPreferences.edit()) {
putString("key", "value")
putInt("number", 42)
apply() // 저장
}
with와 let의 차이점
with : 블록 내부에서 대상 객체를 생략 가능(this로 암시적으로 접근)
let : 블록 내부에서 it으로 대상 객체를 명시적으로 사용
null일 경우 IllegalStateException를 발생시키고, null이 아닐 경우 해당 값을 반환
interface Cleaner {
fun clean()
}
class RobotCleaner : Cleaner {
override fun clean() {
println("clean")
}
}
class RoomCleaner(robotCleaner: Cleaner) : Cleaner by robotCleaner
fun main() {
val robot = RobotCleaner()
val room = RoomCleaner(robot)
room.clean()
}
// RoomCleaner는 Cleaner를 구현하지만, clean()의 처리를 RobotCleaner에게 위임
// RoomCleaner의 clean()을 호출하면 RobotCleaner의 clean() 메서드가 실행