Kotlin

Kotlin 면접준비(2)

취업하고싶다! 2023. 6. 4. 18:08

1. Kotlin에서 변수 선언 방법은?

Immutable variables: read-only 변수. val 키워드를 써서 선언하고 한 번 선언되면 값 변경 불가능

val sample = "interview"
sample = "interview2" 		// compile error

Immutable 변수는 constant(상수)가 아님. 상수가 아니기 때문에 compile-time에 값을 알릴 필요 없음.

아래 코드처럼 호출할 때 마다 값이 바뀔 수 있음

var sample = "interview"
val newSample = sample 		// no compile error

 

Mutable variables: 값 변경 가능. var 키워드 써서 선언

var sample = "interview"
sample = "interview2"

 

 

2. Data Class란?

data를 보유하고 일반적인 함수들을 제공하는 클래스

data class MyData(list_of_parameters)

equals(), hashCode(), copy(), toString()과 같은 함수 제공

적어도 하나 이상의 매개변수가 primary 생성자에 있어야 함

Abstract, open, sealed, inner은 허용 X

오직 interface만 data class에 의해 구현 가능

 

 

3. null safety란?

kotlin type system은 null(nullable reference)을 보유할 수 있는 reference와 그렇지않은 reference(non-null reference)로 구분

nullable references는 타입뒤에 ?를 붙여서 선언 가능

var a:String = "interview"
a = null 		// results in compilation error

var b:String? = "interview"
b = null		// no compilation error

kotlin은 null이 발생한 경우 이용할 수 있는 연산자들을 제공

  • Safe Call: ?.
  • Elvis: ?:
  • Not Null Assertion: !!

kotlin은 nullable reference와 non-null references, 그리고 다양한 연산자들을 이용해 null을 안전하게 처리할 수 있음

 

 

4. Safe Call, Elvis, Not Null Assertion이란?

Safe Call operator(?.): 특정 reference가 null 값이 아닌 경우에만 작업을 수행하는 작업을 단순화하는 Safe Call 연산자가 있음

name?.toLowerCase() 	// Safe Call operator

// 위 Safe Call operator는 아래와 같음
if (name != null)
    name.toLowerCase()
else
    null

 

Elvis Operator(?:): Elvis 연산자의 왼쪽 표현식이 null이 아니면 왼쪽 표현식을 반환하고 null이면 오른쪽 표현식을 반환

val sample1 = sample2 ?: "Undefined"	// Elvis operator

val sample1 = if (sample2 != null)
      sample2
    else
      "Undefined"

// 아래와 같이 예외를 throw 할 수도 있음
val sample1 = sample2 ?: throw illgelArgumentException("invalid")

 

Not Null Assertion Operator(!!): 값이 null이면 예외를 throw. NullPointerException 원하면 이 연산자를 사용해 명시적으로 요청

fun main(args:Array<String>){
    var sample: String? = null
    str!!.length	// Exception in thread "main"kotlinNullPointerException
}

 

 

5. Kotlin과 Java의 차이점

1) Null Safety

Kotlin

- 기본적으로 코틀린의 모든 종류의 변수는 null 값을 허용하지 않

- null 값을 허용하려면 아래와 같이 선언 가능

value num: Int? = null

Java

- Java에서 NullPointerException은 큰 골칫거리

- 모든 변수에 null을 할당할 수 있기 때문에, null 값을 이용하는 개체를 참조할 때 NPE가 발생하는지를 개발자가 직접 관리해야 함

 

 

2) Coroutines Support

Kotlin

- Kotlin은 스레드를 차단(block)하지 않고 실행을 중지(suspend)할 수 있는 코투린을 지원

 

Java

- Java는 수많은 스레드를 생성하고 실행할 수 있지만, 이를 관리하는 것은 쉽지 않음

 

 

3) Data Classes

Kotlin

- Kotlin은 dat를 보유하는 클래스가 필요한 경우, data 키워드를 클래스에 정의할 수 있음

- 컴파일러에 의해 생성자 매개변수에 자동으로 getter/setter를 처리하고 다양한 기본 함수들도 제공

 

Java

- Java는 data를 보유하는 클래스가 필요한 경우, data를 저장할 변수&getter/setter&기본 함수들도 개발자가 직접 명시해야 함

 

 

4) Functional Programming

Kotlin

- Kotlin은 lambda expression, operator overloading, higher-order functions, lazy evaluation 등 다양한 기능을 가진 절차형, 함수형 프로그래밍 언어

 

Java

- Java 8까지 함수형 프로그래밍을 지원하지 않지만, Android 앱을 개발할 때 Java 8의 하위 집합으로 지원

 

 

5) Extenstion Functions

Kotlin

- 기존 클래스에 새로운 기능 추가 가능

fun MutableList<Int>.swap(index1: Int, index2: Int) {
    val tmp = this[index1]
    this[index1] = this[index2]
    this[index2] = tmp
}

val list = mutableListOf(1,2,3)
list.swap(0,2)

Java

