Home Kotlin - 클래스
Post
Cancel

Kotlin - 클래스

클래스

클래스와 프로퍼티

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라고 부른다.
This post is licensed under CC BY 4.0 by the author.