++ 코틀린의 프로퍼티(Properties) ++
- 변수 선언과 기본적인 접근 메서드를 모두 가지고 있음
- 내부적으로 생성 (따로 접근 메서드를 만들지 않아도 됨)
** 자바의 필드 : 단순한 변수 선언만 가지기 때문에 접근을 위한 메서드를 따로 만들어야 한다.
++ 게터(Getter)와 세터(Setter) ++
- 게터와 세터를 합쳐 접근 메서드(Access methods)라고 함
- 자바에서는 모든 필드에 대한 접근 메서드를 만들어야 함
value: 세터의 매개변수로 외부로부터 값을 가져옴
- 외부의 값을 받을 변수가 되므로 value 대신에 어떤 이름이든지 상관 없음
field: 프로퍼티를 참조하는 변수로 보조 필드(backing field)로 불림
- 프로퍼티를 대신할 임시 필드로 만일 프로퍼티를 직접 사용하면 게터나 세터가 무한 호출되는 재귀에 빠짐
var 프로퍼티이름[: 프로퍼티자료형] [= 프로퍼티 초기화]
[get() { 게터 본문 } ]
[set(value) {세터 본문}]
val 프로퍼티이름[: 프로퍼티자료형] [= 프로퍼티 초기화]
[get() { 게터 본문 } ]
** 불변형인 val은 Getter만 설정 가능하다.
package com.example.pp167
class Person {
// 멤버 필드
private String name
private int age
// 생성자
public Person(String name, int age) {
this.name = name;
this.age = age;
}
// 게터와 세터
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
...
}
자바의 Getter, Setter
package com.example.pp171
class User(_id: Int, _name: String, _age: Int) {
// 프로퍼티
val id: Int = _id
get() = field
var name: String = _name
get() = field
set(value) {
field = value
}
var age: Int = _age
get() = field
set(value) {
field = value
}
}
fun main() {
val user1 = User(1, "Kildong", 30)
// user1.id = 2 // 에러. val 프로퍼티는 값 변경 불가
user1.age = 35 // 세터 동작
println("user1.age = ${user1.age}") // 게터 동작
}
게터와 세터 예제
Getter와 Setter
- field는 꼭 이 단어로 사용해야 하는 의무필드이다.
package com.example.pp173
// 커스텀 게터와 세터의 사용
class User(_id: Int, _name: String, _age: Int) {
val id: Int = _id
var name: String = _name
set(value) {
println("the name was changed")
field = value.toUpperCase() // 받은 인자를 대문자로 변경해 프로퍼티에 할당
}
var age: Int = _age
}
fun main() {
val user1 = User(1, "kildong", 35)
user1.name = "coco" // 여기서 사용자 고유의 출력 코드가 실행된다.
println("user3.name = ${user1.name}")
}
커스텀 Getter와 Setter 사용하기
package com.example.pp174
import java.lang.AssertionError
class User(_id: Int, _name: String, _age: Int) {
val id: Int = _id
private var tempName: String? = null
var name: String = _name
get() {
if(tempName == null) tempName = "NONAME"
return tempName ?: throw AssertionError("Asserted by others")
}
var age: Int = _age
}
fun main() {
val user1 = User(1, "kildong", 35)
user1.name = ""
println("user3.name = ${user1.name}")
}
임시적인 보조 프로퍼티
package com.example.pp175
open class First {
open val x: Int = 0 // 오버라이딩 가능
get() {
println("First x")
return field
}
val y: Int = 0 // open 키워드가 없으면 final 프로퍼티
}
class Second: First() {
override val x: Int = 0 // 부모와 구현이 다름
get() {
println("Second x")
return field + 3
}
// override val y: Int = 0 // 에러. 오버라이딩 불가능
}
fun main() {
val second = Second()
println(second.x) // 오버라이딩된 두 번쨰 클래스 객체의 x
println(second.y) // 부모로부터 상속받은 값
}
프로퍼티의 오버라이딩 사용하기
++ 지연 초기화 ++
<변수나 객체의 값은 생성시 초기화 필요>
- 클래스에서는 기본적으로 선언하는 프로퍼티 자료형들은 null을 가질 수 없음
- 하지만, 객체의 정보가 나중에 나타나는 경우 나중에 초기화 할 수 있는 방법 필요
- 지연 초기화를 위해 lateinit과 lazy 키워드 사용
** 선언만 먼저 하고, 값대입을 나중에 한다고 생각하면 된다.
++ lateinit을 사용한 지연 초기화 ++
의존성이 있는 초기화나 unit 테스트를 위한 코드를 작성 시
예) Car클래스의 초기화 부분에 Engine 클래스와 의존성을 가지는 경우 Engine 객체가 생성되지 않으면 완전하게 초기화 할 수 없는 경우
예) 단위(Unit) 테스트를 위해 임시적으로 객체를 생성 시켜야 하는 경우
프로퍼티 지연 초기화
클래스를 선언할 때 프로퍼티 선언은 null을 허용하지 않는다.
하지만, 지연 초기화를 위한 lateinit 키워드를 사용하면 프로퍼티에 값이 바로 할당되지 않아도 됨
** var로 선언된 프로퍼티만 가능
** 프로퍼티에 대한 게터와 세터를 사용할 수 없음
객체 지연 초기화
- 객체 생성 시 lateinit을 통한 지연 초기화 가능
data class Person(var name:String, var age:Int)
lateinit var person1: Person // 객체 생성의 지연 초기화
fun main() {
person1 = Person1("Kildong",30) // 생성자 호출 시점에서 초기화됨
print(person1.name + " is " + person1.age.toString())
}
lazy를 사용한 지연 초기화
lazy를 통한 지연 초기화 특징 : _val만 가능
- 호출 시점에 by lazy {...} 정의에 의해 블록 부분의 초기화를 진행
- 불변의 변수 선언인 val에서만 사용 가능(읽기 전용)
- val이므로 값을 다시 변경할 수 없다.
package com.example.pp179
class Person {
lateinit var name: String // 늦은 초기화를 위한 선언
fun test() {
if(!::name.isInitialized) { //프로퍼티의 초기화 여부 판단
println("not initialized")
} else {
println("initialized")
}
}
}
fun main() {
val kildong = Person()
kildong.name = "Kildong" // 이 시점에서 초기화된다.(지연 초기화)
kildong.test()
println("name = ${kildong.name}")
}
lateinit을 이용해 늦은 초기화하기
:: 참고하다, 참조하다 라는 뜻.
package com.example.pp182
class LazyTest {
init {
println("init block")
}
val subject by lazy {
println("lazy initialized")
"Kotlin Programming" // lazy 반환값
}
fun flow() {
println("not initialized")
println("subject one: $subject") // 최초 초기화
println("subject two: $subject") // 이미 초기화된 값 사용
}
}
fun main() {
val test = LazyTest()
test.flow()
}
by lazy로 선언된 프로퍼티 지연 초기화 하기
++ 위임(delegation) ++
- 하나의 클래스가 다른 클래스에 위임하도록 선언
- 위임된 클래스가 가지는 멤버를 참조없이 호출
< val|var|class> 프로퍼티 혹은 클래스 이름: 자료형 by 위임자
코틀린의 기본 라이브러리는 open되지 않은 최종 클래스
- 표준 라이브러리의 무분별한 상속의 복잡한 문제들을 방지
- 단, 상속이나 직접 클래스의 기능 확장을 하기 어려움
- 위임을 통해 상속과 비슷하게 최종 클래스의 모든 기능을 사용하면서 동시에 기능을 추가 확장 구현 가능
package com.example.pp187
interface Car {
fun go(): String
}
class VanImpl(val power: String): Car {
override fun go() = "는 짐을 적재하며 $power 마력을 가집니다."
}
class SportImpl(val power:String): Car {
override fun go() = "는 경주용에 사용되며 $power 마력을 가집니다."
}
class CarModel(val model: String, impl: Car):Car by impl {
fun carInfo() {
println("$model ${go()}") // 참조 없이 각 인터페이스 구현 클래스의 go를 접근
}
}
fun main() {
val myDamas = CarModel("Damas 2010", VanImpl("100마력"))
val my350z = CarModel("350Z 2008", SportImpl("350마력"))
myDamas.carInfo() // carInfo에 대한 다형성을 나타냄
my350z.carInfo()
}
클래스 위임 사용하기
VanImpl은 구현 클래스
SportImpl은 역시 Car에서 구현한 구현 클래스
go()메소드를 재정의(오버라이드)
by lazy {...} 도 위임
- 사용된 프로퍼티는 람다식 함수에 위임되어 함수에 의해 사용
- lazy 람다식 함수는 람다를 전달받아 저장한 Lazy 인스턴스를 반환한다.
- 최초 프로퍼티의 게터 실행은 lazy에 넘겨진 람다식 함수를 실행하고 결과를 기록한다.
- 이후 프로퍼티의 게터 실행은 이미 초기화되어 기록된 값을 반환한다.
정적 변수와 Companion 객체
사용 범위에 따른 분류
- 지역(local)
- 전역(global)
보통 클래스는 동적으로 객체를 생성하는데 정적으로 고정하는 방법 :
- 동적인 초기화 없이 사용할 수 있는 개념으로 자바에서는 static 변수 또는 객체
- 코틀린에서는 이것을 컴페니언 객체(Companion object)로 사용
- 프로그램 실행 시 고정적으로 가지는 메모리로 객체 생성 없이 사용
- 단, 자주 사용되지 않는 변수나 객체를 만들면 메모리 낭비
package com.example.pp190
class Person {
var id: Int = 0
var name: String = "Youngdeok"
companion object {
var language: String = "Korean"
fun work() {
println("working...")
}
}
}
fun main() {
println(Person.language) // 인스턴스를 생성하지 않고 기본값 사용
Person.language = "English" // 기본 값 변경 가능
println(Person.language) // 변경된 내용 출력
Person.work() // 메서드 실행
// println(Person.name) // name은 companion object가 아니므로 에러
}
컴패니언 객체 사용해보기 예제
컴패니언 객체는 실제 객체의 싱글톤(singleton)으로 정의됨
1.
package com.example.pp191;
// 자바의 Customer 클래스
public class Customer {
public static final String LEVEL = "BASIC"; // static 필드
public static void login() { // static 메서드
System.out.println("Login...");
}
}
2.
package com.example.pp191
// 코틀린에서의 자바의 static 접근
fun main() {
println(Customer.LEVEL)
Customer.login()
}
코틀린에서의 자바의 static 멤버 사용 예제
'Kotlin' 카테고리의 다른 글
Kotlin #10 : 내부 클래스, 연산자 230223 (0) | 2023.02.23 |
---|---|
Kotlin #9 : 객체, 데이터 클래스, Companion 객체, object, singleton, 추상 클래스, 인터페이스 230222 (0) | 2023.02.23 |
Kotlin #7 : 정보 은닉 캡슐화 (연관, 의존, 집합, 구성) 230220 (0) | 2023.02.20 |
Kotlin #6 : 상속과 클래스 230217 (0) | 2023.02.20 |
Kotlin #5 : 객체지향 프로그래밍 230216 (0) | 2023.02.17 |
댓글