본문 바로가기

TIL

TIL) 필드vs프로퍼티, backing field, backing property

Properties expose fields. Fields should (almost always) be kept private to a class and accessed via get and set properties. Properties provide a level of abstraction allowing you to change the fields while not affecting the external way they are accessed by the things that use your class.

Property들이 field를 밖으로 노출한다.
Field는 클래스에서 내에 private 이어야 한다. get/set property들을 통해서만 접근이 가능해야 한다.
Property는 Field가 변경될 수 있는 추상 계층(a level of abstraction)을 제공해야 하는 반면, 그 클래스를 사용하는 다른 객체가 필드에 접근할 수 있는 외적인 방법으로서 작동해서는 안 된다.

https://stackoverflow.com/questions/295104/what-is-the-difference-between-a-field-and-a-property

 

 

1. 필드 vs 프로퍼티

 

즉 필드는 값 자체.

필드는 getter/setter 로 접근할 수 있음 (자바에서 했 듯.)

프로퍼티 = 필드 + getter/setter

 

코틀린에서는 기본적으로 val 또는 var 에서 접근자 (getter) 를 제공한다.

따라서 코틀린에서는 따로 필드가 없다. 모두가 프로퍼티이다.

 

반면 자바에서는 private String name; 과 같이 변수를 선언하면 getter 를 public 으로 열어주지 않는 한 외부에서 접근이 불가능하다. 

 

코틀린에서 프로퍼티를 필드처럼 만드려면 접근제어자를 private으로 주면 된다.

private val/var 은 컴파일시 getter/setter 를 만들지 않는다.

 

또 뭐

var name: String
    private set

이런 식으로 만들면 클래스 내부에서만 변경할 수 있게 만들 수 있다.

 

 

 

 

2.  backing field

코틀린은 프로퍼티를 만들면 자동으로 getter/setter 을 생성해준다.

 

때로는, 이 getter/setter 에 커스텀한 동작을 수행하고 싶을 수 있다.

이 때, 우리는 프로퍼티의 필드를 가져와서 특정 동작을 로직으로 작성할 수 있다.

 

class Human {
    val age = 20
        get() {
            println("Age is: $field")
            return field
        }
}

 

custom getter 를 만들었다.

이제 외부에서 Human 객체의 age 를 꺼내올 때 마다 로그가 찍히게 된다.

 

여기서 field 는 예약어 같은 것이다.

field 라고 쓰게 되면, get() 로직 안에서 age 값을 사용한다.

여기서 field 가 아니라, age 를 직접 쓴다면 자기참조로 순환참조로 stackoverflow 가 난다.

 

이 field 를 뒷받침 필드, backing field 라 한다.

 

위 코드를 컴파일하면 아래가 된다.

public final class Human {
   private final int age = 20;

   public final int getAge() {
      String var1 = "Age is: " + this.age;
      System.out.println(var1);
      return this.age;
   }
}

 

 

 

 

 

3. Backing property

backing field 로는 표현이 부족한 상황에서, 우리는 backing property 를 쓸 수 있다.

 

예를 들어, 아래와 같은 상황.

class Human {
    private val _age: Int = 20
    val age: Int
        get() {
             return _age
        }

    val printAge = {
        println("Age is: $_age")
    }
}

 

age 프로퍼티에 _age 를 backing field 로 구현할 수 있겠지만,

 

아리의 pringAge 에서도 같은 변수를 사용하고 싶다면, backing properties 를 따로 만들어 사용할 수 있다.

 

 

 

 

4. 커스텀 getter vs 직접 초기화

 

class Backing2 : A {
    override val case1: String = "case1"
    override val case2: String = "case2"
}

/*
@NotNull
private final String case1 = "case1";
@NotNull
private final String case2 = "case2";

@NotNull
public String getCase1() {
    return this.case1;
}

@NotNull
public String getCase2() {
    return this.case2;
}
*/

class Backing3 : A {
    override val case1: String
        get() = "case1"
    override val case2: String
        get() = "case2"

    private val kk = 0
}

/*
@NotNull
public String getCase1() {
    return "case1";
}

@NotNull
public String getCase2() {
    return "case2";
}*/

두 방식은 결과는 같으나 구현에 차이가 있다.

 

결론은

 

값을 직접 저장하는 것은 하나의 클래스 변수를 만들어서 재사용하지만 get() 을 사용하는 것은 메서드 안에서 지역변수를 매번 만든다.

 

이로 인해, 들어가는 값이 "case1" 처럼 단순하냐, 또는 복잡해서 매번 get() 에서 실행되는것이 버겁냐,

또는 얼마나 많이 호출되냐 등등에 따라 장단이 나뉠 듯 하다.

 

이와 관련해서 lazy 로딩 (사용될 때 한번만 로딩 후 캐시하고 사용) 이 제일 효율이 좋지 않을까 싶다.