class User(val id: Int, var name: String, var age: Int)
fun main( ) {
val user = User(1, "Sean", 30)
val name = user.name // 게터에 의한 값 획득
user.age = 41 // 세터에 의한 값 지정
println("name: $name, ${user.age}")
}
출력 : name: Sean, 41
📍 기본 선언구조
var 프로퍼티 이름[: 프로퍼티 자료형] [= 프로퍼티 초기화]
[get() {게터본문}]
[set(value) {세터본문}]
val 프로퍼티 이름[: 프로퍼티 자료형] [= 프로퍼티 초기화]
[get() {게터본문}]
// 직접 구성한 기본 게터/세터
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, "Jimuk", 20)
// user1.id = 2 // val 프로퍼티는 값 변경 불가
user1.age = 35 // 세터
println("user1.age = ${user1.age}") // 게터
}
출력 : user1.age = 35
value : 세터의 매개변수로 외부로부터 값을 가져옴
field : 프로퍼티를 참조하는 변수
📍value
는 다른 이름으로 변경 가능,filed
는 불가능 !!
커스텀 게터와 세터 : 사용자가 직접 게터와 세터를 정의하면서 새로운 내용을 작성하는 것
📍 입력 문자를 대문자로 바꾸는 등의 특정 연산을 수행할 경우 게터와 세터로 확장해 코드를 구성할 수 있어 편리하다.
📫 lateinit의 제한
var
로 선언된 프로퍼티만 가능하다.- 프로퍼티에 대한 게터와 세터를 사용할 수 없다.
class Person {
lateinit var name: String
fun test() {
if (!::name.isInitialized) {
println("not initialized")
} else {
println("inirialized")
}
}
}
fun main() {
val jimuk = Person()
jimuk.test()
jimuk.name = "Jimuk"
jimuk.test()
println("name = ${jimuk.name}")
}
출력 :
not initialized
inirialized
name = Jimuk
📫 lazy 특징
- 호출 시점에서
by lazy {...}
정의에 의해 블록 부분의 초기화를 진행한다.- 불변의 변수 선언인
val
에서만 사용 가능하다. (읽기 전용)
📍 읽기 전용의val
로 선언한 객체나 프로퍼티를 나중에 초기화할 때 사용 !!val
이므로 값을 다시 변경할 수 없다.
📝 프로퍼티 지연 초기화
class LazyTest {
init {
println("init block") // (2)
}
val subject by lazy {
println("lazy initialized") // (6)
"Kotlin Programming" // (7) lazy 반환값
}
fun flow() {
println("not initialized") // (4)
println("subject one: $subject") // (5) 최초 초기화 시점!
println("subject two: $subject") // (8) 이미 초기화된 값 사용
}
}
fun main() {
val test = LazyTest() // (1)
test.flow() // (3)
}
출력 :
init block
not initialized
lazy initialized
subject one: Kotlin Programming
subject two: Kotlin Programming
subject
는 val
로 선언되어 있어 값을 다시 설정할 수 없다.📫 lazy는 3가지 모드를 제공한다.
1. SYNCHRONIZED : lock을 사용해 단일 스레드만이 사용하는 것을 보장한다. (default)
2. PUBLICATION : 여러 군데에서 호출될 수 있으나 처음 초기화된 후 반환값을 사용한다.
3. NONE : lock을 사용하지 않기 때문에 빠르지만 다중 스레드가 접근할 수 있다. (값의 일관성을 보장할 수 없음)
by
를 통한 위임이 가능하다.by
키워드를 사용하면 된다.< val | var | class > 프로퍼티 or 클래스이름: 자료형 by 위임자
final
형태의 클래스이기 때문에 상속이나 직접 크래스의 기능 확장이 어렵다. 따라서 필요한 경우에만 윙ㅁ을 통해 상속과 비슷하게 해당 클래스의 모든 기능을 사용, 기능을 추가 확장 가능하게 구현한 것이다.interface Car {
fun go(): String
}
class VanImpl(val power: String) : Car {
override fun go(): String = "은 짐을 적재하며 $power 을 가집니다."
}
class SportImpl(val power: String) : Car {
override fun go(): String = "은 경주용에 사용되며 $power 을 가집니다."
}
class CarModel(val model: String, impl: Car) : Car by impl {
fun carInfo() {
println("$model ${go()}")
}
}
fun main() {
val myDamas = CarModel("Damas 2010", VanImpl("100마력"))
val my350z = CarModel("350Z 2008", SportImpl("350마력"))
myDamas.carInfo()
my350z.carInfo()
}
출력 :
Damas 2010 은 짐을 적재하며 100마력 을 가집니다.
350Z 2008 은 경주용에 사용되며 350마력 을 가집니다.
📫
lazy
의 동작 설명
1. lazy 람다식은 람다식을 전달받아 저장한 Lazy 인스턴스를 반환한다.
2. 최초 프로퍼티의 게터 실행은 lazy에 넘겨진 람다식을 실행하고 결과를 기록한다.
3.이후 프로퍼티의 게터 실행은 이미 초기화되어 기록된 값을 반환한다.
lazy
는 사실 람다식이다.by lazy
에 의한 지연 초기화는 스레드에 좀 더 안정적으로 프로퍼티를 사용할 수 있다.Delegates
를 import 해서 observable()
과 vetoavle()
을 사용할 수 있다.observable()
: 프로퍼티를 감시하고 있다가 특정 코드의 로직에서 변경이 일어날 때 호출되어 처리된다.
import kotlin.properties.Delegates
class User {
var name: String by Delegates.observable("NONAME") { prop, old, new ->
println("$old -> $new")
}
}
fun main() {
val user = User()
user.name = "Jimuk"
user.name = "Dooly"
}
출력 :
NONAME -> Jimuk
Jimuk -> Dooly
vetoable()
: observable()
과 비슷하지만 반환값에 따라 프로퍼티 변경을 허용하거나 취소할 수 있다.
import kotlin.properties.Delegates
fun main() {
var max: Int by Delegates.vetoable(0) { prop, old, new ->
new > old // 기존값 보다 커야 프로퍼티 교체
}
println(max)
max = 10
println(max)
max = 5 // 조건에 맞지 않아 거부
println(max)
}
출력 :
0
10
10
vetoable()
은 컬렉션과 같이 큰 데이터를 다룰 때 유용하다.✏️ 동적인 초기화 없이 사용할 수 있는 변수 : 정적변수
캠패니언 객체
class Person {
var id: Int = 0
var name: String = "Jimuk"
companion object {
var language: String = "Korean"
fun work() {
println("working ...")
}
}
}
fun main() {
println(Person.language)
Person.language = "English"
println(Person.language)
Person.work()
}
출력 :
Korean
English
working ...
Person
클래스의 language
는 객체의 생성 없이도 접근할 수 있게 되었다.📫 싱글톤이란?
전역 변수를 사용하지 않고 객체를 하나만 생성하도록 하며, 생성된 객체를 어디에서든지 참조할 수 있도록 하는 디자인 패턴의 하나
📍 객체가 서로 동일한 정보를 가질 때 하나의 메모리만 유지해 자원의 낭비를 줄일 수 있다.
📝 java
// 자바의 Customer 클래스
public class Customer {
public static final String LEVEL = "BASIC"; // static 필드
public static void login() { // static 메서드
System.out.println("Login...");
}
}
📝 kotlin
// 코틀린에서 자바의 static 접근
fun main() {
println(Customer.LEVEL)
Customer.login()
}
@JvmStatic
애노테이션 표기법을 사용해야 한다.📫 애노테이션이란?
@
기호로 시작- 사전적으로
주석
이라는 뜻이다.- 코드에서는 특수한 의미를 부여해 컴파일러가 목적에 맞추어 해석하도록 하거나 실행할 때 특정 기능을 수행하게 할 수도 있다.
📝 kotlin
class KCustomer {
companion object {
const val LEVEL = "INTERMEDIATE"
@JvmStatic fun login() = println("Login...") // 어노테이션 표기 사용
@JvmStatic val score = 3
@JvmField val JOB = KJob()
}
}
class KJob {
var title: String = "Programmer"
}
📝 java
public class KCustomerAccess {
public static void main(String[] args) {
// 코틀린 코드의 KotlinFoo의 멤버를 접근
System.out.println(KCustomer.LEVEL);
KCustomer.login(); // 어노테이션을 사용할 때 접근 방법
KCustomer.Companion.login(); // 위와 동일한 결과로 어노테이션을 사용하지 않을 때 접근 방법
// KJob에 대한 객체 생성 후 접근
KJob kjob = KCustomer.JOB;
System.out.println(kjob.getTitle());
// KCostomer를 통한 접근
KCustomer.JOB.setTitle("Accountant");
System.out.println(KCustomer.JOB.getTitle());
}
}
const
: 컴파일 시간의 상수, 컴파일 시간에 이미 할당되어 있다.최상위함수 (=패키지 레벨 함수) : 클래스 없이 만든 함수는 객체 생성 없이도 main() 함수 어디에서든 실행할 수 있다.
static
으로 선언되어 있다.fun packageLevelFunc() {
println("Package-Level Function")
}
fun main() {
packageLevelFunc()
}
// 1. object 키워드를 사용한 방식
object OCustomer {
var name = "Jimuk"
fun greeting() = println("Hello World!")
val HOBBY = Hobby("Drinking")
init {
println("Init!")
}
}
// 2. companion object를 사용한 방식
class CCustomer {
companion object {
const val HELLO = "hello" // 상수 표현
var name = "KIM"
@JvmField
val HOBBY = Hobby("Programming")
@JvmStatic
fun greeting() = println("Hello World!")
}
}
class Hobby(val name: String)
fun main() {
OCustomer.greeting()
OCustomer.name = "Baby"
println("name = ${OCustomer.name}")
println(OCustomer.HOBBY.name)
CCustomer.greeting()
println("name = ${CCustomer.name}, HELLO = ${CCustomer.HELLO}")
println(CCustomer.HOBBY.name)
}
출력 :
Init!
Hello World!
name = Baby
Drinking
Hello World!
name = KIM, HELLO = hello
Programming
open class Superman() {
fun work() = println("Taking photos")
fun talk() = println("Talking with people.")
open fun fly() = println("Flying in the air.")
}
fun main() {
val pretendedMan = object: Superman() { // object 표현식으로 fly()구현의 재설계
override fun fly() = println("I'm not a real superman. I can't fly!")
}
pretendedMan.work()
pretendedMan.talk()
pretendedMan.fly()
}
출력 :
Taking photos
Talking with people.
I'm not a real superman. I can't fly!