[Kotlin] 클래스(3)
📚 클래스(3)
📔 object 정의
📖 object 정의 규칙
- class 키워드 대신 object 키워드를 사용하여 object 이름 정의
- 클래스와 객체 생성이 정의에서 생성되어 별도의 생성자가 필요 없음
- object 정의 내부에 상수인 const val이 가능
- object 정의를 처음으로 사용할 때 메모리에 로딩
- object 이름으로 코드 블록에 정의된 속성이나 메서드 사용 가능
- 클래스 정의도 있어 클래스 상속과 인터페이스 구현이 가능
- 내부에 객체와 클래스 정의 가능
📖 object 정의와 사용
object Counter {
private var count: Int = 0 // 비공개 속성 정의
fun currentCount() = count // 비공개 속성 조회
fun increment() = ++count // 비공개 속성 갱신
}
Counter.increment()
println(Counter.currentCount())
1
📖 클래스 상속
open class Value(open val x: Int, open val y: Int) // 슈퍼클래스 정의
object Operation : Value(100, 200) { // 객체에서 슈퍼클래스 정의
override val x = super.x // 속서을 오버라이딩
override val y = super.y
fun add() = x + y
fun sub() = x - y
fun mul() = x * y
fun div() = x / y
}
println(Operation.add())
println(Operation.sub())
println(Operation.mul())
println(Operation.div())
300
-100
20000
0
📖 object에서 인터페이스 구현
interface Actionable { // 인터페이스 정의
fun eat(): Unit
fun talk(): Unit
fun pray(): Unit
}
object Action : Actionable { // 인터페이스 구현
override fun eat() {
println("음식먹기")
}
override fun talk() {
println("대화하기")
}
override fun pray() {
println("기도하기")
}
}
Action.eat()
Action.talk()
Action.pray()
음식먹기
대화하기
기도하기
📖 클래스 내부에 object 사용
class Person(val name: String, val age: Int) {
object Inner {
fun foo() = "bar " // 내포된 object 메서드
fun getPerson(p: Person) = p.info() // 내포된 object에서 외부 클래스의 객체를 전달받아 처리
fun create(name: String, age: Int) = Person(name, age) // 외부 클래스의 객체 생성
}
fun info() = "이름: $name 나이: $age" // 객체 속성 출력
}
println(Person.Inner.foo()) // 클래스의 이름만으로 내포된 객체 접근
val p = Person.Inner.create("가나다", 20)
println(p.info())
println(Person.Inner.getPerson(p))
bar
이름: 가나다 나이: 20
이름: 가나다 나이: 20
📔 동반 객체 처리
📖 object와 companion object 비교
- object 키워드를 사용하는 것은 동일하나, companion object는 말 그대로 앞에 companion 키워드르 사용
- object는 클래스이름.객체이름.메서드를 호출하지만, companion object는 클래스이름.메서드 로 더 간단히 메서드를 호출
class ObjectClass {
object ObjectText { // 싱글톤 객체 생성
const val CONST_STRING = "1" // 상수 정의
fun test() { println("object 선언 : $CONST_STRING") }
}
}
class CompanionClass {
companion object { // companion object 정의
const val CONST_TEST = 2 // 상수 정의
fun test() { println("companion object : $CONST_TEST") }
}
}
CompanionClass.test()
ObjectClass.ObjectText.test()
companion object : 2
object 선언 : 1
📖 companion object로 외부 클래스 객체 생성
class Person private constructor(val name: String) { // 클래스의 생성자 보호
var age: Int = 0
companion object {
fun create(name: String, age: Int) : Person { // 팩토리 함수 작성
val result = Person(name)
result.age = age
return result // 클래스로 객체 생성
}
}
}
val p = Person.create("홍길동", 44)
println("이름: ${p.name} 나이: ${p.age}")
이름: 홍길동 나이: 44
📖 클래스에서 companion object의 속성 참조
class OuterClass {
companion object {
private val private_str = "companion object 비공개 속성" // companion object 보호 속성
val public_str = "companion object 공개 속성"
}
fun getSecretValue() = private_str + " " + public_str // 클래스의 메서드에서 companion object의 속성 접근
}
println(OuterClass.public_str)
println(OuterClass().getSecretValue())
companion object 공개 속성
companion object 비공개 속성 companion object 공개 속성
📖 내부 클래스에서 companion object 참조
companion object와 내부 클래스를 같이 사용할 때 companion object의 다양한 정보를 내부 클래스가 사용할 수 있어 다양한 처리 기능을 만들 수 있다.
class OuterClass(val name: String) {
class NestedClass(val man: String) { // 내부 클래스
fun getCompInfo() = "$man - companion object 멤버 : $con $attr - ${getDate()}"
fun getOutInst(): String {
val out = OuterClass("홍길동") // 외부 클래스의 객체 생성
return "$man - 외부 객체 멤버: ${out.name}"
}
}
companion object {
const val con = "companion object 상수"
val attr = "companion object 속성"
fun getDate(): String = "2022-04-10"
}
}
println(OuterClass.NestedClass("내부 클래스의 객체").getCompInfo())
println(OuterClass.NestedClass("내부 클래스의 객체").getOutInst())
내부 클래스의 객체 - companion object 멤버 : companion object 상수 companion object 속성 - 2022-04-10
내부 클래스의 객체 - 외부 객체 멤버: 홍길동
📖 이너 클래스에서 동반 객체 참조
class OuterClass(val name: String, val age: Int) {
inner class InnerClass(val man: String) {
fun getCompInfo() = "$man - companion object 멤버 : $con $attr - ${getDate()}"
fun getOutInst() = "$man - 외부 객체 멤버 : $name $age"
}
companion object {
const val con = "companion object 상수"
val attr = "companion object 속성"
fun getDate(): String = "2022-04-10"
}
}
println(OuterClass("홍길동", 30).InnerClass("이너클래스의 객체").getCompInfo())
println(OuterClass("홍길동", 30).InnerClass("이너클래스의 객체").getOutInst())
이너클래스의 객체 - companion object 멤버 : companion object 상수 companion object 속성 - 2022-04-10
이너클래스의 객체 - 외부 객체 멤버 : 홍길동 30
📔 일반 속성(property)과 확장 속성(extension property)
코틀린의 속성은 기본적으로 field를 제공하고, getter/setter 메서드를 제공한다. 코틀린은 이름으로 접근하고자 할 때, 조회일 경우 게터 메서드, 갱신일 경우 세터 메서드로 변환해서 처리한다.
📖 속성(Property) 정의와 특징
- 속성을 정의할 수 있는 곳: 최상위 레벨(전역변수), 클래스, 객체, 인터페이스, 추상 클래스 등
- 속성 정의의 키워드 차이: 변경할 수 없는 속성을 val로 정의할 때 get 메서드 생성. 변경할 수 있는 속성을 var로 정의할 경우 get과 set 메서드 생성. 확장 속성일 때 이 두 가지는 직접 정의해 처리
- 속성의 특징: 속성에는 정보를 관리하는 영역인 field가 있음. 이 필드를 배경필드(backing field)라고 하는데, 확장 속성은 이 배경필드가 없으므로 대신 실제 값을 처리하도록 구현할 필요가 있음
📖 확장 속성
- 클래스 또는 object 외부에 정의되는 속성
- 확장 속성에는 배경 필드를 제공하지 않음. 따라서 확장 속성의 값을 게터 메서드 내부에 별도로 값을 반환하도록 작성해야 함
최상위 속성
파일에 전역 변수로 정의한 속성은 실제 클래스나 object 등에 포함되지 않는다.
val person: Int = 0
get(): Int { // getter 메서드
return field // 속성의 배킹 필드
}
var man: Int = 0
get() = field // getter 메서드
set(value) { // setter 메서드, 매개변수 value
field = value // 속성의 배킹 필드에 갱신
}
println(person)
man = 100
println(man)
0
100
val weight: Int
get(): Int { // getter 메서드
return 100 * 2 // 배킹 필드없이 계산식으로 처리
}
var height: Int = 100 // 초기화 반드시 필요
get() = field * 2
set(value) { field = value }
println(weight) // 항상 동일 값 처리
println(weight)
println(height)
height = 300 // 변경 가능
println(height)
200
200
200
600
📖 변경 가능한 속성을 변경 불가능하게 처리
var privateW: Int = 100 // 초기화 반드시 필요
get() = field * 2
private set // 최상위 레벨 속성에서는 비공개가 작동하지 않음
println(privateW)
privateW = 300
println(privateW)
class Weight(weight: Int) {
var privateW: Int = weight
private set // 클래스 속성에서는 비공개 처리
fun setW(value: Int) { // 별도의 메서드로 갱신 처리
privateW = value
}
}
val w = Weight(100)
println(w.privateW)
// w.privateW = 333 // 클래스 밖에서 접근 불가
w.setW(333) // 메서드를 호출해서 값을 변경
println(w.privateW)
200
600
100
333
📖 속성 확장 규칙
- 확장을 지정할 때는 어느 클래스인지 먼저 정의하고 점연산자 다음에 속성 이름을 지정
- 확장할 때 가장 중요한 것은 어느 클래스에 확정할지 결정하는 것이다. 그래야 이 클래스의 객체를 전달받아 처리할 수 잇다. 이런 객체를 리시버라고 하며, 클래스를 리시버(receiver) 클래스라고 함
- 간단히 말해 속성 앞에 접두사인 리시버 클래스를 지정해야 확장이라는 것을 알 수 있음
- 속성과 확장 속성이 같은 이름일 때에는 속성이 먼저 조회되므로 확장 속성은 사용할 수 없음. 확장 속성을 사용하려면 기존 속성을 비공개 처리해야 확장 속성만 외부에서 사용 가능
📖 모든 클래스의 속성 확장
최상위 클래스 Any를 리시버 클래스로 사용해서 확장 속성을 지정하면 하위 모든 클래스는 이 확장 속성을 사용할 수 있음
val Any.classTag: String?
get() = this::class.java.kotlin.simpleName // 클래스 참조
class Person // 임의의 클래스 지정
println((100).classTag)
println("문자열".classTag)
println(Person().classTag)
Int
String
Person
📖 일반 클래스 속성 확장
객체지향은 변경보다 추가를 더 선호한다. 따라서 속성을 추가할 때 확장 속성을 사용해서 클래스의 기능을 확장한다.
class Temperature(var 섭씨온도: Float)
val a = Temperature(100.1f)
var Temperature.화씨온도: Float // 화씨온도 계산 속성 추가
get() = (섭씨온도 * 9 / 5) + 32 // 계산식을 직접 처리
set(value) {
섭씨온도 = (value - 32) * 5 / 9
}
println("화씨온도: " + a.화씨온도)
println("섭씨온도: " + a.섭씨온도)
a.화씨온도 = 30.2f
println("화씨온도: " + a.화씨온도)
println("섭씨온도: " + a.섭씨온도)
화씨온도: 212.18
섭씨온도: 100.1
화씨온도: 30.2
섭씨온도: -0.9999996
📖 object나 companion object에도 확장 가능
object A // object 속성 확장
val A.extVal: String
get() = "object 확장 속성" // backing field 없이 초깃값 설정
println(A.extVal)
class AA { // 클래스
companion object {} // companion object
}
val AA.extVal: String // 객체 속성 추가
get() = "object 객체 확장 속성" // backing field 없이 초깃값 설정
val AA.Companion.extVal: String // companion object 속성 추가
get() = "companion object 확장 속성" // backing field 없이 초깃값 설정
println(AA.extVal)
println(AA().extVal)
object 확장 속성
companion object 확장 속성
object 객체 확장 속성