클래스
클래스와 프로퍼티
1
2
3
4
5
6
7
8
9
10
11
public class Person {
private final String name;
private int age;
Person(String name, int age) {
this.name = name;
this.age = age;
}
//getter, setter..
}
위와 같은 Person 클래스를 코틀린으로 만들어보자.
1
2
3
4
5
6
7
8
9
10
11
class Person constructor(name: String, age: Int) {
val name = name
var age = age
}
class Person(
val name: String,
var age: Int
) {
}
- 생성자는 클래스 옆 constructor 키워드를 사용해 선언해줄 수 있다.
- constructor 키워드는 생략이 가능하다.
- 프로퍼티 = 필드 + getter + settter
- 코틀린에서는 필드만 만들면 getter, setter를 자동으로 만들어준다.
- 클래스의 필드 선언(프로퍼티 = name, age)과 생성자를 동시에 선언할 수 있다.
- 바디에 아무 내용이 없으면 중괄호도 생략이 가능하다.
1
2
3
4
5
6
7
8
9
10
class Person(
val name: String,
var age: Int
)
fun main() {
val person = Person("Lee", 100)
println(person.name) //getter
person.age = 10 //setter
}
- .필드로 getter와 setter를 사용가능하다.
- 이는 자바로 만들어진 클래스를 사용해도 이렇게 사용 가능하다.
생성자와 init
1
2
3
4
5
6
7
8
9
10
11
12
13
public class Person {
private final String name;
private int age;
public Person(String name, int age) {
if (age <= 0) {
throw new IllegalArgumentException();
}
this.name = name;
this.age = age;
}
}
자바코드에서는 위와 같이 생성자에서 나이를 검증하고 생성할 수 있었다.
그런데 코틀린에서는 어떻게 검증해야 할까? 클래스 옆 괄호가 생성자라고 하는데 우리가 사용하던 구조와 다르기 때문에 감이 오지 않는다.
1
2
3
4
5
6
7
8
9
10
class Person(
val name: String,
var age: Int
) {
init {
if (age <= 0) {
throw IllegalArgumentException()
}
}
}
- init 블록을 사용한다.
- 값을 적절히 만들어주거나 검증 로직을 넣는 용도로 사용된다.
그렇다면 기본 생성자가 아닌 다른 생성자를 추가하고 싶은 경우는 어떻게 할까?
1
2
3
4
5
6
7
8
9
10
class Person(
val name: String,
var age: Int
) {
constructor(name: String): this(name, 1)
constructor() : this("홍길동") {
println("부생성자 2")
}
}
- 위에 있는 생성자는 그대로 두고 바디에 constructor 키워드를 이용해 만들 수 있다.
- this로 위에 있는 생성자를 호출한다.
- 주생성자
- 클래스 옆 괄호 생성자
- 반드시 존재해야 한다.
- 부생성자
- 바디의 constructor 키워드를 사용한 생성자.
- 최종적으로 주생성자를 this로 호출해야 한다.
- constructor(): this(“홍길동”)
- 위의 경우 첫번째 부생성자를 호출하고 그 생성자가 다시 주생성자를 호출하므로 가능한 경우이다.
- body를 가질 수 있다.
- 본문은 역순으로 실행된다.
- Person() 으로 부생성자2를 호출했더라도 init 블록, 첫 부생성자, 부생성자2 순서로 출력된다.
부생성자보다는 default parameter를 쓰자.
코틀린에서는 부생성자보다 default parameter를 권장한다.
1
2
3
4
5
6
7
8
9
10
class Person(
val name: String = "Lee",
var age: Int = 1,
) {
init {
if (age < 0) {
throw new IllegalArgumentException("나이는 ${age}일 수 없습니다.")
}
}
}
자바처럼 여러 생성자를 만드는 것 보다 파라미터 값을 넣지 않으면 기본 값을 쓰도록 하는게 더 깔끔하다.
더해 어떤 다른 객체를 Person으로 바꾸는 Converting 같은 경우에도 부생성자보단 정적 팩토리 메소드를 사용하는 것이 권장된다.
커스텀 getter
1
2
3
fun isAdult(): Boolean {
return this.age >= 20
}
위는 isAdult()라는 메서드를 호출했을 때 나이가 20 이상인지 검증하는 메서드이다.
코틀린에서는 이러한 부분을 함수 대신 프로퍼티로도 만들 수 있다.
그것을 커스텀 getter라고 한다.
1
2
3
4
5
6
7
val isAdult: Boolean
get() = this.age >= 20
val isAdult: Boolean
get() {
return this.age >= 20
}
각각의 방법 모두 사용해도 된다.
상황에 따라 적당한 방법을 사용하도록 하자.
backing field
- name을 get할 때 무조건 대문자로 바꾸는 경우
- 기본 getter대신 커스텀 getter를 사용하기 위해 주생성자의 name에는 val을 붙이지 않았다.
- 주생성자에서 받은 name을 불변 프로퍼티 name에 바로 대입한다.
- 커스텀 getter에서 field를 사용한다.
- 밖에서 Person.name과 같이 getter를 호출할텐데 커스텀 getter에서 만약 name.uppercase()와 같이 사용한다면 이 또한 getter이기 때문에 계속해서 getter를 호출하는 무한루프가 발생하게 된다.
- 따라서 field 예약어를 사용한다.
하지만 이 또한 잘 사용되지 않을 수 있다. 같은 기능을 하는 여러 방법의 아래 예시를 보자.
1
2
3
4
5
6
7
8
9
10
11
12
class Person(
val name: String,
var age: Int,
) {
fun getUppercaseName(): String {
return this.name.uppercase()
}
val uppercaseName: String
get() = this.name.uppercase()
}
상황에 맞게, 코드 스타일에 맞게 사용하면 된다.
커스텀 setter
1
2
3
4
5
6
7
8
9
10
class Person(
name: String = "Lee",
var age: Int = 1
) {
var name = name
set(value) {
field = value
}
//...
}
커스텀 getter와 같다.
그러나 보통 setter 자체를 지양하기 때문에 커스텀 setter도 잘 사용하지 않고 메서드를 사용하는 방식을 자주 사용하게 된다.
정리
- 코틀린에서는 필드를 만들면 getter와 setter가 자동으로 생성된다.
- 때문에 name, age와 같은 것을 프로퍼티라고 부른다.
- 코틀린에서는 주생성자가 필수이다.
- constructor 키워드를 이용해 부생성자를 추가로 만들 수 있다.
- 하지만 default parameter나 정적 팩토리 메서드가 권장된다.
- 실제 메모리에 존재하는 것과 무관하게 커스텀 getter와 setter를 만들 수 있다.
- getter, setter를 함수로 만들 수 있었지만 프로퍼티 처럼 만들 수 있었다.
- 커스텀 getter와 setter에서 무한루프를 방지하기 위해 field라는 키워드를 사용한다.
- 이를 backing field라고 부른다.