Kotlin

chaeny·2024년 12월 4일

변수와 상수

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로 선언한다.
형추론 - 문맥상 추론이 가능하면 자료형을 생략할 수 있다

extends

kotlin에는 extends 키워드로 상속하지 않고 ':'를 이용하여 상속해준다

lateinit var

  • 변수 값을 나중에 초기화한다고 명시하는 것, 변수의 첫 상태를 정의하기 어려울 때 사용한다
  • 변수를 선언할 때 즉시 값을 할당할 수 없는 상황에서 유용하다
  • null 값을 가질 수 없다
  • 주로 Android의 UI 요소(예: 뷰, 버튼 등)를 초기화할 때 많이 사용한다
  • 변수가 초기화되지 않은 상태에서 사용하려고 하면 UninitializedPropertyAccessException가 발생한다

Elvis 연산자

?: 를 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

함수처럼 호출할 수 있는 객체를 다룰 때 사용되는 특별한 메소드
람다 함수나 함수형 객체에서 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 발생

Map

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

함수를 호출할 때 사용하는 특별한 방법으로 두개의 변수 가운데 오는 함수이다.

infix fun Int.multiply(a: Int): Int {
    return this * a
    // this는 multiply 함수를 호출한 객체를 참조 (아래의 2)
}

// infix 
val result = 2 multiply 3


// 일반 메소드
val result = multiply(2, 3)

vararg

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은 빌드 도구를 통해 더 유연하게 모듈을 관리한다

semicolon

kotlin에서 semicolon은 생략가능하므로, 대신 줄바꿈이 중요하다.
단, 상수 목록과 추가 메서드/속성 사이를 구분하기 위해 enum 항목들 끝에는 꼭 붙여야한다.

when

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();
    }
}

nullable type

Kotlin에서 모든 변수는 기본적으로 null 값을 가질 수 없다
?를 사용하여 변수를 nullable로 선언하면, 해당 변수는 null 값을 가질 수 있으며, 이를 통해 유연성을 부여할 수 있다

String?는 "이 값은 String이거나 null일 수 있다"

var Name: String = "A"
nonNullableName = null // 컴파일 오류 발생

var Name: String? = "B"
nullableName = null // 문제 없음

as, is

  • "as" 연산자는 어떤 값을 지정한 타입으로 캐스트를 하고, 만약 대상 타입으로 캐스트 할 수 없으면 ClassCastException이 발생한다.
  • "as?" 를 사용하면 변환 실패 시 null을 반환한다.
  • java에서는 괄호를 사용한다.
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) {
	//
}
  • "is" 연산자는 객체의 타입을 확인하고 자동으로 캐스팅 된다.
  • java에서는 instanceof로 확인만 한다.

Any

모든 클래스의 최상위 타입으로 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 -> //
    }
}

const val

컴파일 시점에 값이 결정되는 상수로 기본 타입(예: Int, Double, String)만 지원 한다.

  • val : 런타임에 값을 할당할 수 있으며 클래스, 함수 안 등 어디서나 사용할 수 있다
  • const val : companion object나 object 안에서만 사용
    기본 타입(예: Int, Double, String)만 지원

companion object

클래스의 정적 멤버를 정의할 수 있는 특별한 객체, 인스턴스 없이 접근할 수 있는 변수와 메서드를 포함한다.

java의 static과 유사하다

constructor

클래스를 만들 때 호출되는 특별한 메소드다. 객체가 생성될 때 필요한 값(매개변수)을 전달한다.

// 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 키워드를 사용하여 별도로 정의된다.
주 생성자 외에 추가적인 초기화 로직이 필요할 때 사용한다.

let

  • Kotlin의 스코프 함수 중 하나
  • 객체가 null이 아니면 블록이 실행되고, null이면 블록이 실행되지 않는다
  • 특정 객체에 대한 작업을 국소적으로 수행할 수 있다
  • 블록 안에서는 it 또는 원하는 이름으로 객체를 참조할 수 있다

"특정 객체에 대해 잠깐 작업을 할 때 쓰는 메소드"

val result = myObject?.let { obj ->
    obj.someMethod()
}
val name = "cy"
name.let {
    println(it.uppercase()) // "CY"
}

with

  • 특정 객체를 대상으로 여러 작업을 수행할 때 대상 객체를 반복적으로 쓰지 않도록 도와준다

"이 객체로 여러 작업을 한 번에 할 때 쓰는 메소드"

val sharedPreferences = getSharedPreferences("my_prefs", Context.MODE_PRIVATE)
with(sharedPreferences.edit()) {
    putString("key", "value")
    putInt("number", 42)
    apply() // 저장
}

with와 let의 차이점
with : 블록 내부에서 대상 객체를 생략 가능(this로 암시적으로 접근)
let : 블록 내부에서 it으로 대상 객체를 명시적으로 사용

checkNotNull

null일 경우 IllegalStateException를 발생시키고, null이 아닐 경우 해당 값을 반환

by

  • 객체의 특정 기능을 다른 객체에 위임하는 디자인 패턴
  • 위임을 사용하면 코드가 간결해지고 재사용성이 높아지는 장점이 있다
  1. 클래스 위임
    어떤 클래스가 특정 인터페이스를 구현할 때, 인터페이스의 구현을 다른 객체에게 위임
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() 메서드가 실행
  1. 프로퍼티 위임
    어떤 클래스의 프로퍼티 값을 다른 객체가 관리하도록 위임

0개의 댓글