Language/Kotlin

클래스 Kotlin

JunOnJuly 2024. 7. 14. 02:05
728x90

클래스, 생성자

클래스는 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()
}

 

728x90