코틀린에서 생성자는 자바와 조금 다르다. 기본 생성자를 대체할 수 있는 init
블록을 제공하며, 생성자에 인자가 필요한 경우 인자처럼 받을 수 있다. 이를 주 생성자라 부른다.
init
블록init
블록은 기본 생성자에서 수행할 작업을 정의할 수 있다. 예를 들면, 초기화 작업을 수행한다.
class Test {
init {
// 기본 생성자에서 수행할 작업
// 예를 들면, 초기화
}
}
class Test(a: Int) {
init {
println(a)
}
}
생성자의 인자를 통해 클래스 내부의 프로퍼티에 값을 할당할 수 있다. 이는 프로퍼티 선언을 대신하며, 값 할당 또한 생성자 호출과 동시에 수행된다.
class Test(val a: Int, val b: Int) {
// 생략..
}
주 생성자 외에 다른 형태의 생성자가 필요한 경우 constructor 키워드를 사용해 선언할 수 있다.
class Test(val a: Int, val b: Int) {
// a 값만 인자로 받는 추가 생성자.
// 기본 생성자를 반드시 호출해야 한다.
constructor(a: Int) : this(a, 0)
// 두 인자의 값을 모두 0으로 지정하는 생성자.
constructor() : this(0, 0)
}
추가 생성자는 인자와 프로퍼티를 함께 선언할 수 없다. 따라서 프로퍼티 선언이 필요한 경우 주 생성자에서 이를 처리해야 한다.
생성자 앞에 접근 제한자를 붙여 가시성을 변경할 수 있다.
class Test private constructor(val a: Int, val b: Char) {
private constructor(a: Int): this(a, 0)
constructor(): this(0, 0)
}
메소드를 함수로 표현하며, 반환 값이 없을 경우 Unit을 사용한다. 이는 생략 가능하다.
클래스의 상속과 인터페이스의 구현은 콜론(:)을 사용한다. 상속받는 경우 반드시 부모 클래스의 생성자를 호출해야 하며, 부모 클래스의 생성자는 super 키워드를 사용해 호출한다.
open class View
class MyView : View {
constructor(context: Context) : super(context) {
// 초기화
}
constructor(context: Context, attrs: AttributeSet?) : super(context, attrs) {
// 초기화
}
// 생성자가 여럿일 경우, this 키워드를 사용해 자기 자신의 생성자를 호출할 수 있다.
constructor(context: Context) : this(context, null) {
// 초기화
}
}
오버라이드는 어노테이션 대신 override 키워드를 사용한다.
open
키워드open 키워드를 사용해 클래스 혹은 함수의 상속 여부를 결정한다.
open class Parent {
open fun foo() {
println("Parent foo")
}
}
class Child : Parent() {
override fun foo() {
println("Child foo")
}
}
this
키워드this 키워드는 클래스 자신을 지칭할 때 사용한다. 클래스 내에서 다른 클래스나 인터페이스의 객체를 동적으로 생성하여 사용하는 경우, 키워드를 사용하는 위치에 따라 this가 의미하는 클래스가 달라질 수 있다.
코틀린은 클래스 내에 정적 필드나 정적 함수를 가질 수 없지만, 동반 객체를 사용하면 클래스 내 모든 멤버에서 접근할 수 있으며, 인스턴스 생성 없이 호출할 수 있는 함수를 작성할 수 있다.
class MyClass {
companion object {
fun create(): MyClass = MyClass()
}
}
val instance = MyClass.create()
단 하나의 인스턴스만 생성되도록 제약을 두는 디자인 패턴으로, 코틀린에서는 object 키워드를 사용해 간편하게 선언할 수 있다.
object Singleton {
val a = "a"
fun aa() {
println(a)
}
}
val value = Singleton.a
Singleton.aa()
자료형을 확인하기 위해 사용한다. 특정 타입이 아닌 경우를 확인하기 위해서는 !is를 사용한다.
fun test(obj: Any) {
if (obj is Int) {
println("Int 입니다.")
} else if (obj is Float) {
println("Float 입니다.")
}
}
특정 변수를 원하는 자료형으로 변환하는데 사용한다.
fun processNumber(number: Number) {
val value: Int = number as Int
}
특정 범위를 순회하거나 해당 범위 내에 특정 항목이 포함되어 있는지 확인할 때 사용한다.
val range: IntRange = 0..10 // 0<=n<=10
val range2: IntRange = 0 until 10 // 0<=n<10
val value: Boolean = 5 in range // true
val value2: Boolean = 5 !in range2 // false
for (i in 5 downTo 1) {
print(i) // 54321
}
for (i in 5 downTo 1 step 2) {
print(i) // 531
}
값을 반환할 수 있으며, checked exception을 따로 검사하지 않는다. 즉, 대부분의 예외를 try-catch 문으로 감싸 처리해야 했던 자바와 달리 코틀린에서는 이를 선택적으로 사용할 수 있다.
코틀린에서는 컴파일 단계에서 널 포인터 예외가 발생할 문제를 해결하기 위해 모든 타입에 명시적으로 널 허용 여부를 함께 표기하도록 한다.
명시적으로 타입 뒤에 ?를 붙여 널 값을 가질 수 있도록 한다.
val a: String? = null
val b: String = "b"
// 아래 두 경우 컴파일 에러 발생
val name: String
val address: String = null
널 값을 허용하지 않는 값 혹은 변수에 널 값을 반환할 수 있는 함수의 결과를 대입해야 하는 경우에 사용할 수 있다.
val value = foo ?: 0 // foo가 null일 때는 0을 반환
널 값 확인을 위해 if문 사용을 하던 자바와 다르게 간편하게 처리 가능하다.
val foo = bar?.baz // bar가 null이 아닐 경우에만 baz를 대입
foo?.bar() // foo가 null이 아닐 경우에만 bar() 함수 호출
자바에서 지원되지 않는 자료형으로 변환을 시도할 가능성이 있는 부분을 try-catch 블록으로 감싸서 처리한다. 코틀린에서는 as? 연산자를 사용해 간편하게 처리한다.
val foo: String = "foo"
val bar: Int? = foo as? Int // 자료형 변환에 실패하므로 bar로 null 값이 할당된다.
val bar: Int? = foo as? Int ?: 0 // 변환 실패 시 기본값을 0으로 설정
널 값을 포함할 수 있는 타입을 널 값을 포함하지 않는 타입으로 변환하여 사용할 수 있다.
val foo: Foo? = ...
val nonNullFoo: Foo = foo!! // foo는 널 값을 포함하지 않음을 보증
foo!!.bar() // foo가 널 값이 아님을 보증하면서 bar() 함수 호출
lateinit
키워드초기화 없이 변수(var)만 선언할 수 있다. 사용 전에 반드시 초기화를 해야 한다. 초기화를 하지 않은 채로 사용하려고 하면 NPE 예외가 발생하기 때문에 초기화 작업은 필수적이다.
코드 복사
class Test {
lateinit var api: API
fun process() {
if (::api.isInitialized) {
// 작업 수행. 초기화 됨.
} else {
// 작업 수행. 초기화 안됨.
}
}
}
코틀린은 자바보다 간결하고 효율적인 문법을 제공하여 개발자가 더 쉽게 코드를 작성할 수 있도록 돕는다. 생성자, 널 안전성, 범위 연산자 등은 코틀린