[Kotlin] 새차원의 코틀린 강좌(8)

새차원의 코틀린 강좌 - Properties and Fields


프로퍼티 선언
  • 코틀린 클래스는 프로퍼티를 가질 수 있음
1
2
3
4
class Address {
var name: String = "Kotlin" // mutable
val city: String = "Seoul" // read-only
}
  • 프로퍼티 사용은 자바의 필드를 사용하듯이 하면 됨
1
2
3
4
5
fun copyAddress(address: Address): Address {    
val result = Address()
result.name = address.name + " copy"
return result
}



프로퍼티 문법
  • 전체 문법
1
2
3
var <propertyName>[: <PropertyType>] [=<property_initializer>]
[<getter>]
[<setter>]
  • 옵션 (생략 가능)
    • PropertyType : property_initiallizer에서 타입을 추론 가능한 경우 생략 가능
    • property_initializer
    • getter
    • setter
  • 객체지향 언어(JS, C#, 루비 등)는 언어레벨에서 프로퍼티를 지원한다. (내부적으로 은닉, 캡슐화)

  • var (mutable) 프로퍼티

1
2
3
4
5
6
7
8
9
class Address {    
// default getter와 setter
// 타입은 Int
var initialized = 1

// error
// default getter와 setter를 사용한 경우 명시적인 초기화 필요
var allByDefault: Int?
}


  • val (read-only) 프로퍼티
    • setter 가 없음
1
2
3
4
5
6
7
8
9
class Address {    
// default getter와 setter
// 타입은 Int
val initialized = 1

// error
// default getter. 명시적인 초기화 필요
val allByDefault: Int?
}



Custom accessors (getter, setter)
  • custom accessor는 프로퍼티 선언 내부에, 일반 함수처럼 선언할 수 있음

    • getter
    1
    2
    val isEmpty: Boolean
    get() = this.size == 0
    • setter (관습적으로 파라미터 이름은 value)
    1
    2
    3
    4
    5
    var stringRepresentation: String    
    get() = this.toString()
    set(value) {
    setDataFromString(value)
    }



타입생략
  • 코틀린 1.1 부터는 getter를 통해 타입을 추론할 수 있는 경우, 프로퍼티의 타입을 생략할 수 있음.
1
2
val isEmpty //: Boolean (생략 가능)    
get() = this.size == 0



프로퍼티
  • accessor에 가시성(접근지정자) 변경이 필요하거나, 어노테이션이 필요한 경우 기본 accessor의 수정 없이 body 없는 accessor를 통해 정의 가능
1
2
class Address {    
var setterVisibility: String = "abc" private set var setterWithAnnotation: Any? = null @Inject set // annotate the setter with Inject}
  • Body를 작성해도 됨

var setterVisibility: String = "abc" private set(value) { field = value }



Backing Fields (후원필드)
  • 코틀린 클래스는 field를 가질 수 없음 (예약어인가?)
  • ‘field’ 라는 식별자를 통해 접근할 수 있는 automatic backing field를 제공함
  • filde는 프로퍼티의 accessor에서만 사용 가능
1
2
3
4
5
// the initializer value is written directly to the backing field
var counter = 0
set(value) {
if (value >= 0) field = value
}
  • backing fields 생성 조건
    • accessor 중 1개라도 기본 구현을 사용하는 경우 (재정의하지 않는 경우)
    • custom accessor에서 field 식별자를 참조하는 경우
1
2
3
4
5
// the initializer value is written directly to the backing field
var counter = 0
set(value) {
if (value >= 0) field = value
}
    • 아래의 경우는 backing field 를 생성하지 않음
      • val : read-only 라서 setter가 없는데, getter 에서 field를 사용하지 않으므로.
    1
    2
    val isEmpty: Boolean    
    get() = this.size == 0


  • 추가로 테스트해본 것

    • isEmpty1 : backing field 사용 X
    • isEmpty2 : backing field 사용 O
  • 코틀린

    1
    2
    3
    4
    5
    val isEmpty1: Boolean    
    get() = this.size == 0

    val isEmpty2: Boolean = true
    get() = field
  • 디컴파일 후 (자바)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    public final class Address {   
    private final boolean isEmpty2 = true;

    public final boolean isEmpty1() {
    return this.size == 0;
    }
    public final boolean isEmpty2() {
    return this.isEmpty2;
    }
    }

    중간부분은 생략. 위와 같이, field 를 사용하지 않으면 자바 클래스의 멤버변수로 선언되지 않음.



Backing Properties
  • “implicit backing field” 방식이 맞지 않는 경우에는 “backing property”를 이용할 수도 있음
  • 이 부분 확실하게 이해 못함
1
2
3
4
5
6
7
8
private var table: Map<String, Int>? = null // backing property
val table: Map<String, Int>
get() {
if (table == null) {
_table = HashMap()
}
return _table ?: throw AssertionError("null ")
}



Compile-Time Constants
  • const modifier를 이용하면 컴파일 타임 상수를 만들 수 있음.
    • 이런 프로퍼티는 어노테이션에서도 사용 가능
  • 조건 : Top-level(패키지의 최상위), object의 멤버, String 이나 primitive type 으로 초기화된 경우
1
2
3
4
const val SUBSYSTEM_DEPRECATED: String = "This subsystem is deprecated"

@Deprecated(SUBSYSTEM_DEPRECATED)
fun foo() { }



Late-Initialized Properties
  • 일반적으로 프로퍼티는 non-null 타입으로 선언됨

  • 간혹 non-null 타입 프로퍼티를 사용하고 싶지만, 생성자에서 초기화를 해줄 수 없는 경우가 있음

    • Dependency injection
    • Butter knife
    • Unit test의 setup 메서드
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    public class MyTest {    
    lateinit var subject: TestSubject

    @SetUp fun setup() {
    subject = TestSubject()
    }

    @Test fun test() {
    subject.method() // dereference directly
    }
    }


  • 객체가 생성자에서는 할당되지 않지만, non-null 타입으로 사용하고 싶은 경우 lateinit modifier를 사용하면 된다.

  • 조건

    • 클래스의 바디에서 선언된 프로퍼티만 가능

    • 기본생성자에서 선언된 프로퍼티는 안됨 (파라미터에 키워드 선언 불가)

    • var 프로퍼티만 가능

    • custom accessor이 없어야 함

    • non-null 타입이어야 함

    • primitive type은 불가

    • lateinit 프로퍼티가 초기화되기 전 접근할 경우 오류 발생

      kotlin.UninitializedPropertyAccessException: lateinit property tet has not been initialized


출처 : 새차원의 코틀린 강좌 https://www.inflearn.com/course/%EC%BD%94%ED%8B%80%EB%A6%B0-%EA%B0%95%EC%A2%8C-%EC%83%88%EC%B0%A8%EC%9B%90/

Share