클래스 Kotlin
클래스, 생성자
클래스는 class 키워드로 선언합니다.선언자 다음에 클래스의 이름, 중괄호로 나뉘는 본문이 있습니다. 또 본문에 입력할 내용이 없다면 중괄호를 생략할 수 있습니다. 코틀린에서는 생성자를 본문이 아닌 선언부에 작성할 수 있어서 본문이 없는 클래스도 자주 사용합니다.
class Test {}
class Test
클래스의 멤버는 생성자, 변수, 함수, 클래스로 구성됩니다. 그리고 constructor 라는 키워드로 선언하는 함수가 코틀린에서의 생성자입니다. 그리고 클래스 안에서 다른 클래스를 선언할 수 있습니다.
class Person {
var name = "JH"
constructor(name: String) {
this.name = name
}
fun printName() {
println("name : $name")
}
class InnerClass
}
코틀린에서 객체를 생성할 때는 new 키워드를 사용하지 않습니다. 다만 Person("Sung") 같이 클래스 이름과 같은 함수로 객체를 생성합니다.
val person = Person("Sung")
person.printName()
소괄호 안에 전달한 인자는 생성자의 매개변수와 들어맞아야 합니다. Person 클래스의 생성자인 constructor 함수의 매개변수는 (name: String) 이므로 문자열 데이터를 전달받아야 합니다.
주 생성자
코틀린은 생성자를 주 생성자와 보조 생성자로 구분합니다. 둘 다 선언도 가능하고 주 생성자만, 혹은 보조 생성자만 선언할 수도 있습니다. 주 생성자는 constructor 키워드로 클래스 선언부에 선언합니다. 주 생성자 선언은 필수는 아니며 한 클래스에 하나만 가능합니다. 또 constructor 키워드는 생략할 수 있고, 주 생성자를 선언하지 않으면 매개변수가 없는 주 생성자를 자동으로 추가합니다. 그리고 필요에 따라 매개변수를 선언할 수 있습니다.
class Person constructor() {
}
class Person() {
}
class Person {
}
class Person(name: String, lastName: String) {
}
주 생성자를 이용해 객체를 생성할 때 로직을 수행할 수 있는데, 주 생성자의 실행 영역인 본문을 추가하면 오류가 발생할 수 있습니다.
class Person(name: String, lastName: String) {
} {
}
주 생성자는 클래스 선언부에 있기 때문에 주 생성자에 { } 를 추가할 수 없는 것이 그 이유입니다. 이럴 때는 init 키워드를 이용해 주 생성자의 본문을 구현합니다. init 생성자는 꼭 선언할 필요는 없고 주 생성자의 본문을 구현하고 싶을 때 사용합니다. 또 init 영역은 주 생성자뿐 아니라 보조 생성자로 객체를 생성할 대에도 실행되지만 보조생성자는 클래스 안에 선언하므로 중괄호를 이용해 본문을 지정할 수 있습니다. 즉 init 은 일반적으로 주 생성자의 본문을 구현하는 용도로 쓰입니다.
class Person(name: String, lastName: String) {
init {
println("init")
}
}
fun main() {
val person = Person("Jihoon", "Sung")
} // -> 실행결과 : init
다만 생성자의 매개변수는 생성자에서만 사용할 수 있는 지역변수이므로 클래스 멤버 변수처럼 다른 함수에서 사용해야한다면 생성자의 매개변수를 클래스의 멤버변수로 만들어야 합니다. 두 가지 방법이 있는데, 클래스의 멤버 변수를 선언하고 init 영역에서 매개변숫값을 클래스의 멤버 변수에 대입하는 방법, 그리고 주 생성자의 매개변수를 var, val 키워드로 선언하는 방법이 있습니다.
class Person(name: String, lastName: String) {
var name: String
var lastName: String
init {
this.name = name
this.lastName = lastName
}
}
class Person(val name: String, val lastName: String) {
}
보조 생성자
주 생성자와 다르게 보조 생성자는 클래스의 본문에 constructor 키워드로 선언합니다. 클래스 본문에 선언하므로 여러 개를 추가할 수 있습니다. 또 여러 개의 보조 생성자의 매개변수가 다르다면 매개변수가 올바른 보조 생성자가 실행됩니다.
class Person {
constructor(name: String) {
println("constructor(name: String)")
}
constructor(name: String, lastName: String) {
println("constructor(name: String, lastName: String)")
}
}
주 생성자와 보조 생성자는 동시에 선언할 수 있다고 언급했습니다. 각자 선언하면 상관없지만 둘을 동시에 선언하면 둘을 연결해줘야 합니다.
class Person(name: String) {
constructor(name: String, lastName: String): this(name) {
}
}
위와 같이 보조 생성자의 선언부에 this(name) 을 붙여주면 보조 생성자로 객체를 생성할 대 주 생성자가 함께 호출됩니다. 보조 생성자로 객체를 생성하게 된다면 어떤 방식으로든 주 생성자가 호출되게 해야 합니다.
class Person(name: String) {
constructor(name: String, lastName: String): this(name) {
}
constructor(name: String, lastName: String, email: String): this(name, lastName) {
}
}
상속
다른 클래스를 참조해 클래스를 선언하는 것을 상속이라고 합니다. 어떤 클래스를 상속받으려면 선언부에 콜론과 함께 상속받을 클래스 이름을 입력하면 기존 클래스를 재사용 할 수 있습니다. 기본적으로 코틀린의 클래스는 다른 클래스가 상속할 수 없습니다. 다만 open 키워드를 사용해 다른 클래스의 상속을 허용합니다.
open class Super {
}
class Sub: Super() {
}
상위 클래스를 상속받은 하위 클래스의 생성자에는 상위 클래스의 생성자를 호출해야 합니다. 또 꼭 상위 클래스의 생성자 호출문을 클래스 선언부에 할 필요 없이 하위 클래스의 보조 생성자 영역에서 호출할 수 있습니다.
open class Super(name: String) {}
class Sub(name: String): Super(name) {}
class Sub: Super {
constructor(name: String): super(name) {}
}
클래스 상속 시, 상위 클래스에 정의된 멤버를 하위 클래스에서 자신의 멤버처럼 사용할 수 있습니다.
open class Super {
var superNum = 10
fun superFun() {
println("superFun, $superNum")
}
}
class Sub: Super()
fun main() {
val sub = Sub()
sub.superNum = 20
sub.superfun()
}
오버라이딩
상속은 받되 상위 클래스에 정의된 멤버를 하위 클래스에서 재정의해야 할 때 다시 선언하는 것을 오버라이딩이라고 합니다. 일반적으로 하위 클래스에서 새로운 로직을 같은 함수명으로 사용하고 싶을 때 오버라이딩을 사용합니다. 상속 때와 비슷하게 오버라이딩을 허용할 변수나 함수 선언 앞에 open 키워드를 선언하고, 재정의 할 때 선언문 앞에 override 라는 키워드를 추가해야 합니다.
open class Super {
open var classNum = 10
open fun classFun() {
println("superFun, $classNum")
}
}
class Sub: Super()
fun main() {
override var classNum = 20
override fun classFun() {
println("subFun, $classNum")
}
}
접근 제한자
접근 제한자는 클래스의 멤버를 어느정도까지 이용하게 할 것인지 결정하는 키워드입니다. 또 코틀린에서는 클래스의 멤버 뿐 아니라 최상단에 선언된 변수나 함수 또한 접근 제한자로 이용 범위를 지정할 수 있습니다.
- public : 모든 파일에서 가능 / 모든 클래스에서 가능
- internal : 같은 모듈 내에서 가능 / 같은 모듈 내에서 가능
- protected : 사용 불가 / 상속 관계의 하위 클래스에서만 가능
- private : 파일 내부에서만 이용 / 클래스 내부에서만 이용
open class Super {
var publicNum = 10
protected var protectedNum = 20
private var privateNum = 30
}
class Sub: Super() {
publicNum++ // -> 성공
protectedNum++ // -> 성공
privateNum++ // -> 실패
}
fun main() {
val cls = Super()
publicNum++ // -> 성공
protectedNum++ // -> 실패
privateNum++ // -> 실패
}
데이터 클래스
데이터 클래스는 data 키워드로 선언하고 자주 사용하는 데이터를 객체로 묶습니다. 또 VO(value object) 클래스를 편리하게 이용할 수 있게 해줍니다. 일반 클래스보다 데이터를 편리하게 사용하는 것이 주목적이므로 이를 편하게 사용할 수 있는 함수가 존재합니다.
equals() 함수를 사용 시 일반 클래스는 객체 자체를 비교하지만 data 클래스의 경우 객체의 데이터를 비교하므로 객체의 데이터를 비교하는데 편리합니다. 또 데이터를 다루는 것이 주 목적이므로 주 생성자에 val, var 키워드로 매개변수를 선언해 사용하는것이 일반적입니다. 물론 본문에서 변수나 함수를 추가할 수 도 있지만 equals() 함수는 주 생성자에 선언한 멤버 변수의 데이터만 비교 대상으로 삼습니다.
또 객체의 데이터를 확인할 수 있는 toString() 함수의 경우에도 일반 클래스와 data 클래스의 결과가 다릅니다. 일반 클래스의 경우 출력값은 의미있는 데이터가 아니지만 데이터 클래스의 경우 객체가 포함하는 멤버 변수의 데이터를 출력합니다.
data class DataClass(val name: String, val email: String, val age: Int) {}
class NormalClass(val name: String, val email: String, val age: Int) {}
fun main() {
val dc1 = DataClass("JiHoon", "Ji@Hoon.com", "20")
val dc2 = DataClass("JiHoon", "Ji@Hoon.com", "20")
val nc1 = DataClass("JiHoon", "Ji@Hoon.com", "20")
val nc2 = DataClass("JiHoon", "Ji@Hoon.com", "20")
println("dc_equals : ${dc1.equals(dc2)}") // -> true
println("nc_equals : ${nc1.equals(nc2)}") // -> false
println("dc_to_string : ${dc1.toString()}") // -> DataClass(name=Jihoon, ...)
println("nc_to_string : ${nc1.toString()}") // -> com.example. ....
}
오브젝트 클래스
오브젝트 클래스는 익명 클래스를 만들 목적으로 사용합니다. 클래스 이름이 없으므로 클래스를 선언하며 동시에 객체를 생성해야 합니다. 또 선언과 동시에 객체를 생성한다는 의미에서 object 키워드를 사용합니다.
val obj = object {
var data = 10
fun func() {
println("obj")
}
}
fun main() {
obj.data = 20 // -> 실패
obj.func() // -> 실패
}
다만 위와 같이 선언 후 멤버에 접근하려고 시도할 시 오류가 발생합니다. 클래스의 타입을 지정하지 않을 경우 Any 타입으로 취급되는데, Any 타입 객체는 data 와 func 라는 멤버가 없기 때문에 오류가 발생합니다. 때문에 보통은 타입까지 입력해 선언하며 object 뒤에 콜론을 입력하고 상위 클래스 혹은 인터페이스를 입력합니다.
open class Super {
open var data = 10
open fun func() {
println("super : $data")
}
}
val obj = object: Super() {
override var data = 20
override fun func() {
println("obj : $data")
}
}
fun main() {
obj.data = 30 // -> 성공
obj.func() // -> 성공
}
컴패니언 클래스
컴패니언 클래스는 멤버 변수나 함수를 클래스 이름으로 접근하기 위해 사용합니다. 또 컴패니언 클래스는 객체를 생성하지 않고도 클래스 이름으로 특정 멤버를 이용할 수 있습니다. 클래스 내부에서 companion object {} 형태로 선언하면 이 클래스를 감싸는 이름으로 멤버에 접근할 수 있게됩니다.
class CompClass {
companion object {
var data = 10
fun func() {
println(data)
}
}
}
fun main() {
CompClass.data = 20
CompClass.func()
}