들어가기 전에
본 포스팅은 개발자를 위한 코틀린 프로그래밍 의 chapter단위로 공부하고, 정리, 부족한 내용의 추가 학습내용을 정리하는 블로깅입니다.
이전 포스팅 ⬇️
01 클래스 알아보기
기본 클래스에 대한 내용은 책의 내용을 정리하는 것보다, 공식문서의 내용이 더 간략하며 이해하기 쉽다고 생각되어, 공식문서 내용으로 대체합니다.
02 상속 알아보기
상속에 대한 내용 또한 책의 내용보다 공식문서의 내용이 더 직관적이라 공식문서로 대체합니다.
03 다양한 클래스 알아보기
3.1 내포 클래스, 이너 클래스, 지역 클래스
- 내포 클래스(Nested class): 외부와 내부 클래스가 별도의 기능을 처리하는 경우
- 이너 클래스(Inner class): 외부와 내부 클래스가 연관된 처리를 하는 경우
- 지역 클래스(local class): 함수 내부에도 클래스를 선언하고 사용할 수 있음
내포 클래스(Nested class)
- 아무런 지시자도 추가하지 않고 정의한다. 클래스 내부에 정의되었지만, 별도의 클래스이다.
- 내포클래스에서 외부에 사용할 때는 외부 클래스 이름으로 접근해서 사용한다.
내포 클래스는 자신을 정의한 외부 클래스 내의 속성 등에 직접 접근할 수 없다. - 내포 클래스의 객체를 생성할 때는 외부 클래스 이름으로 접근해서 내포 클래스 생성자를 호출해 객체를 만든다.
그 후 내보에 있는 메서드를 호출해서 처리한다.
class Outer {
private val bar: Int = 1 // 외부 클래스의 비공개 속성
class Nested {
private val nestVar = 100 // 중첩 클래스의 비공개 속성
fun foo() = 999 // 중첩 클래스의 공개 함수
fun accessOuter() {
// 중첩 클래스는 외부 클래스의 비공개 속성에 접근할 수 없다.
// println(bar)
// this@Outer.bar // Unresolved reference: @Outer
}
}
}
fun main() {
val nested = Outer.Nested()
println(nested.foo())
// println(nested.nestVar) // Cannot access 'nestVar': it is private in 'Nested'
}
이너클래스(inner class)
- 클래스 내부에 클래스를 정의 할정의할 때 inner 예약어를 붙여서 이너 클래스를 정의할 수 있다.
- 이너 클래스의 객체는 this이고 외부 클래스의 객체는 this@외부 클래스 이름으로 사용한다.
그래서 이너 클래스 내부에서 외부 클래스의 속성에 접근할 수 있다. - 외부 클래스에서 이너 클래스를 사용하려면 객체를 생성해서 사용한다.
class Outer {
private val bar: Int = 1 // 외부 클래스의 비공개 속성
inner class Inner {
private val bar = 100 // 내부 클래스의 비공개 속성 (동일 이름)
fun foo() = this@Outer.bar // 내부 클래스의 메서드에서 외부 클래스의 속성에 접근
fun fbar() = this.bar // 내부 클래스의 메서드에서 내부 클래스의 속성에 접근
}
fun getBar() = Inner().fbar()
}
fun main() {
val foo = Outer().Inner().foo() // == 1
Outer().getBar() // == 100
}
내포 클래스(Nested class)와 이너 클래스(inner class)의 차이점
- 내포 클래스는 외부 클래스의 이름으로 접근해서 객체를 생성할 수 있지만, 이너클래스는 멤버처럼 객체로 접근해서 객체를 생성한다.
- 내포 클래스는 (외부 클래스와 별개의 클래스라) 외부 클래스의 속성을 참조할 수 없지만, 이너 클래스는 외부 클래스의 속성을 참조할 수 있다.
04 object 알아보기
최상위 클래스는 object가 아닌 Any이다. object예약어는 클래스 정의와 하나의 객체 생성을 동시에 한다. 그래서 object 예약어를 사용하면 하나의 객체만 만들어진다. (싱글턴 객체)
object 예약어는 3가지 경우에 사용된다.
- object표현식. 익명 클래스로 특정 클래스 없이 객체를 생성
- object선언. 하나의 객체만 만들어서 사용
- 동반객체(companion object). 클래스에 정의해서 클래스와 같이 동반해서 사용
4.1 object 표현식(expression)
익명의 클래스로 익명의 객체를 만들 필요가 있을 때 object표현식으로 하나의 객체를 만든다.
일회성 객체를 사용하는 경우는 아래와 같다.
- 최상위 변수에 바로 객체 할당
- 함수 내부 또는 메서드 내부에서 지역변수에 객체 할당
- 함수의 매개변수에 객체 할당
- 내부함수의 반환값으로 정의해서 외부함수의 지역변수를 내부 메서드에서 참조
지역변수에서 익명 클래스 생성
함수나 메서드 등에서 일회성 객체가 필요한 경우 내부에서 정의하여 바로 사용한다.
- 함수 내부에 여러 개의 변수를 하나의 목적으로 사용할 때 object 표현식에 묶어서 같이 사용한다.
- 익명 클래스의 객체이지만, 변수에 할당되어서 다른 객체와 동일하게 속성을 갱신하는 방식으로 처리할 수 있다.
fun getLength(): Double {
val point = object {
val x = 2.0
val y = 3.0
override fun toString() = "Point($x, $y)" //메서드 재정의
}
println(point)
return Math.sqrt((point.x * point.x + point.y * point.y))
}
fun main() {
println(getLength())
}
// Point(2.0, 3.0)
// 3.605551275463989
함수의 매개변수에 익명 객체 전달
함수의 인자를 전달할 때 익명 객체로 바로 정의해 전달한다.
interface Personnel {
val name: String
val age: Int
}
fun getObject(p: Personnel): Personnel {
return p
}
fun main() { // 인자로 object표현식으로 생성된 객체 전달
val p = object : Personnel {
override val name: String = "홍길동"
override val age: Int = 30
}
println(getObject(p).name)
println(getObject(p).age)
}
메서드의 반환값 사용
interface StrType
class C {
private fun getObject() = object : StrType {
val x: String = "내부속성값"
}
fun printX() {
println(getObject().x)
}
}
fun main() {
val c = C()
c.printX() // 객체를 만들어서 출력하면 익명 객체의 속성을 출력
}
클래스와 인터페이스를 상속하는 object 표현식 처리
open class A(x: Int) {
open val y: Int = x
open fun display() = y
}
interface Add {
fun add(x: Int, z: Int): Int
}
val a: Add = object : A(10), Add { //클래스와 인터페이스를 상속하고 객체 변수에 할당
override val y: Int = super.y // 상속한 클래스를 super로 접근
override fun add(x: Int, z: Int): Int = x + z
}
4.2 object정의
앞서 4.1은 익명 객체를 만드는 경우이지만, object는 하나의 싱글턴 패턴을 만드는 방법이다.
object 정의하는 규칙
- class 예약어 대신 object 예약어를 사용하여 object 이름 정의
- 클래스와 객체 생성이 정의에서 만들어져서 별도의 생성자가 필요 없다.
- object정의 내부 상수인 const val가능
- object 정의를 처음으로 사용할 때 메모리에 로딩된다.
- object 정의의 이름으로 코드 블록에 정의된 속성이나 메서드를 사용할 수 있다.
- 클래스 정의도 있어서 클래스 상속과 인터페이스 구현이 가능
- 내부에 객체와 클래스를 정의할 수 있다.
object 정의와 사용
object Counter {
private var count = 0
fun getCount(): Int = count
fun increment(): Int = ++count
}
fun main() {
Counter.increment()
println(Counter.getCount())
}
클래스 상속
object정의도 클래스 정의와 마찬가지로 다른 클래스를 상속해서 구현할 수 있다.
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
}
fun main() {
println(Operation.add())
println(Operation.sub())
println(Operation.mul())
println(Operation.div())
}
또한 마찬가지로 인터페이스를 구현 할 수 있다.
4.3 동반 객체(companion object)처리
클래스 내부의 Object 정의는 실제 클래스와 상관 없이 작동하는 객체를 만드는 것이다. 하지만 클래스와 상호작용을 하는 동반 객체도 정의할 수 있다.
object vs companion object
- 클래스와 동반 객체는 하나처럼 움직이도록 구성되었다. 그래서 클래스에 동반 객체를 정의하면 다양한 기능을 클래스 이름으로 처리 할 수 있다.
- 클래스로 여러 객체를 만들때, 클래스 내부의 동반객체는 하나만 만들어진다.
그래서 클래스의 여러 객체에서 동반객체는 공유해서 사용. (다른 프로그램 언어의 정적 상태와 동일하다)
class ObjectClass {
object ObjectTest {
const val CONST_STRING = "const string"
fun test() {
println("Object 선언: $CONST_STRING")
}
}
}
class CompanionClass {
companion object {
const val CONST_STRING = "companion const string"
fun test() {
println("Companion 선언: $CONST_STRING")
}
}
}
fun main() {
ObjectClass.ObjectTest.test() // Object 선언: const string
CompanionClass.test() // Companion 선언: companion const string
}
05 확장 알아보기
역시 공식문서가 더 깔끔하고 이해하기 쉬워 공식문서로 대체합니다.
'코틀린 > 개발자를 위한 코틀린 프로그래밍' 카테고리의 다른 글
[chapter07] 클래스 관계 등 추가사항 알아보기 (0) | 2023.08.27 |
---|---|
[chapter06] 내장 자료형 알아보기 (0) | 2023.08.18 |
[chapter04] 함수 알아보기 (0) | 2023.08.06 |
[chapter03] 문장 제어처리 알아보기 (0) | 2023.07.24 |
[chpater 02] 코틀린에서는 모든 것이 객체이다. (0) | 2023.07.21 |