반응형
Notice
Recent Posts
Recent Comments
Link
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 | 30 | 31 |
Tags
- 해시
- 다리를 지나는 트럭
- 쿠버네티스
- @configuration
- 루씬 인 액션
- 롬복 어노테이션
- 코딩 테스트
- 가장 큰 수
- @Getter
- 정렬
- 커링
- Java
- K번째수
- 모던 자바 인 액션
- @EnableScheduling
- 완주하지 못한 선수
- 크론 표현식
- @Setter
- 검색 기능 확장
- H-index
- @Data
- 스택/큐
- 영속 자료구조
- 고차원 함수
- 전화번호 목록
- kubenetes
- 알고리즘
- 프로그래머스
- 스프링 스케쥴러
- 기능개발
Archives
- Today
- Total
Today I Learned
객체와 클래스 본문
728x90
객체와 싱글톤
객체 표현식으로 사용하는 익명 객체
- 코틀린의 객체 표현식은 Java의 익명 클래스를 생성하는 데 유용하게 쓰이지만, JavaScript의 객체, C#의 익명 타입과 비슷하다.
fun drawCircle() {
val circle = obejct { // an expression
val x = 10
val y = 20
val radius = 30
}
println("Circle x: ${circle.x} y: ${circle.y} radius: ${circle.radius}")
}
drawCircle()
- 객체 표현식은 지역변수들을 그룹핑 할때 유용하지만, 다음과 같은 한계를 가진다.
- 익명 객체의 내부 타입은 함수나 메소드의 리턴 타입이 될 수 없다.
- 익명 객체의 내부 타입은 함수나 메소드의 파라미터가 될 수 없다.
- 클래스 안에 저장된 속성들이 있다면, 해당 속성들은 Any로 간주된다. 그러면 모든 속성이나 메소드에 직접 접근할 수 없게 된다.
- 작은 변화만 주면 익명 객체는 인터페이스의 구현체가 된다.
fun createRunnable(): Runnable {
val runnable = object: Runnable {
override fun run() { println("You called...") }
}
return runnable
}
val aRunnable = createRunnable()
aRunnable.run() //You called...
- 함수에서 생성된 인스턴스를 리턴할 수도 있고, 익명 내부 객체가 구현하고 있는 인터페이스를 리턴 타입으로 사용할 수도 있다.
- 인스턴스를 인터페이스가 아니라 클래스로 확장한 경우라면 타입으로 사용될 수도 있다.
- 익명 내부 클래스에 의해 구현된 인터페이스가 싱글 추상 메소드 인터페이스라면 다음과 같이 메소드 일름을 명명하지 않고 바로 구현할 수 있다.
fun createRunnable(): Runnable = Runnable {println("You called...") }
- 익명 내구 클래스가 둘 이상의 인터페이스를 구현해야 한다면 리턴이 필요한 경우에는 반드시 리턴할 인스턴스 타입을 명시해줘야 한다.
fun createRunnable(): Runnable = object: Runnable, AutoCloseable {
override fun run() {println("You called...") }
override fun close() {println("closing...") }
}
객체 선언을 위한 싱글톤
- object 키워드와 {} 블록 사이에 이름을 넣으면, 명령문 또는 선언으로 인식한다.
- 익명 이너클래스의 인스턴스는 객체 표현식으로 만들고, 싱글톤은 객체선언을 사용한다.
object Util {
fun numberOfProcessors() = Runtime.getRuntime().availableProcessors()
}
println(Util.numberOfProcessors())
- Util은 이미 싱글톤 객체인 상태이며 클래스로 취급하지 않는다.
- 싱글톤은 메소드 뿐만 아니라 val과 var로 선언된 속성 모두 가질 수 있다.
- 싱글톤이 부모 클래스나 인터페이스를 가지고 있다면 싱글톤은 참조로 할당되거나 부모 타입의 파라미터로 전달될 수 있다.
object Sun : Runnable {
val radiusInKM = 696000
var coreTemperatureInC = 15000000
override fun run() { println("spin...") }
}
fun moveIt(runnable: Runnable) {
runnable.run()
}
println(Sun.radiusInKM) //696000
moveIt(Sun) //spin..
탑레벨 함수 vs 싱글톤
- 다음 예제는 탑레벨 함수와 싱글톤이 모두 포함되어 있는 패키지이다.
package com.agiledeveloper.util
fun unitsSupported() = listOf("Mertric", "Imperial")
fun precision(): Int = throw RuntimeException("Not implemented yet")
object Temperature {
fun c2f(c: Double) = c * 9.0/5 + 32
fun f2c(f: Double) = (f - 32) * 5.0/9
}
object Distance {
fun milesToKm(miles: Double) = miles * 1.609344
fun kmToMiles(km: Double) = km / 1.609344
}
- package 키워드를 이용해서 패키지를 정의하고 파일에 직접 탑레벨 함수들을 정의했다.
- 그리고 관련있는 메소드들을 그룹핑한 싱글톤을 만들었다.
- 이제 com.agiledeveloper.util 패키지의 메소드들을 사용해보자.
package com.agiledeveloper.use
import dom.agiledeveloper.util.*
import dom.agiledeveloper.util.Temperature.c2f
fun main(){
println(unitSupported())
println(Temperature.f2c(75.253))
println(c2f(24.305))
}
- 싱글톤의 c2f()는 이미 import 해두었기 때문에 검증하지 않고, f2c() 메소드를 호출할땐 Temperature로 메소드명을 검증해준다.
- 사용할 함수들이 하이레벨이거나 일반적이거나 넓게 사용될 예정이라면 패키지 안에 직접 넣어서 탑레벨 함수로 사용하는게 좋고,
반대로 함수들이 연관되어 있다면 싱글톤을 사용하는 게 좋다.
클래스 생성
- Java에서 클래스를 작성하려면 매우 많은 보일러플레이트 코드가 필요하지만, 코틀린은 코드 생성을 IDE에서 컴파일러로 옮겨버렸다.
읽기전용 속성
class Car(val yearOfMake: Int)
- 이 간결한 문법을 통해서 생성자가 정수형 파라미터를 받도록 만들었고, yearOfMake라는 이름으로 Int 타입의 읽기전용 속성을 만들었다.
- 코틀린 컴파일러는 생성자를 작성했고, 필드를 정의하고, 해당 필드에 접근하게 해주는 getter를 추가헀다.
- 위 코드는 아래의 내용을 축약해서 적은 것이다.
public class Car public constructor(public val yearOfMake: Int)
- 기본적으로 클래스의 멤버는 생성자의 멤버와 같고, constructor 키워드는 접근제어자나 주 생성자용 표기를 사용할 게 아니라면 필요없다.
인스턴스 생성하기
- 코틀린에서 객체를 만들때는 new 키워드를 사용하지 않고 함수를 사용하듯 그냥 클래스 이름을 이용한다.
val car = Car(2019)
println(car.yearOfMake) //2019
읽기-쓰기 속성
- 원한다면 var를 사용해서 변경가능한 속성으로 만들 수 있다.
class Car(val yearOfMake: Int, var color: String)
val car = Car(2019, "red")
car.color = "green"
println(car.color) //green
들여다보기 - 필드와 속성
- 앞선 예제에서 yearOfMake와 color는 Java의 관점에서 봤을때 속성이라기보다는 필드에 가깝다.
- 하지만 이것들은 모두 속성이며, 코틀린에서는 클래스에 필드가 없다.
- car.yearOfMake를 호출하면 실제로는 car.getYearOfMake()를 호출한 것이다.
속성 제어 변경
class Car(val yearOfMake: Int, theColor: String) {
var color = theColor
set(value) {
if(value.isBlank()) {
throw RuntimeException("empty")
}
field = value
}
}
- 코틀린에서 개발자는 필드를 만들 수 없으며, 커스텀 getter와 setter를 가진 필드를 정의하고 field 키워드를 사용한 백킹 필드를 사용하지 않는다면 백킹 필드가 생성되지 않는다.
- color 속성은 getter만 만들고 setter는 제공되는 코드를 사용하게 된다.
- 전달받은 값이 사용 가능하다면 값을 스페셜 키워드인 field에 의해서 참조되고 있는 필드에 할당한다.
접근 제어자
- 코틀린에는 public, private, protected, internal 네 개의 접근 제어자가 있다.
- proteced는 파생 클래스의 메소드가 속성에 접근할 수 있는 권한을 준다.
- internal은 같은 모듈(함께 컴파일된 소스코드)에 있는 모든 코드에서 속성이나 메소드에 접근이 가능하다.
- getter의 접근 권한은 속성의 접근 권한과 동일하지만, setter은 원하는 대로 접근 권한을 설정할 수 있다.
var fuelLevel = 100
private set //setter의 접근 제어
초기화 코드
- 코틀린은 생성자용 바디를 만들기위해 init 블록을 제공한다.
- 클래스는 0개 이상의 init 블록을 가질 수있으며, 이 블록들은 주 생성자의 실행의 한 부분으로써 실행된다.
class Car(val yearOfMake: Int, theColor: String) {
var fuelLevel = 100
private set
var color = theColor
set(value) {
if(value.isBlank()) {
throw RuntimeException("empty")
}
field = value
}
init {
if (yearOfMake <2020) { fuelLevel = 90}
}
}
- init 블록 대신 fuelLevel을 정의하는 시점에 처리하려면 다음과 같이 사용할 수 있다.
var fuelLevel = if (yearOfMake <2020) 90 else 100
private set
보조생성자
- 주생성자 외에 만들어진 생성자를 보조 생성자라고 하며, 보조 생성자는 주 생성자를 호출하거나, 다른 보조 생성자를 호출해야만 한다.
- 보조 생성자에서는 속성을 선언할 수 없으며, 주 생성자와 클래스 내부에서만 속성을 정의할 수 있다.
class Person(val first: String, val last: String) {
val fulltime = true
var location: String = "-"
constructor(first: String, last: String, fte: Boolean): this(first, last){
fulltime = fte
}
constructor(
first:String, last: String, loc: String): this(first, last, false) {
location = loc
}
override fun toString() = "$first $last $fulltile $location"
}
인스턴스 메소드 정의
- 클래스 안의 메소드를 정의할 때는 fun 키워드를 사용한다.
- 메소드는 기본적으로 public이고 private, protected, internal 키워드로 권한을 설정할 수 있다.
class Person(val first: String, val last: String) {
//...
internal fun fullName() = "$last, $first"
private fun yearOfService(): Int =
throw RuntimeException("Not implemented yet")
}
val jane = Person("Jane","Doe")
println(jane.fullName()) //Doe, Jane
//jave.yearsOfService() //ERROR: private 메소드에 접근할 수 없다.
인라인 클래스
- inline 클래스는 컴파일 시간에는 클래스의 장점을 취할 수 있고, 실행 시간에는 프리미티브 타입으로 취급되어 함수 호출로 인한 오버헤드가 발생하지 않는다.
inline class SSN(val id: String)
fun receiveSSN(ssn: SSN) {
println("Received $ssn")
}
컴패니언 객체와 클래스 멤버
- 컴패니언 객체는 클래스 안에 정의한 싱글톤이다.
- 컴패니언 객체는 인터페이스를 구현할 수도 있고 다른 클래스를 확장할 수도 있다.
클래스 레벨 멤버
class MachineOperator(val name: String) {
fun checkin() = checkedIn++
fun checkout() = checkedIn--
companion object {
var checkedIn = 0
fun minimumBreak() = "15 minutes every 2 hours"
}
}
- 클래스에서 컴패니언 객체는 companion Object 키워드를 이용해서 정의되었다.
- 컴패네이넌 객체 안에 있는 속성 checkedIn은 MachineOperator 클래스의 클래스 레벨 속성이 되었다.
- 클래스 이름을 참조하여 클래스의 컴패니언 객체의 멤버에 접근할 수 있다.
MachineOperator("Mater").checkin()
println(MachineOperator.minimumBreak()) // 15 minutes every 2 hours
println(MachineOperator.checkedIn) // 1
- 단, 멀티 스레드인 경우 컴패니언 객체에 뮤터블 속성을 사용하면 스레드 안정성 문제가 발생할 수 있으니 주의해야 한다.
컴패니언에 접근하기
- 컴패니언 객체의 속성이 아닌 컴패니언 객체 자체의 참조가 필요한 경우 , 클래스에 .Companion을 붙여서 접근할 수 있다.
var ref = MachineOperator.Companion
- 클래스 컴패니언을 빈번하게 사용해야 한다면 직접 이름을 지정해줄 수도 있다.
//companion object {
companion object MahineOperatorFactory {
var checkedIn = 0
// ...
}}
val ref MachineOperator.MahineOperatorFactory
팩토리로 사용하는 컴패니언
- 컴패니언을 팩토리로 사용하기 위해서는 클래스에 private 생성자를 만들고, 컴패니언 객체에서 생성된 인스턴스를 리턴하기 전에 인스턴스를 처리하는 메소드를 하나 이상 생성해야 한다.
class MachineOperator private constructor(val name: String) {
//...
companion object {
//...
fun create(name: String): MachineOperaotr {
var instance = MachineOperaotr(name)
instance.checkin()
return instance
}
}
}
- MachineOperaotr 클래스의 생성자가 private가 되었고, 클래스 외부에서는 어떤 인스턴스도 직접 생성할 수 없다.
- create() 메소드가 인스턴스를 생성하는 유일한 방법이기 때문에 컴패니언 객체가 클래스의 팩토리로서 동작한다.
static과는 다르다
- 동작하는 방식을 보면 컴패니언 객체의 멤버가 static 멤버가 되는 것처럼 보이지만 그렇지 않다.
제네릭 클래스의 생성
- 제네릭 클래스는 타입 안정성을 지키면서 일반화를 할 때 사용된다.
- 코틀린의 제네릭 기능은 여러 부분에서 Java와 유사하지만, 가변성과 제약들은 다르게 선언되었다.
class PriorityPair<T: Comparable<T>>(member1: T, member2: T) {
val first: T
val second: T
init {
if (member1 >= member2) {
first = member1
second = member2
} else {
first = member2
second = member1
}
}
override fun toString() = "${first}, ${second}"
}
println(PriorityPair(2,1)) //2,1
println(PriorityPair("A","B")) //B,A
- PriorityPair<T>의 인스턴스는 Comparable<T>의 인터페이스를 구현하고 있는 타입이라면 어떤 타입이든 이용해서 만들 수 있다.
데이터 클래스
- data class는 특정한 행동, 동작보다는 데이터를 옮기는 데 특화된 클래스이다.
- 데이터 클래스에선 val이나 var를 사용한 속성 정의가 하나 이상 필요하며, val이나 var가 아닌 파라미터는 사용할 수 없다.
- 데이터 클래스에는 자동으로 equals(), hasCode(), toString() 메서드가 만들어지며, copy() 메서드도 제공된다.
- 주 생성자에 의해서 정의된 각각의 속성에 접근할 수 있게 해주는 component로 시작하는 이름의 메서드도 제공된다.
data class Task(val id: Int, val name: String,
val completed: Boolean, val assinged: Boolean)
val task1 = Task(1, "Create Project", false, true)
println(task1) // Task(id=1,name=Create Project, completed=false, assinged=true)
println("Name: #{task1.name}") // Name: Create Project
val task1Completed = Task(completed=true, assigned=false)
println(task1Completed) // Task(id=1,name=Create Project, completed=true, assinged=false)
- copy() 함수는 프리미티브 참조에 대한 쉘로우 카피만 가능하며, 객체 내부의 참조들은 딥카피 되지 않는다.
- componentN() 메소드의 주된 목적은 구조분해이다.
- 구조분해를 사용해서 주 생성자가 만든 속성 순서와 동일한 속성 순서로 속성을 추출할 수 있다.
val (id, _, _, isAssigned) = task1
println("Id: $id Assinged: $isAssigned") //Id:1 Assinged: true
- 만약 뒤에 더 많은 속성이 있었더라도 사용하지 않는다면 언더 스코어를 쓰지 않더라도 무시된다.
- 코틀린의 구조 분해는 이름이 아닌 주 생성자에 전달되는 프로퍼티 순서에 기반하기 때문에, 개발자가 현재 파라미터 사이에 새로운 파라미터를 넣게 된다면 구조 분해에 심각한 영향을 끼친다.
728x90
'Kotlin > 다재다능 코틀린 프로그래밍' 카테고리의 다른 글
델리게이션을 통한 확장 (1) (0) | 2023.09.01 |
---|---|
클래스 계층과 상속 (0) | 2023.08.24 |
오류를 예방하는 타입 안정성 (2) (0) | 2023.08.01 |
오류를 예방하는 타입 안정성(1) (0) | 2023.07.27 |
콜렉션 사용하기 (0) | 2023.07.27 |
Comments