- Java에서는 기존 클래스에 새로운 기능을 추가하려면 상속을 통해 구현해야 함. 따라서 기본적으로 extenstion 함수가 없음

 

 

6) Data Type Inference

Kotlin

- 변수의 타입을 명시적으로 지정하지 않아도 괜찮음

val str = "String" 	// 타입 추론

Java

- Java에서는 변수의 type을 명시적으로 선언해야 함

 

 

7) Smart Casting

Kotlin

- 변수의 type을 직접 검사하지 않고, 컴파일러가 알아서 검사하고 캐스팅함

fun smartCast(any:Any) {
    when(any) {
      is Int->println(any+2)
      is String->println(any)
      else->return
    }
}

Java

- Java에서는 변수의 타입을 검사하고 적절하게 캐스팅해야 함

 

 

8) Checked Exceptions

Kotlin

- checked exception 없음. 개발자가 선언하거나 catch할 필요가 없음

 

Java

- checked exception 지원, 이를 개발자가 직접 선언하고 catch할 필요

 

 

 

6. 생성자 유형은?

1) Primary Constructor

- class header에 초기화되며 class 이름 뒤에 선언. constructor 키워드를 이용해 선언

class Sample constructor(val a: Int, val b: Int) {
	// my code...
}

- primary 생성자는 코드를 포함할 수 없기 때문에 초기화 코드는 init block에서 처리해야 함. init block은 객체가 생성된 후 호출됨

class Sample(a: Int, b: Int) {
    val p: Int
    var q: Int
    
    // initializer block
    init {
      p = a
      q = b
      println("The first parameter value is: $p")
      println("The second paremeter value is: $q")
    }
}

 

2) Secondary Constructor

- 변수를 초기화하고 logic 추가 가능. constructor 키워드를 이용해 선언

class Sample {
    constructor(a: Int, b: Int) {
        println("The first parameter value is: $p")
        println("The second parameter value is: $q")
    }
}

- kotlin에서는 하나의 primary 생성자와 하나 이상의 secondary 생성자를 가질 수 있음

- primary 생성자는 클래스를 초기화하는 반면, secondary 생성자는 초기화와 몇 가지 logic 추가 가능

 

 

7. 두 string을 연결할 방법은?

val s1 = "interview"
val s2 = "Bit"

val s3 = "$s1$s2"	// Using String Interploation

val s4 = s1 + s2 	// Using the +
val s5 = s1.plus(s2)	// Using the plus() operator

val s6 = StringBuilder()
s6.append(s1).append(s2)
val s7 = s6.toString	// Using StringBuilder

 

 

8. companion object란?

Java와 같은 일부 언어에서는 static 키워드를 이용하여 class member를 선언하고 object 생성 없이 이를 활용함

Kotlin에는 static 키워드가 없음. companion object를 이용해 구현할 수 있음

class CompanionClass {
    companion object CompanionObjectName {
      // code
    }
}

val obj = CompanionClass.CompanionObjectName
class Sample {
    companion object Test {
    // companion object 내부에 변수와 함수를 선언할 수 있음
    var a: Int = 1
    fun testFunction() = println("Companion Object's Member function called.")
    }
}

fun main(args:Array<String>) {
    println(Sample.a)
    Sample.testFunction()
}

 

 

9. open과 public 키워드 차이는?

open: 확장(expansion)을 나타냄. Java의 final과 반대 개념. kotlin에서는 기본적으로 클래스 상속 x. open 사용해 상속함

기본적으로 모든 클래스가 public

 

 

10. when 키워드란?

Java의 switch 문 대신 when을 사용

when(...) {
    condition ->
    condition ->
    else ->
}

 

 

11. val mutableList와 var immutableList 중 더 좋은 것은?

Collection의 이용 목적에 따라 나눌 수 있음.

Collection이 변경될 경우 mutableList를 쓰고 반대로 view만 한다면 immutableList를 사용함.

 

val과 var은 immutable list와 mutable list와는 다른 이용 목적을 가짐.

val은 변수의 값(value)/참조(reference)가 한 번만 할당되고 이를 실행 중 수정할 수 없을 때 이용함.

반대로 var은 변수의 값/참조가 언제든 변경될 수 있을 때 이용함.

 

Immutable list는 아래와 같은 장점들이 있음

  • immutable이기 때문에 상태(state)를 변경하지 않음. 따라서 다음 함수(map, filter, reduce...)로 전달할 때 상태 변경 없이, 새로운 상태를 구성해 전달하는 함수형 프로그래밍 기법을 추구
  • side effect가 없기 때문에 이를 이해하고 디버그하기 더 쉬운 경우가 많음
  • 다중 스레드 시스템에서 write 접근이 필요없기 때문에 리소스 경쟁 조건을 유발하지 않음

반대로 단점도 있음

  • 단일 조각을 추가/삭제/변경 하기위해, collection 전체를 복사하는 것은 비용이 많이 듦

 

 

12. lateinit이란? 사용할 때 고려할 것은?

