Kotlin interview questions
InterviewBit
https://www.interviewbit.com/kotlin-interview-questions/#freshers
Answer
Immutable variables
read-only 변수라고도 알려져 있다.
val 키워드를 써서 선언해야하고, 한번 선언되면 값을 변경할 수 없다.
val sample = "interview"
sampe = "interviewBit" // compile error
Immutable 변수는 constant(상수) 가 아니다, 상수가 아니기 때문에 compile-time 에 값을 알 필요가 없다.
아래 코드처럼 호출할때마다 값이 바뀔 수 있다.
var sample = "interview"
val newSample = sample // no compile error
Mutable variables
값을 변경할 수 있다.
var 키워드를 써서 선언한다.
var sample = "interview"
sampe = "interviewBit"
Answer
Data 클래스는 data 를 보유하고 일반적인 함수들을 제공하는 클래스입니다.
data class className ( list_of_parameters)
아래와 같은 함수들을 제공하고 있습니다.
equals()
hashCode()
copy()
toString()
그리고 다음과 같은 사항들이 있습니다.
적어도 하나 이상의 매개변수가 primary 생성자에 있어야 한다.
Abstract, open, sealed, inner 는 허용되지 않습니다.
오직 interface 만 data 클래스에 의해 구현될 수 있습니다.
Answer
Kotlin type system 은 null(nullable references) 을 보유할 수 있는 reference 와 그렇지 않은 reference(non-null references) 로 구분합니다.
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 references 와 non-null references, 그리고 다양한 연산자들을 이용해서 null 을 안전하게 처리할 수 있습니다.
Answer
Safe Call operator ( ?. )
Kotlin 에서는 특정 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 IllegalArgumentException("Invalid")
Not Null Assertion Operator ( !! )
값이 null 이면, 예외를 throw 합니다.
NullPointerException 을 원하면 이 연산자를 사용하여 명시적으로 요청할 수 있습니다.
fun main(args: Array<String>) {
var sample : String? = null
str!!.length // Exception in thread "main" kotlin.KotlinNullPointerException
}
Answer
1. Null Safety
Kotlin
기본적으로 Kotlin 의 모든 종류의 변수는 null 값을 허용하지 않습니다.
만약 null 값을 허용하려면 아래와 같이 선언할 수 있다.
value num: Int? = null
Java
Java 에서는 NullPointerExceptions 는 큰 골칫거리입니다.
모든 변수에 null 을 할당할 수 있기 때문에, null 값을 이용하는 개체를 참조할때 NPE 가 발생하는지를 개발자가 직접 관리해야 합니다.
2. Coroutines Support
Kotlin
Kotlin 은 스레드를 차단(block) 하지 않고, 실행을 중지(suspend) 할 수 있는 코루틴을 지원합니다.
Java
Java 는 수많은 스레드를 생성하고 실행할 수 있지만, 이를 관리하는 것은 쉽지 않습니다.
3. Data classes
Kotlin
Kotlin 은 data 를 보유하는 클래스가 필요한 경우, data 키워드를 클래스에 정의할 수 있습니다.
컴파일러에 의해 생성자 매개변수에 자동으로 getter/setter 를 처리하고, 다양한 기본 함수들도 제공합니다.
Java
Java 는 data 를 보유하는 클래스가 필요한 경우, data 를 저장할 변수, getter/setter, 그리고 기본 함수들도 개발자가 직접 명시적으로 작성해야 합니다.
4. Functional Programming
Kotlin
Kotlin 은 lambda expressions, operator overloading, higher-order functions, and lazy evaluation 등 다양한 기능을 가진 절차형(procedural), 함수형(functional) 프로그래밍 언어입니다.
Java
Java 8 까지 함수형 프로그래밍을 지원하지 않지만, Android 앱을 개발할 때 Java 8 의 하위 집합으로 지원합니다.
5. Extension 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 에서는 기존 클래스에 새로운 기능을 추가하려면, 상속을 통해 구현해야 합니다.
따라서 Java 는 기본적으로 Extension 함수가 없습니다.
6. Data Type Inference
Kotlin
변수의 Type 을 명시적으로 지정하지 않아도 괜찮습니다.
val str = "String" // 타입 추론
Java
Java 에서는 변수의 Type 을 명시적으로 선언해야 합니다.
7. Smart Casting
Kotlin
변수의 Type 을 직접 검사하지 않고, 컴파일러가 알아서 검사하고 캐스팅 합니다.
fun smartCast(ant : Any) {
when (any) {
is Int -> println(any + 2)
is String -> println(any)
else -> return
}
}
Java
Java 에서는 변수의 Type 을 검사하고 적절하게 casting 해야 합니다.
8. Checked Exceptions
Kotlin
Kotlin 에서는 checked exceptions 이 없습니다.
따라서 개발자가 선언하거나 catch 할 필요가 없습니다.
Java
Java 에서는 checked exceptions 을 지원하며, 이를 개발자가 직접 선언하고 catch 해야 합니다.
Answer
Primary Constructor
class header 에 초기화되며, class 이름 뒤에 선언합니다.
constructor 키워드를 이용해서 선언합니다.
class Sample constructor(val a: Int, val b: Int) {
// 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 parameter value is : $q")
}
}
Secondary Constructor
Secondary 생성자를 이용하면 변수를 초기화하고 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 을 추가할 수 있습니다.
Answer
val s1 = "Interview"
val s2 = "Bit"
val s3 = "$s1 $s2" // Using String Interpolation
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
Answer
Java 와 같은 일부 언어에서는 static 키워드를 이용하여 class member 를 선언하고, object 생성 없이 이를 활용합니다.
Kotlin 에서는 static 키워드가 없습니다.
companion object 를 이용해서 구현할 수 있습니다.
class CompanionClass {
companion object CompanionObjectName {
// code
}
}
val obj = CompanionClass.CompanionObjectName
// 이름 없이 선언할 수 도 있습니다.
class CompanionClass {
companion object {
// code
}
}
val obj = CompanionClass.Companion
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()
}
Answer
open 키워드는 확장(expansion) 을 용어를 나타냅니다.
Java 에서 쓰이는 final 과 반대 개념입니다.
Kotlin 에서는 기본적으로 클래스를 상속할 수 없습니다.
open 키워드를 이용하면, 상속을 지원합니다.
Kotlin에서는 기본적으로 모든 클래스가 public 합니다.
visibility modifier 가 지정되지 않으면 기본적으로 public 입니다.
Answer
Java 의 switch 문 대신 when 을 이용합니다.
fun main(args: Array<String>) {
var temp = "Interview"
when(temp) {
"Interview" -> println("Interview Bit is the solution.")
"Job" -> println("Interview is the solution.")
"Success" -> println("Hard Work is the solution.")
}
}
Answer
Collection 의 이용 목적에 따라 나눌 수 있다.
Collection 이 변경될 경우 mutable list 를 쓰고, 반대로 view 만 한다면 immutable list 를 씁니다.
val 와 var 는 immutable list 와 mutable list 와는 다른 이용 목적을 가지고 있습니다.
val 는 변수의 값(value)/참조(reference) 가 한 번만 할당되고 이를 실행중 수정할 수 없을때 이용 합니다.
반대로 var 는 변수의 값/참조가 언제든 변경될 수 있을때 이용합니다.
Immutable list 는 아래와 같은 장점들이 있습니다.
immutable 이기 때문에 상태(state) 를 변경하지 않습니다.
따라서 다음 함수(ex. map, filter, reduce ...)로 전달할 때 상태 변경 없이, 새로운 상태를 구성해 전달하는 함수형 프로그래밍 기법을 추구합니다.
side effect 가 없기 때문에 이를 이해하고 디버그하기 더 쉬운 경우가 많습니다.
다중 스레드 시스템에서 write 접근이 필요 없기 때문에, 리소스 경쟁 조건을 유발하지 않습니다.
반대로 단점도 있습니다.
단일 조각(single piece) 을 추가/삭제/변경 하기 위해, collection 전체를 복사하는 것은 비용이 많이 듭니다.
Answer
생성자에서 변수를 초기화하지 않고, 나중에 초기화하려면 lateinit 키워드를 이용해서 변수를 선언합니다.
초기화되기 전까지는 메모리 할당이 되지 않습니다.
lateinit 은 나중에 초기화가되므로 val 을 이용할 수 없습니다.
primitive type 이나 nullable 은 쓸 수 없습니다.
만약 초기화되기 전에 접근하면 예외가 발생합니다.
lateinit var test: String
fun testFunction() {
test = "Interview"
println(test.length) // 9
test = "Bit"
}
Android lifecycle 에서 초기화되는 변수
생성자 외부에서 독립적으로 초기화되는 주입된 클래스 변수 (DI)
단위 테스트에서 @Before 주석이 달린 함수에서 테스트 환경 변수
Answer
객체 초기화에 시간이 너무 많이 소요되는 경우, lazy initialization 이 유용합니다.
lazy initialization 을 이용하여 객체를 선언하면, 객체가 이용될 때 한번만 객체가 초기화됩니다.
만약 이용하지 않으면, 초기화도 되지 않습니다.
class FastClass {
private val slowObject: SlowClass by lazy {
println("Slow Object initialised")
SlowClass()
}
fun access() {
println(slowObject)
}
}
fun main(args: Array<String>) {
val fastClass = FastClass()
println("FastClass initialised")
fastClass.access()
fastClass.access()
}
// FastClass initialised
// Slow Object initialised
// SlowClass@2b12fkk7
// SlowClass@2b12fkk7
Answer
lateinit
초기화 시점 지연
프로그램 어디에서나 초기화 가능
여러번 초기화 가능
Thread-safe 하지 않다.
var 만 가능
primitive type 불가능
lazy initialization
나중에 사용할 때만 초기화
프로그램 전체에서 개체의 단일 복사본이 유지 관리됩니다.
초기화 람다만 가능
한번만 초기화 가능
Thread-safe 하다
val 만 가능
primitive type 가능
아래와 같이 나눌 수 있습니다.
속성이 변경 가능한 경우 lateinit 을 이용
속성이 외부에서 설정되는 경우 lateinit 이용
한 번만 초기화되고, 모두에게 공유되어 있고, 더 내부적으로 설정되어 있는 경우 lazy initialization 이용
lateinit 을 이용할 수 있지만, lazy initialization 을 이용하면 캡슐화가 더 잘되는 경우
Answer
Kotlin 표준 라이브러리에는 객체 context 내에서 코드 블록 실행을 지원하는 많은 함수가 있다.
람다 식을 이용하여 객체에서 이러한 함수를 호출하면 임시 scope 가 생성되는데, 이를 scope function 이라 부른다.
https://kotlinlang.org/docs/scope-functions.html#function-selection
let
let 함수는 null safety call 에 자주 쓰입니다.
val str: String? = "Hello"
val length = str?.let {
println(it)
}
// Hello
apply
객체를 초기화하기 위해 자주 쓰입니다.
val adam = Person("Adam").apply {
age = 20 // same as this.age = 20 or adam.age = 20
city = "London"
}
with
람다 결과를 제공하지 않고, context 객체에 대한 함수를 호출할 때 권장됩니다.
val numbers = mutableListOf("one", "two", "three")
with(numbers) {
println("'with' is called with argument $this")
println("It contains $size elements")
}
run
'let'과 'with' 함수의 조합입니다.
객체 람다가 반환 값의 초기화와 계산을 모두 포함하는 경우 이것이 사용하는 방법입니다.
run 을 이용하여 null-safe 하게 계산을 수행할 수 있습니다.
val service = MultiportService("https://example.kotlinlang.org", 80)
val result = service.run {
port = 8080
query(prepareRequest() + " 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}")
}
also
객체 멤버가 초기화된 후 추가 작업을 수행해야 할 때 이용됩니다.
fun getRandomInt(): Int {
return Random.nextInt(100).also {
writeToLog("getRandomInt() generated value $it")
}
}
Answer
sealed 클래스는 sub 클래스의 집합을 가진 클래스입니다.
sub 클래스의 종류를 제한하고 있습니다.
이를 통해 런타임이 아닌 컴파일 타임에 타입의 유효성을 검사하기 때문에 타입 안정성이 보장됩니다.
sealed class Sample {
class A : Sample() {
fun print() {
println("This is the subclass A of sealed class Sample")
}
}
class B : Sample() {
fun print() {
println("This is the subclass B of sealed class Sample")
}
}
}
fun main() {
val obj1 = Sample.B()
obj1.print()
val obj2 = Sample.A()
obj2.print()
}
Answer
back field 는 접근자(getter/setter) 내부에서만 이용할 수 있는 자동 생성된 field 입니다.
custom getter/setter 를 이용할 때, 필요한 경우 field 식별자로 접근할 수 있습니다.
var marks: Int = someValue
get() = field
set(value) {
field = value
}
Answer
internal, crossinline, expect, reified, sealed, inner, open 같이 Java 에는 없는 명확하지 않은 의미를 가진 키워드들이 있다.
Checked exception 없기 때문에, 이로인한 안정성을 보장받지 못한다고 느낄 수 있습니다.
data class 를 정의하면 많은 코드들이 자동으로 추가되기 때문에, 개발자 입장에선 디버깅 하기에 숨겨져 있다고 느낄 수 있습니다.
Java 에 비해 자료나 학습 자원 부족합니다.
Kotlin 에는 다양한 컴파일 속도가 있습니다.
일부 상황에서는 Java 가 훨씬 빠릅니다.