생성자
생성자는 클래스를 통해 객체가 만들어질 때 기본적으로 호출되는 함수를 말한다.
외부에서 인자를 받아 초기화할 수 있도록 constructor를 통해 부 생성자를 선언하여 사용할 수 있다.
class 클래스 이름 constructor(매개 변수...) { // 주 생성자
...
constructor(매개 변수...) { // 부 생성자
... // 프로퍼티 초기화
}
}
이처럼 사용이 가능하며, 부 생성자는 여러개 선언하여 사용할 수 있다.
이 때, 주 생성자를 초기화 하지 않게 되면, 따로 값을 초기화한 상태의 객체 생성을 할 수 없으며 부 생성자로 선언된 매개 변수 타입으로만 객체를 생성할 수 있다.
주 생성자는 다음과 같이 사용할 수 있다.
Bird1 클래스의 경우 아무것도 생략하지 않은 기본 형태의 주 생성자 이다.
Bird2 클래스는 constructor를 생략한 것으로, 주 생성자를 선언할 때는 해당 키워드를 생략해도 상관 없다.
Bird3 클래스는 Bird2 클래스에서 내부 프로퍼티를 생략하고 생성자의 매개변수에 프로퍼티를 추가한 방법이다.
내부 프로퍼티를 생략하는 경우, 해당 매개변수의 변수 명을 그대로 사용해도 된다.
즉, Bird2에서는 _name 변수를 name 변수에 저장한 후, name을 사용해야 한다.
하지만 Bird3에서는 내부 프로퍼티가 존재하지 않기 떄문에 전달 받은 매개변수 name을 그대로 사용하면 된다.
예제를 보면, Bird2에서 _name을 그대로 사용하려고 하면 에러가 발생하는 것을 확인할 수 있다.
프로퍼티의 기본값 지정
생성자의 매개변수에도 기본값을 사용할 수 있다.
기본값이 설정된 생성자의 경우, 해당 값을 생략하고 생성자를 호출할 수 있다.
일반 함수에 기본값을 설정하는 것 처럼 사용할 수 있다.
중간에 있는 매개변수의 기본값이 비어있을 경우, 앞에서 부터 해당 매개변수의 값까지 전부 입력하는 방식도 있지만, 해당 변수를 명시적으로 호출하여 값을 저장하는 방식을 사용하면 간단하다.
물론, 위의 age = 15로 선언했다고 age = 15, 123 과 같이 id 값을 초기화할 수는 없다.
초기화 블록
객체 생성 시 변수 초기화 이외의 코드를 호출하기 위해서는 부 생성자를 호출해야 한다. 주 생성자를 표현하는 경우 클래스 블록 안에 ({})를 호출할 수 없기 때문에 변수 초기화 이외의 코드를 호출 할 수 없다.
따라서 초기화 시에 꼭 사용해야 하는 코드가 있다면 init 초기화 블록을 사용하면 된다.
위의 예제를 보면 한눈에 알 수 있다.
data1을 출력하는 println문은 당연히 호출될 수 없다. 따라서 주 생성자를 통해 변수 초기화 외의 작업을 수행하기 위해서 init 블록을 사용해 data2를 출력하도록 했다.
아래 부 생성자를 선언한 부분을 보면 중괄호를 사용해 블록을 만들 수 있기 때문에 생성자 호출 시에 data3, data 값을 출력할 수 있다.
상속과 다형성
open 키워드
Kotlin에서 상속을 가능하게 선언하려면 open 키워드를 사용해야 한다.
open 없이 클래스를 선언하면 상속할 수 없는 기본 클래스가 되기 때문에, 상속을 사용하기 위해선 클래스 선언시 open 키워드를 사용해 선언해야 한다.
val value: Int
open class BaseClass(value: Int)
class SampleClass1(value: Int) : BaseClass(value)
class SampleClass2 : BaseClass {
...
constructor(...) : super(value)
...
}
클래스를 상속할 때는 위와 같은 방식으로 사용하면 된다.
open 클래스로 상속가능한 클래스를 생성하고, 상속 받는 클래스를 선언할 때 : 상속할 클래스 를 사용하거나, 부 생성자에 super() 를 사용하면 된다.
여기서, 상속받을 메서드를 커스텀해서 사용하기 위해서는 override를 선언하여 재정의하면 되고, 이 때 final 키워드가 붙어있는 메서드는 재정의가 불가능하다.
다형성
상속 받은 하위 클래스에서 상위 클래스의 똑같은 이름의 메서드나 프로퍼티를 사용할 때, 상위 클래스와는 다른 형태, 다른 결과를 가질 수 있도록 하는 것.
- 오버로딩(overloading) : 동작은 동일하지만 인자의 형식만 달라지는 것.
- 오버라이딩(overriding) : 메서드나 프로퍼티의 이름은 같지만 기존 동작을 재정의 하는 것.
// 오버라이딩
fun add(x: Int, y: Int): Int = x + y
fun add(x: Double, y: Double): Double = x + y
fun add(x: Int, y: Int, z: Int): Int = x + y + z
// 오버로딩
open fun sample() { println("sample()") }
override fun sample() { println("override") }
오버라이딩으로 메서드를 재정의 하기 위해서는, 해당 메서드가 포함된 class를 상속받고 오버라이딩 할 메서드를 open으로 선언해야 한다.
재정의 할 때는 override 키워드를 넣어서 메서드를 선언하여 사용한다.
바깥 클래스 호출
이너 클래스에서 바깥 클래스의 상위 클래스를 호출하기 위해서는 super 키워드와 함께 @ 기호 옆에 바깥 클래스 이름을 작성하면 된다.
이와 같이 호출하면, Inner > Child > Base 순으로 출력된다.
인터페이스에서 참조하기
인터페이스 자체로는 객체로 만들 수 없고 항상 인터페이스를 구현하는 클래스에서 생성해야 한다.
Kotlin은 자바처럼 한 번에 2개 이상의 클래스를 상속받는 다중 상속이 되지 않는다. 하지만, 인터페이스로는 필요한 만큼 다수의 인터페이스를 지정해 구현할 수 있다.
동일한 이름의 프로퍼티나 메서드가 있다면 앵글 브래킷(<>)을 사용해 명시해줄 수 있다.
C class의 test 메서드에서 호출한 것 처럼 f 메서드를 호출하면 된다.
가시성 지시자
각 클래스나 메서드, 프로퍼티의 접근 범위를 가시성이라고 하며, 가시성을 지정해주는 키워드를 가시성 지시자라고 한다.
- private : 외부에서 접근할 수 없다
- public : 어디서든 접근이 가능하다. Default 값
- protected : 외부에서 접근은 불가능. 하위 상속 요소에서는 접근 가능
- internal : 같은 정의의 모듈 내부에서 접근 가능
internal은 JAVA와 다르게 새롭게 정의된 이름. package 단위가 아닌 module 단위이기 때문에, 같은 모듈에 속한다면 패키지가 달라도 접근이 가능하다.
클래스와 클래스의 관계
클래스 들이나 객체들 간의 관계는 연관, 의존, 집합, 구성의 관계가 존재한다.
- 연관 관계 : 2개의 서로 분리된 클래스가 연결은 가지는 것. 단/양 방향 모두 가능하며, 두 요소가 서로 다른 생명주기를 가지고 있다는 특징. 단/양 방향에 상관 없이 각각의 객체의 생명주기에 영향을 주지 않음.
- 의존 관계 : 한 클래스가 다른 클래스에 의존되어 있어 영향을 주는 경우. B 클래스를 생성하는데 A 클래스의 객체가 필요한 경우. B 클래스 생성을 위해 A 클래스가 먼저 생성되어야 하므로, B 클래스는 A 클래스에 의존한다.
- 집합 관계 : 연관 관계와 거의 동일하지만 특정 객체를 소유한다는 개념이 추가. N : N 관계.
- 구성 관계 : 집합 관계와 거의 동일하지만 특정 클래스가 어느 한 클래스의 부분이 되는 것. 구성품으로 지정된 클래스는 생명주기가 소유자 클래스에 의존. 소유자 클래스가 삭제되면 구성하고 있던 클래스도 같이 삭제 된다.
'Language > Kotlin' 카테고리의 다른 글
[Kotlin] 7장. 다양한 클래스와 인터페이스 1 - 추상 클래스와 인터페이스 (0) | 2020.07.03 |
---|---|
[Kotlin] 6장. 프로퍼티와 초기화 (0) | 2020.07.03 |
[Kotlin] 4장. 프로그램의 흐름 제어 (0) | 2020.06.26 |
[Kotlin] 3장. 함수와 함수형 프로그래밍 3 - 다양한 함수 (0) | 2020.06.25 |
[Kotlin] 3장. 함수와 함수형 프로그래밍 2 - 고차 함수와 람다식 (0) | 2020.06.25 |