생성자에서 변수를 초기화하지 않고 나중에 초기화하려면 lateinit 키워드를 이용해 변수를 선언

  • 초기화되지 전까지는 메모리 할당 x
  • lateinit은 나중에 초기화되므로 val을 이용할 수 없음
  • primitive type이나 nullable은 쓸 수 없음
  • 만약 초기화되기 전에 접근하면 예외 발생
lateinit var test: String

fun testFunction() {
    test = "interview"
    println(test.length)	// 9
    test = "bit"
}

 

 

13. lazy initialization이란?

객체 초기화에 시간이 너무 많이 소요되는 경우, lazy initialization이 유용

lazy initialization을 이용해 객체를 선언하면, 객체가 이용될 때 한 번만 객체가 초기화됨

만약 이용하지 않으면 초기화도 되지 x

 

 

14.lateinit과 lazy initialization의 차이는? 언제 써야하나?

lateinit

  • 초기화 시점 지연
  • 프로그램 어디서나 초기화 가능
  • 여러번 초기화 가능
  • thread-safe X
  • var만 가능
  • primitive type 불가능

lazy initialization

  • 나중에 사용할 때만 초기화
  • 프로그램 전체에서 개체의 단일 복사본이 유지관리
  • 초기화 람다만 가능
  • 한번만 초기화 가능
  • thread-safe
  • val만 가능
  • primitive type 가능

아래와 같이 정리할 수 있음

  • 속성이 변경 가능, 외부에서 설정 -> lateinit 이용
  • 한 번만 초기화, 모두에게 공유, 더 내부적으로 설정 -> lazy initialization 이용

 

 

15. scope function이란?

코틀린 표준 라이브러리에는 객체 context 내에서 코드 블록 실행을 지원하는 함수가 많음

람다 식을 이용해 객체에서 이러한 함수를 호출하면 임시 scope가 생성되는데 이를 scope function이라 함

 

1) let

- let 함수는 null safety call에 자주 쓰임

var str:String? = "Hello"

val length = str?.let {
    println(it)
}

// Hello

 

2) apply

- 객체를 초기화하기 위해 자주 쓰임

val adam = Person("Adam").apply {
    age = 20		// same as this.age = 20 or adam.age = 20
    city = "London"
}

 

3) with

- 람다 결과를 제공하지 않고 context 객체에 대한 함수를 호출할 때 권장됨

val numbers = mutableListOf("one", "two", "three")

with(numbers){
    println("'with' is called with argument $this")
    println("it contains $size elements")
}

 

4) run

- let과 with 함수의 조합

- 객체 람다가 반환 값의 초기화와 계산을 모두 포함하는 경우

- run을 이용해 null-safe하게 계산 수행 가능

val service = MultiportService("https://example.kotlinlang.org", 80)

val result = service.run {
    port = 8080
    query(perpareRequest() + " to port $port")
}

// the same code written with let() function
val letResult = service.let {
    it.port = 8080
    it.query(it.prepareRequest() + "to port ${it.port}")
}

 

5) also

객체 멤버가 초기화된 후 추가 작업을 수행해야 할 때 이용

fun getRandomInt(): Int {
    return Random.nextInt(100).also {
        wirteToLog("getRandomInt() generated value #it")
    }
}

 

 

16. sealed class란?

sealed class는 sub class의 집합을 가진 클래스. sub class의 종류를 제한함.

이를 통해 런타임이 아닌 컴파일 타임에 타입의 유효성을 검사하기 때문에 타입 안전성이 보장됨.

sealed class Sample {
    class A: Sample() {
      fun print() {
        println("this is subclass A of sealed class Sample")
      }
    }
    
    class B: Sample() {
      fun print() {
        println("this is subclass A of sealed class Sample")
      }
    }
}

fun main() {
    val obj1 = Sample.B()
    obj1.print()
    
    val obj2 = Sample.A()
    obj2.print()
}

 

 

17. back field란?

back field는 접근자(getter/setter) 내부에서만 이용할 수 있는 자동 생성된 field

custom getter/setter를 이용할 때 필요한 경우 field 식별자로 접근 가능

var marks:Int = someValue
    get() = field
    set(value) {
        field = value
    }

 

 

18. 코틀린의 단점은?

  • internal, crossinline, expect, reified, sealed, inner, open 같이 Java에는 없는 명확하지 않은 의미를 가진 키워드들이 있음
  • checked exception이 없기 때문에 이로인한 안전성 보장받지 못한다고 느낄 수 있음
  • data class를 정의하면 많은 코드들이 자동으로 추가돼서 개발자 입장에선 디버깅하기에 숨겨져있다고 느낄 수 있음
  • Java에 비해 자료나 학습 자원 부족

'Kotlin' 카테고리의 다른 글

Kotlin- Activity와 Fragment  (0) 2023.06.07
Kotlin 면접준비(1)  (0) 2023.06.04
Kotlin- 문법1  (0) 2023.06.02
클린 아키텍처(Clean Architecture)  (0) 2023.06.02
코틀린 컨벤션  (0) 2023.06.01