간략한 소개
class
classs 는 object 의 청사진이다.
붕어빵틀과 붕어빵 개념? 일 것 같다.
codelabs 에서는 Objects are instances of classes 라고 한다.
참고. interface
Interface 는 다른 Class 가 구현해야하는 함수에 대한 specification 이다.
codelabs 에서는 어항 클래스가 있고 청소 인터페이스가 있다고 했을 때, 청소는 어항말고도 가전제품, 집, 사무실 등 여러 다른 클래스에서도 쓰일 수 있기 때문에 청소를 인터페이스로 빼고 어항 클래스에서 청소 인터페이스를 구현하는 예시를 들어주었다.
- 생성자
- 생성자를 클래스 옆에 constructor() 안에 함수 parameter 처럼 쓸 수 있다. ( 그래서 default value 지정도 함수 인자처럼 할 수 있다. ) 그리고 객체를 생성할 때 해당 argument 를 넘겨야한다.
이것을 Primary Construtor 라고 부른다.
primary constructor 는 하나만 가질 수 있고 다수의 Secoindary Constructor 를 가질 수 있다.
참고1. 추상 클래스가 아닌 클래스에 어떠한 생성자도 선언하지 않았다면, 코틀린은 자동으로 파라미터없는 primary constructor 를 생성해준다.
class Aquarium constructor(length: Int = 100, width: Int = 20, height: Int = 40) {
// Dimensions in cm
var length: Int = length
var width: Int = width
var height: Int = height
...
}
인자로 받아와서 property 를 초기화하고 있다. 자바였다면 Aquarium 이라는 생성자 함수를 따로 만들어야했을 것이다. 이 코드는 아래와 같이 더 간결하게사용할 수 있다.
참고. 여기서 property 는 멤버변수 + getter/setter 를 말한다.
참고2. primary constructor 의 default 접근제어자는 public 이며 constructor 키워드를 아래 코드와 같이 생략할 수 있다. private 을 붙이고 싶다면 private constructor 를 사용하면된다.
class Aquarium(var length: Int = 100, var width: Int = 20, var height: Int = 40) {
...
}
그냥 생성자에 properties 를 넣어서 코드가 더 간결해졌다. default value 기능도 사용할 수 있어서 생성자 overloading 을 없앨 수 있다.
- init, primary constructor 의 연장선
primary construtor 에서 단순히 properties 를 정의하고 값을 할당하는 것 이외에 초기화 코드를 작성하고 싶다면 다음과 같이 init 키워드를 사용해서 코드를 작성한다.
참고3. init 블록은 primary constructor 의 일부라고 생각하면 된다.
class Aquarium (var length: Int = 100, var width: Int = 20, var height: Int = 40) {
init {
println("aquarium initializing")
}
init {
// 1 liter = 1000 cm^3
println("Volume: ${width * length * height / 1000} l")
}
}
위와 같이 init 을 여러번 사용할 수도 있고, 객체를 생성할 때 쓰여진 순서대로 호출된다.
- secondary constructor ( 키워드는 constructor, 생략 불가 )
package aquarium
class Aquarium(var width: Int = 20, var height: Int = 40, var length: Int = 100) {
init {
println("Aquarium Init")
}
init {
println("Aquarium Second Init")
}
fun printSize() {
println("PrintSize : Width: $width cm " +
"Length: $length cm " +
"Height: $height cm ")
}
constructor(numberOfFish: Int) : this() {
println("constructor 호출")
// 2,000 cm^3 per fish + extra room so water doesn't spill
val tank = numberOfFish * 2000 * 1.1
// calculate the height needed
height = (tank / (length * width)).toInt()
}
}
primary constructor 가 없다면 this 를 생략해도 되지만 지금처럼 primary constructor 가 있다면 this() 생성자를 이용해서 직간접적으로 primary constuctor 에 위임해야한다.
위와 같은 클래스의 객체를 생성하고 아래와 같이 main 에서 호출해주면
package aquarium
fun buildAquarium() {
val aquarium6 = Aquarium(numberOfFish = 124)
aquarium6.printSize()
println("Volume: ${aquarium6.width * aquarium6.length * aquarium6.height / 1000} l")
}
fun main() {
buildAquarium()
}
Aquarium Init
Aquarium Second Init
constructor 호출
PrintSize : Width: 20 cm Length: 100 cm Height: 136 cm
Volume: 272 l
위와 같은 결과가 나온다.
정리하면 primary constructor, init, property 순서대로 초기화 -> secondary constructor
- 자동으로 getter 와 setter 추가
- properties 의 getter setter 를 자동으로 생성해줘서 property 에 바로 접근할 수 있다.
예를 들어, width 라는 변수에 접근하려면 객체이름.width 로 값을 가져올 수 있는데 이는 getter 함수로 가져온 것이라고 이해하면된다.
그리고 객체이름.width = 5 이런식으로 값을 넣을 때는 setter 함수로 가져온 것이라고 이해하면된다.
property 가 val 로 선언되어있다면 당연히 한번만 값을 할당할 수 있을 것이다. ( 참고로 코틀린은 가능하면 val 을 더 선호한다. 가능하면 val 을 사용하자 )
- 명시적으로 getter 와 setter 추가
property 의 값을 단순히 가져오기만 하는 것이 아니라 가공해서 가져오고,
지금까지의 클래스에 volume 이라는 property 를 추가하고 getter 와 setter 를 명시적으로 선언하는 코드를 보자
var volume: Int
get() = width * height * length / 1000
set(value) {
height = (value * 1000) / (width * length)
}
다음과 같이 property 아래에 get() set() 을 써주면 된다. fun 키워드와 반환타입이 생략된 함수처럼 느껴진다.
fun buildAquarium() {
val aquarium6 = Aquarium(numberOfFish = 29)
aquarium6.printSize()
aquarium6.volume = 70
aquarium6.printSize()
}
fun main() {
buildAquarium()
}
위 코드를 실행한 결과는 아래와 같다.
Aquarium Init
Aquarium Second Init
constructor 호출
PrintSize : Width: 20 cm Length: 100 cm Height: 31 cm
PrintSize : Width: 20 cm Length: 100 cm Height: 35 cm
- companion object vs object class
클래스 안에 상수를 정의하고 싶다면 companion 키워드를 사용하면된다.
companion object 는 클래스 안에 있는 싱글톤 객체가 된다.
그렇다면 object class 에 쓰인 싱글톤 객체와는 어떤 차이가 있을까? 초기화 되는 시점이 다르다.
object class 는 프로그램 실행중에 처음으로 사용될 때 lazy 로 초기화되는 반면 companion object 는 해당 클래스가 만들어질 때 함께 만들어진다.
- const val 과 val 의 차이
val 과 var 의 차이는 익히 알고 있을 것이다.
그렇다면 const val 과 val 의 차이는 무엇일까?
const val 은 컴파일되는 시점에 정의된다. 반면에 val 은 실행시점에 정의된다.
그래서 val 은 프로그램을 실행하는 동안에 함수로 값을 할당할 수 있지만 const val 은 그럴 수 없다.
const val 은 object class 나 companion object 에 구성한다.
- 확장함수 ( extension functions )
클래스의 소스코드에 직접 접근하지 않고도 해당 클래스에 함수를 정의하여 기능을 추가할 수 있게 만들어준다.
실제로 클래스를 수정하는 것은 아니다.
당연히 해당 클래스에 private 으로 지정된 변수엔 접근할 수 없다.
확장 당한 클래스를 revceiver 라고 부른다.
open class AquariumPlant(val color: String, private val size: Int)
class GreenLeafyPlant(size: Int) : AquariumPlant("green", size)
fun AquariumPlant.print() = println("AquariumPlant")
fun GreenLeafyPlant.print() = println("GreenLeafyPlant")
val plant = GreenLeafyPlant(size = 10)
plant.print()
println("\n")
val aquariumPlant: AquariumPlant = plant
aquariumPlant.print()
GreenLeafyPlant
AquariumPlant
확장 당한 클래스를 receiver 라고 부른다.
아래코드와 같이 이 receiver 가 null 일 수 있음을 가정할 수도 있다.
fun AquariumPlant?.pull() {
this?.apply {
println("removing $this")
}
}
val plant: AquariumPlant? = null
plant.pull()
출력되는 것이 없다.
사용 방법은 아래 링크를 참고
https://nosorae.tistory.com/18
- 확장 프로퍼티 ( extension properties )
확장 함수와 같은 개념이다. 아래와 같이 코드를 작성해서 확장 프로퍼티를 가질 수 있다.
val AquariumPlant.isGreen: Boolean
get() = color == "green"
상속과 다양한 class 는 아래링크에서 확인할 수 있다.
https://nosorae.tistory.com/24
- 출처 : codelabs https://readystory.tistory.com/124
'Kotlin' 카테고리의 다른 글
[Kotlin] 상속/ interface/ abstract class/ object/ data class/ enum class (0) | 2021.08.05 |
---|---|
[Kotlin] 접근제어자 ( visibility modifiers ) (0) | 2021.08.04 |
[Kotlin] lambda 와 higher-order function (0) | 2021.08.03 |
[Kotlin] 함수 기초 (선언, 반환, default value, compact functions) (0) | 2021.08.03 |
[Kotlin] 리스트, 배열, 반복문에서 흥미로웠던 부분 (0) | 2021.08.03 |