해결된 질문
작성
·
449
·
수정됨
2
안녕하세요. 강의 정말 잘 듣고 있습니다!
isAdult() 함수를 custom getter를 활용한 프로퍼티 변환에 대한 부분인데요..
fun isAdult(): Boolean {
return age >= 20
}
fun isAdult2() = age >= 20
val isAdult5
get() = this.age >= 20
var isAdult6
get() = this.age >= 20
set(value) {}
var isAdult7 = false
get() = this.age >= 20
isAdult() 함수를 변환하는 여러가지 방법들을 시도해 보았는데요. 다 동일한 결과를 얻더라구요.
isAdult2() 는 return 하는게 단일값 이면 block 을 없애고 반환 타입도 생략한 방법이고
isAdult5는 수업시간에 보여주신 custom getter를 활용한 방법입니다.
Q1. isAdult를 val 이 아닌 var로 바꿔봤고 아래 코드처럼 써보니 빨간줄이 뜨더라구요.
var isAdult8
get() = this.age >= 20
val은 getter만 있어야 되지만, var은 setter도 있어야 하는건가 싶어서 isAdult6처럼 custom setter도 명시를 하니까 빨간줄이 사라지더라구요.
그래서.. 'var로 프로퍼티를 선언했고, custom getter를 명시했다면, custom setter를 반드시 명시해줘야 하는건가?' 라고 이해하려 했는데, isAdult7의 경우 따로 custom setter를 명시해주지 않아도 빨간줄이 뜨지 않더라구요.
어떤 방식으로 이해해야 할지 감이 안오네요.
요약: isAdult8는 왜 안되고 isAdult6과 isAdult7은 왜 되는건가요?
Q2.
val person2 = Person("KIM", 10)
println(person2.isAdult5) // false
person2.age = 20
println(person2.isAdult5) // true
제가 지금까지 이해하기로는 val은 자바에서 final 같은거라 한번 값이 초기화되면 값을 변경 못해야 될것 같은데,, isAdult5 처럼 val로 선언했을때 isAdult5가 false를 한번 가리키게 되면 계속 false만 가리키고 있어야할것 같은데, Person의 age 값을 10에서 20으로 변경하면 val로 선언한 isAdult5이 true로 변경이 되네요. 물론 성인 여부를 확인해야 되는 기능상 age가 바뀌면 그에 따라 결과도 바뀌는것이 당연히 자연스러운것이긴 한데, 문법적으로 val 프로퍼티의 값이 어떻게 변경될 수 있는지 의아합니다.
요약: val 프로퍼티 인데도 왜 값이 변경될 수 있나요? final 개념으로 이해하면 안되는 건가요
감사합니다.
답변 1
1
안녕하세요, Shin Berg님!! 크으~~~ 정말 정말 날카로운 질문이십니다! 👍👍
하나씩 답변 드려보도록 하겠습니다!!
[1. isAdult8는 왜 안되고 isAdult6과 isAdult7은 왜 되는건가요?]
파악해주신 내용이 맞습니다!
var
로 선언된 프로퍼티는 "setter"라는 존재가 있어야 합니다.
isAdult8 / isAdult6 / isAdult7을 하나씩 살펴보겠습니다!
var isAdult8
get() = this.age >= 20
우리 isAdult8
친구를 살펴보겠습니다~~ 우선 왜 빨간줄의 에러가 나는지 확인해봐야 하는데요! 빨간 줄을 읽어보시면, "Property must be initialized" 라는 에러를 확인하실 수 있을 겁니다! 그렇습니다! 이 친구의 문제는 setter가 없는 것이 문제가 아니라 "초기값"이 없는 것이 문제입니다!!
사실 Kotlin의 경우 프로퍼티의 존재만으로 getter와 setter가 자동 생성됩니다. 예를 들어, var age = 4
라는 프로퍼티 하나 만으로 getAge
와 setAge
가 생기게 되죠. isAdult8
도 마찬가지입니다. 다만, getter와 setter가 자동으로 생겼으나, 우리가 만들어 준 custom getter (get() = this.age >= 20
) 에 의해 override 된 느낌입니다.
즉, 이 프로퍼티는 "초기화"되지 않았기 때문에 에러가 발생했습니다. 많이 어색하긴 하지만, 생성자에서 매개변수를 받아 isAdult8을 초기화 해준다면, 에러는 사라질 수 있습니다.
class Person(val age: Int, isAdult8: Boolean) {
private var isAdult8: Boolean = isAdult8
get() = this.age >= 20
}
이 느낌으로 아래 두 코드도 파악해보겠습니다!
var isAdult6
get() = this.age >= 20
set(value) {}
isAdult6
친구는 getter (custom getter) 와 setter (custom setter)가 모두 있습니다. 또한 기본값 역시 존재합니다! 바로 this.age >= 20
으로 존재하죠. 그래서 잘 통과가 되고요!
var isAdult7 = false
get() = this.age >= 20
이 친구 역시 getter와 setter를 모두 갖고 있습니다! getter는 custom getter를 갖고 있고, setter는 기본 setter를 갖고 있군요. 초기값도 false로 잘 들어 있고요. 물론 어차피 isAdult7의 getter를 호출하면 this.age >= 20
을 따라 값이 반환되긴 할겁니다. 따라서 문제가 없습니다.
[2. val 프로퍼티 인데도 왜 값이 변경될 수 있나요? final 개념으로 이해하면 안되는 건가요]
다음 질문으로 넘어가 보겠습니다! val 프로퍼티 인데도 어떻게 값이 변경될 수 있는가!!
이는 custom getter를 활용한 val 프로퍼티가 사실은 함수이기 때문에 가능합니다.
Java로 생각해보자면 이런 느낌입니다.
val hello = "World"
hello
필드가 있음
public String getHello()
함수가 있음
val hello: String
get() = "World"
public String getHello()
함수가 있음
즉, custom getter를 사용한 val 프로퍼티는 Java의 함수와 동일하며 따라서 함수처럼 내부 로직에 따라 값이 계속해서 변경될 수 있게 됩니다. 단지, 우리가 해당 프로퍼티에 접근해서 직접 값을 변경하지 못할 뿐이지요!
때문에 보통은,
아예 필드 + 자동 생성 get/set
(setter의 경우 내부적으로만..) 활용하거나
custom getter
를 활용하거나
둘 중 하나만 구분해서 활용하는 편입니다.
살짝 TMI이긴 하지만, 스프링 쪽 질문 기록도 있으셔 더 말씀 드려보자면... 이러한 특성 때문에 Hibernate 역시 이 둘을 구분하게 됩니다.
val isAdult1 = name.age >= 20
val isAdult2: Boolean
get() = this.age >= 20
isAdult1
같은 경우는 name.age >= 20
을 기본값으로 하는 "필드"가 되기 때문에, Table 매핑에 있어 자동으로 isAdult1
을 만들거나 매핑하려고 하고요!
isAdult2
는 단순히 "함수"이기 때문에 그러한 매핑에서 제외가 됩니다.
때문에 자칫 잘못하면 의도치 않은 필드가 auto ddl에 생기거나 하는 경우도 있더라고요 ㅎㅎㅎ.. (물론 @Access
어노테이션 설정에 따라 잘라지겠지만 일반적으로 default option인 FIELD를 기준으로 설명 드려 보았습니다)
제 답변이 도움이 되었으면 좋겠습니다.
또 궁금한 점 있으시다면 편하게 질문 남겨주세요~ 감사합니다! 🙏
안녕하세요!! 어떤 부분에서 isAdult8
과 isAdult6
가 어렵게 느껴지시는지 이해했습니다!
제가 isAdult8
에서 설명드린 "초기값"을 이용해 설명을 어느 정도 이어가려다 보니 헷갈리게 설명한 감이 있는 것 같습니다! 😢 그래서 아예 새롭게 설명드려보겠습니다!
class Person {
var age // Property must be initialized or be abstract
}
위의 코드는 에러가 발생하는 에러입니다! Person
클래스에 age
프로퍼티를 선언해 두었으나 초기값이 없기 때문에 에러가 발생하죠.
isAdult8
역시 비슷하게 생각해볼 수 있습니다.
class Person(val age) {
var isAdult8
get() = this.age >= 20
}
isAdult8
역시 클래스 안에 선언된 프로퍼티입니다. 단지 getter에 대한 custom getter가 존재할 뿐 여전히 초기값이 없다고 할 수 있죠! 따라서 이 코드는 초기값이 없어 에러가 발생하게 됩니다.
class Person(val age) {
var isAdult6
get() = this.age >= 20
set(value) { }
}
반면, isAdult6
는 프로퍼티처럼 보이지만 함수입니다! custom getter와 custom setter만 있기에 단지 함수 2개가 있다고 할 수 있죠.
Java로 번역해보자면
public boolean isAdult6() { // getter
return this.age >= 20; // getter를 했을 때의 기본값
}
public void setAdult6(boolean value) { // setter
}
라고 할 수 있습니다. 즉, 애당초 "필드" 자체가 필요없죠. 때문에 isAdult6
는 필드에 대한 초기화가 필요하다는 에러가 발생하지 않습니다.
이렇게 접근하시면 조금 더 이해에 도움이 되시지 않을까 싶네요..!!! 꼼꼼하게 봐주셔서 정말 감사합니다! 😊😊
아아!! 제가 선언한 custom setter가 필드가 필요없는 setter였기 때문에 초기화가 필요하다는 에러가 나타나지 않는것이군요!! 그리고 자동으로 만들어지는 기본 setter는 set(value) {field = value} 이기에 필드가 필요했던거구요!! 이제 완전히 이해 했습니다. 감사합니다 ㅎㅎ
와 정말 상세한 답변 감사합니다.
Q2는 확실히 이해했습니다. 함수라 생각하니 충분히 납득이 가네요.
다만 Q1에 대한 추가적인 질문을 드리자면,,
프로퍼티를 선언하면 기본 getter와 setter가 자동으로 생성이 된다는것, 따로 커스텀 getter나 커스텀 setter를 명시할경우 기본 getter setter을 오버라이딩 하는 느낌이라는것도 잘 이해했습니다. 그리고 '초기화되지 않는것'이 빨간줄의 원인이라는것도 이해했습니다.
그런데, isAdult8에서는 초기화가 안된 반면, isAdult6에서는 초기값이
this.age >= 20
으로 존재하게 되는게 의아합니다. isAdult8과 isAdult6의 차이는 custom setter를 따로 만들어준것 뿐인데, custom setter의 유무가 어떻게 초기화 여부에 영향을 미치는것인지 이해가 잘 안가네요. isAdult7의 경우에는 필드값을= false
라고 대놓고 주입해주고 있으니 어느정도 이해가 갑니다. val로 선언된 isAdult5의 경우에는 setter가 애초에 올 수 없으니 초기화가 된것도 어느정도 이해가 갑니다.감사합니다!