클래스와 프로퍼티
자바의 클래스는 다음과 같다.
public class JavaPerson {
private final String name;
private int age;
public JavaPerson(String name, int age) {
if (age <= 0) {
throw new IllegalArgumentException(String.format("나이는 %s일 수 없습니다", age));
}
this.name = name;
this.age = age;
}
public JavaPerson(String name) {
this(name, 1);
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public boolean isAdult() {
return this.age >= 20;
}
}
이를 코틀린으로 변환해보자.
드디어 코틀린 파일 말고 클래스를 만들어본다.!!
1. name, age를 프로퍼티로 갖고 있다.
2. name은 final로 상수로 표현된다.
3. name, age를 생성자로 받는다.
4. name만 받고 default 값으로 age를 1로 세팅하는 생성자가 오버로드 돼있다.
5. name은 setter가 없다.
6. getter가 구현되어 있다.
7. 성인임을 나타내는 메서드가 하나 있다.
class Person constructor(name: String, age: Int){
val name: String = name
var age : Int = age
}
코틀린은 생성자를 상단에 표현한다.! ( contructor는 생략이 가능하다.)
class Person(name: String, age: Int){
val name = name
var age = age
}
클래스의 기본은 public이며 생략이 가능하다.
생성자에서 타입을 명시했기 때문에 다음과 같이 타입을 생략할 수 있다.
class Person constructor(name: String, age: Int){
val name = name
var age = age
}
더욱이 신기한 것은 코틀린은 필드만 만들면 getter와 setter를 자동으로 만들어 준다.
아니 근데 setter를 사용하기 싫다면 어떻게 해야 하지?
그런데 생성자에서 필드 선언을 할 수 있다고 한다.
class Person(
val name: String,
var age: Int
)
해당 코드가 위의 프로퍼티를 나타낸다.
fun main() {
val person = Person("oncerun", 20)
println(person.name)
person.age = 29
}
코틀린에서 getter와 setter는 Object.field를 통해 접근 및 수정이 가능하다.
만약 자바 코드를 가져와도 코틀린에서는 동일하게 접근하여 사용한다.
생성자와 init
public JavaPerson(String name, int age) {
if (age <= 0) {
throw new IllegalArgumentException(String.format("나이는 %s일 수 없습니다", age));
}
this.name = name;
this.age = age;
}
자바는 다음과 같이 init 코드를 생성 자안에서 검증 로직을 작성한다.
코틀린에서는 init 블록을 지원한다.
class Person(
val name: String,
var age: Int
){
//초기화 시점에 한번 호출되는 블럭
init {
if (age <= 0) {
throw IllegalArgumentException("나이는 ${age}일 수 없습니다.")
}
}
}
생성자를 오버 로드해보자.
constructor(name: String): this(name, 1)
위에 있는 생성자는 그대로 두고 아래의 constructor을 사용해서 정의하고 this는 생성자를 가리켜서 위의 있는 생성자를 호출하도록 하자.
첫 번째 생성자를 주 생성자 (primary constructor)라고 한다. 이는 반드시 있어야 한다. 다만 프로퍼티가 하나도 없다면 생략이 가능하다.
아래에 생성된 생성자는 부 생성자 (secondary constructor)라고 한다. 이는 최종적으로 주생 성자를 this로 호출해야 한다.
class Person(
val name: String,
var age: Int
){
//초기화 시점에 한번 호출되는 블럭
init {
if (age <= 0) {
throw IllegalArgumentException("나이는 ${age}일 수 없습니다.")
}
}
constructor(name: String): this(name, 1)
constructor(): this("hi")
}
첫 번째, 두 번째 생성자는 바디 {}를 가질 수 있다.
사실 코틀린에서는 부 생성자보다는 기본 파라미터를 권장한다.
class Person(
val name: String = "onceRun",
var age: Int = 1
)
그리고 부생 성자보다는 정적 팩토리 메서드를 이용하자.
커스텀 gettter, setter
이제 성인인지 확인하는 메서드를 만들어보자.
fun isAdult(): Boolean {
return this.age >= 20
}
프로퍼티처럼 보이게 할 수 있다.
val isAdult: Boolean
get() = this.age >= 20
get()은 하나의 Expression이기 때문에 블록을 생략했지만 별도의 작업이 필요하다면 return과 블럭을 사용해서 표현해도 된다.
동일한 방법이 많으면 가독성을 기준으로 선택할 수 있다.
속성을 표현하면 속성으로 표현하고 그게 아니고 동작을 표현하면 함수를 쓰라는 방법도 있는데
과연 통일성 없는 사용법이 가독성이 좋을지는 아직 잘 판단이 서지 않는다.
backing field
class Person(
name: String = "onceRun",
var age: Int = 1
){
val name = name
get() = field.uppercase()
다음과 같이 자기 자신을 변경하는 get도 가능합니다.
그런데 field라는 특별한 키워드가 있다.
만약 이를 name.uppercase()로 사용하게 되면 무한루프가 발생한다. 따라서 자기 자신을 가리키는 field로 사용한다.
이러한 필드를 backing field라고 한다.
다만 backing field보다는 this.property로 많이 사용하는 것 같다.
setter는 var만 가능하다.
var name = name
get() = field.uppercase()
set(value) {
field = value.uppercase()
}
value는 들어오는 파라미터, field는 자기자신을 나타낸다.
사실 setter를 지양하기 때문에 custom setter도 잘 안 쓰긴 한다.
댓글