kotlin 주요 class
개인 광고 영역
Kotlin의 기본 클래스를 정리합니다.
Kotlin은 별도의 클래스 정의하지 않고, Util을 생성하기도 합니다.
이러한 방법 및 상속시에 사용 가능한 추상 클래스 등을 살펴보도록 하겠습니다.
기본 클래스
기본 클래스의 형태는 Java
와 동일합니다.
class ClassName {
}
별도의 함수 정의가 없다면 다음과 같이 {}
을 제외할 수 있지만, 이런 식으로 사용하는 부분은 많지 않겠죠.
class ClassName
생성자
생성자는 항상 overloading
을 통해서 여러 개의 형태가 만들어지게 됩니다. Kotlin도 Java와 함께 쓰다 보니 예외는 없습니다.
다만 코틀린으로 클래스를 생성하고, 코틀린에서만 사용하게 되면 default 변수 정의
가 가능하기 때문에 별도의 overloading
을 정의하지 않아도 됩니다.
fun setItems(val name: String, val age: Int = 0) {}
예를 들면 위와 같이 default
를 정의해주면 overloading
을 하지 않아도 무관합니다.
다만 Java에서도 같은 함수를 사용하게 만들어야 하고, default
를 허용하지 않아서 결국 overloading
을 만들어야 합니다.
우선 Java에서의 생성자는 다음과 같습니다.
class ClassName {
public ClassName(String name) {
// ...
}
}
위와 같이 별도의 함수로 생성자를 선언합니다.
코틀린에서는 다음과 같이 클래스 이름 끝에 ()
을 포함하여 생성자를 선언해주게 됩니다.
class ClassName(name: String) {
// Class 정의
}
클래스 생성자의 원형은 다음과 같습니다.
class ClassName constructor(name: String) {
// Class 정의
}
원래는 constructor
가 생성자를 나타냅니다. constructor
을 매번 써주는 것도 코드량만 늘어나고 불필요한 사항이라서 생략이 가능한 형태로 제공을 하기 때문에 constructor
은 생략할 수 있는 형태로 제공되며, 간단하게 class ClassName(name: String)
의 형태로 선언이 가능합니다.
생성자 초기화
Java에서는 생성자에서 많은 일을 할 수 있습니다.
다음과 같이 초기화를 하기도 합니다.
class ClassName {
private int[] age;
public ClassName() {
age = new int[10];
}
}
코틀린에서는 constructor
에서는 이러한 행동을 할 수 없습니다. 하지만, init
블락이 별도로 제공됩니다. init
에서 다음과 같이 초기화를 해줄 수 있습니다.
class ClassName(name: String) {
init {
println("Initialized with value ${name}")
}
}
init
블락을 사용하지 않고, 다음과 같이 upperName
이라는 String 변수에 생성자에서 넘겨받은 name
을 toUpperCase
하여 바로 대문자로 초기화할 수 있습니다.
class ClassName(name: String) {
val upperName = name.toUpperCase()
}
별도의 생성자 정의 없이 위와 같이 바로 초기화가 가능한 형태로 사용할 수 있습니다.
추가로 val
은 읽기 전용이고, var
은 읽기 쓰기가 가능한 형태입니다.
생성자에 val
로 정의하였다면 읽기만 가능하고, var
로 정의하였다면 읽기 쓰기가 가능한 형태입니다. Java에서는 final
입니다. 다음과 같은 형태로 생성자 정의가 가능합니다.
class Person(val name, var age: Int) {
// ...
}
1개 이상의 생성자
클래스 이름의 선언과 동시에 생성자를 선언하는 Kotlin에서도 1개 이상의 생성자 정의가 가능합니다.
우선 자바에서는 다음과 같이 합니다.
class Person {
public Person(String name) {
// name 정의
}
public Person(String name, int age) {
this(name);
// age 정의
}
}
Kotlin에서는 constructor
을 여러 개로 정의할 수 있는 대 다음과 같은 형태로 생성자 정의가 가능합니다.
class Person(val name: String) {
constructor(name: String, age: Int) : this(name) {
// ...
}
}
첫 번째 생성자는 val name: String
만을 초기화할 수 있고, 2번째는 name: String, age: Int
를 가지는 생성자입니다.
name
은 중복적으로 사용되는 키워드이므로 기존 생성자로 넘겨주기 위해서 this()
키워드를 사용하여 정의 가능합니다.
생성자 private으로 사용하기
Java에서는 싱글톤을 사용하는 경우 private
을 정의하여 생성자를 가립니다.
Kotlin에서도 private
의 생성자를 만들 수 있는데 다음과 같습니다.
class PrivateConstructor private constructor() {
// Class 정의
}
클래스 사용하기
클래스를 생성하기 위해서 Java에서는 new
키워드를 사용하게 됩니다.
new
를 사용하여 다음과 같이 ClassName
클래스에 접근하여 사용할 수 있습니다.
class ClassName {
// Class 정의
}
ClassName className = new ClassName();
코틀린에서는 new
라고 쓰지 않고도 Class
사용이 가능합니다.
아래와 같이 해당 클래스의 생성자만 정의하면 바로 사용이 가능합니다.
class ClassName {
// Class 정의
}
val className = ClassName()
Kotlin의 상속
Java에서는 상속을 extends
와 implements
으로 사용합니다.
public abstract class Base {
public Base(int age) {
}
}
// 추상 클래스의 상속을 다음과 같이 사용
public class UseBase extends Base {
public UseBase(int age) {
super(age);
}
}
kotlin에서는 abstract
와 interface
에 대한 별도 구분 없이 :
으로 구분합니다.
open class Base(age: Int)
// open으로 생성한 추상 클래스를 다음과 같이 사용
class UseBase(age: Int) : Base(age)
간단하게 위와 같이 :
으로 구분하여 상속을 구현하게 됩니다.
Android
에서 많이 사용할 View
상속은 다음과 같이 처리할 수 있습니다.
class MyView : View {
constructor(ctx: Context) : super(ctx) {
// 정의
}
constructor(ctx: Context, attrs: AttributeSet) : super(ctx, attrs) {
// 정의
}
}
함수 Overriding
함수를 Overriding을 하기 위해서 java에서는 abstract
또는 interface
를 사용합니다.
abstract
은 extends
을 이용하여 상속을 구현하고, interface
은 implements
을 이용하여 상속을 구현합니다.
abstract class AbstractBase {
abstract void onCreate();
final void onResume() {} // 상속에서 재 구현 금지
}
interface Base {
void onStart();
}
public Use extends AbstractBase implements Base {
// Abstract 상속에 대한 구현
@Override
void onCreate() {}
// interface에 대한 구현
@Override
void onStart() {}
}
위와 같이 상속을 구현합니다.
final
에 대해서는 아래쪽에서 설명하고, 우선 kotlin에서 Interface
와 Abstract
를 :
으로 처리한다고 말씀드렸습니다.
다음과 같이 open
이라는 키워드를 함께 사용하면 다음과 같습니다.
interface BaseItem {
fun onStart()
}
open class Base {
open fun v() {}
fun nv() {}
}
class AbstractBase() : Base(), BaseItem {
// Base의 open 키워드를 통해 상속을 구현
override fun v() {}
// Interface에서 구현한 상속
override fun onStart() {}
}
kotlin open
kotlin에서 사용하게 되는 open
키워드는 다음과 같습니다.
- java에서는 상속의 재 정의를 방지하기 위해
final
을 사용합니다. - kotlin에서는 반대로 상속의 재 정의를 허용하기 위해서
open
을 사용합니다.
java에서는 final
을 통해서 상속의 재 정의를 막지만 kotlin에서는 open
을 이용하여 함수의 재 정의를 할 수 있도록 허용합니다.
open
클래스의 open
함수가 있다면, 이는 상속을 받아 재 정의가 가능한 형태가 제공됩니다.
그래서 다음과 같이 open
클래스를 구현하게 되면 v()
는 재 정의가 가능하고, nv()
는 재 정의가 불가능한 형태가 만들어집니다.
open class Base {
open fun v() {
print("ABC")
}
fun nv() {}
}
open은 변수에서도 사용이 가능한데 다음과 같습니다.
open class Foo {
open val x: Int get { ... }
}
class Bar(override val x: Int) : Foo() {
}
위의 코드를 예제로 작성하면 다음과 같고 실행하면 12라는 결과를 얻을 수 있습니다.
fun main(args: Array<String>) {
print(C(12).x)
}
open class A {
open val x: Int = 0
}
class C(override val x: Int) : A() {
}
Overriding Rules
다음과 같은 코드에서 다중 상속을 허용하게 됩니다.
- Kotlin의 사이트에 나오는 코드를 그대로 가져왔습니다.
open class A {
open fun f() { print("A") }
fun a() { print("a") }
}
interface B {
fun f() { print("B") } // interface members are 'open' by default
fun b() { print("b") }
}
class C() : A(), B {
// The compiler requires f() to be overridden:
override fun f() {
super<A>.f() // call to A.f()
super<B>.f() // call to B.f()
}
}
해당 C()
의 f()
함수를 실행한 결과는 다음과 같습니다.
- print(“A”), print(“B”)
open
의 클래스 A의 f()
함수와 B interface의 f()
함수가 다중으로 상속되는 상황입니다. 그래서 super<Base>
의 형태로 함수를 각각 불러올 수 있습니다. class C()에서 보듯 f()
함수를 정의한 부분을 살펴볼 수 있습니다.
super<A>.f()
는 A.f()를 하는 것과 동일합니다.super<B>.f()
는 B.f()를 하는 것과 동일합니다.
함수 f()
가 open class A
의 f()
도 상속받았고, interface B
의 f()
도 함께 상속이 된 상태라서 가능한 rule입니다.
추가로 Java 8 이전 버전을 공부하신 분은 interface
에서 함수 정의
가 가능함을 의아해하실 수 있을 것 같습니다. 다음의 java 8 virtual extension methods
자료를 참고하시면 되겠습니다.
Abstract class
Java에서 사용하는 추상 클래스 정의는 다음과 같습니다.
public abstract class Base {
public Base(String name) {
updateName(name);
}
protected abstract void updateName(String name);
}
// Base를 상속 받는 클래스
public class UseName extends Base {
public UseName(String name) {
super(name);
}
@Override
protected void updateName(String name) {
// 정의
}
}
위와 같이 정의를 하여 사용합니다. kotlin에서도 기본 Abstract은 동일하게 구현하게 됩니다.
abstract class BaseUse(name: String) {
init {
updateName(name)
}
protected abstract fun updateName(name: String)
}
// Base를 상속 받는 클래스
class UseName(name: String) : BaseUse(name) {
override fun updateName(name: String) {
// 정의
}
}
추가로 kotlin의 open class
를 추가하여 다음과 같이 확장도 가능합니다.
open class Base {
open fun f() {}
}
abstract class AbstractBase : Base() {
override abstract fun f()
}
open
클래스의 f()
함수는 override
가 가능한 형태입니다. 이를 AbstractBase
에서 상속받고, abstract
으로 확장할 수 있습니다.
Java의 static 메소드 사용하기
java에서는 함수 내에 static
을 선언하여 외부에서 접근을 하게 됩니다.
public class ClassName {
// getInstance() 구현을 위하여 private
private ClassName() {
}
public static ClassName getInstance() {
return new ClassName();
}
}
// Use...
ClassName className = ClassName.getInstance();
위와 같은 getInstance
의 형태로 사용하게 됩니다.
kotlin에서는 companion
키워드를 사용하여 구현하게 됩니다.
// 생성자 private 처리
class ClassName private constructor() {
// 외부에서 static 형태로 접근 가능
companion object {
fun getInstance() = ClassName()
}
}
// Use kotlin
val className = ClassName().getInstance()
// Use java
ClassName className = ClassName.Companion.getInstance();
Sealed Classes
kotlin의 마지막 내용인 Sealed Classes
내용입니다.
열거 형태로 자기 자신을 return이 가능하고, 다음과 같이 class
와 object
에 자기 자신을 return
하는 클래스 형태를 제공합니다.
대략 다음을 실행하면 eval
이라는 함수에 Expr
을 셋팅합니다. when
으로 동작하는데 Expr
의 Sum
클래스를 초기화합니다. 초기화시에는 2개의 Expr
을 사용하고, 이를 +
하는 함수입니다. 실제로는 expr.number
를 가져와서 처리하는 예제입니다.
fun main(args: Array<String>) {
print(eval(Expr.Sum(Expr.Const(12.44), Expr.Const(12.33))))
}
sealed class Expr {
class Const(val number: Double) : Expr()
class Sum(val e1: Expr, val e2: Expr) : Expr()
object NotANumber : Expr()
}
fun eval(expr: Expr): Double = when(expr) {
is Expr.Const -> expr.number
is Expr.Sum -> eval(expr.e1) + eval(expr.e2)
Expr.NotANumber -> Double.NaN
// the `else` clause is not required because we've covered all the cases
}
위의 코드를 보아서는 이해가 되기는 하는데… 응용을 어떤 식으로 하면 좋을지는 저도 잘 모르겠습니다.
우선은 코틀린 설명에 나온 코드를 그대로 가져와 보았습니다.
마무리
코틀린 클래스를 정리해보았습니다. 기본 클래스 선언하는 방법부터 클래스 상속/다중 상속 등에 대해서 정리해보았습니다.
코틀린 클래스 문서에 잘 나와 있는 부분을 제 임의로 몇 개 더 추가하여 설명을 해보았습니다.
마지막의 Sealed Classes의 경우 C#에도 존재하는대 사용 방법은 조금 다르게 나와 있습니다.
궁금하신 분은 다음의 C# 설명 자료를 추가로 참고하시면 되겠습니다.
Comments