이번 포스팅은 클래스와 객체에 대해 다룹니다.
클래스와 객체의 자세한 개념은 자바 포스팅(여기를 눌러 이동)을 참고해주세요.
자바에서는 클래스를 이렇게 작성합니다.
class Person {
// 멤버변수
private String name;
private String gender;
private int age;
// 생성자
public person() {}
public person(String name, String gender, int age) {
this.name = name;
this.gender = gender;
this.age = age;
}
// getter와 setter...생략
// Override된 toString()...생략
// 필요에 따라 멤버 메서드도 작성 가능
public static String sayHello() {
return "Hello! " + name
}
}
우선 멤버변수, 생성자가 눈에 띄고 DTO 목적으로 사용한다면 getter
와 setter
가 필요합니다.
필요에 따라 멤버 메서드도 작성할 수 있습니다.
class Person {
var firstName:String
var lastName:String
var age:Int
constructor(firstName:String, lastName:String, age:Int) {
this.firstName = firstName
this.lastName = lastName
this.age = age
}
fun getFullName():String {
return "$firstName $lastName $age"
}
}
클래스의 기본적인 틀은 자바와 비슷합니다. 다만 생성자를 만들어줄 때 constructor
라는 것을 통해 멤버변수를 넣어주고 this
를 사용했습니다.
그리고 멤버 메서드로 함수를 만들어주었습니다.
또 다른 방법으로는 constructor
를 사용하지 않고 클래스명 옆에 함수의 매개변수처럼 선언해주는 방법입니다.
class Person(var firstName:String, var lastName:String, var age:Int) {
fun getFullName():String {
return "$firstName $lastName $age"
}
}
이처럼 constructor
로 생성자를 만들지 않고 클래스명 옆에 멤버변수를 선언해주면 생성자가 자동으로 만들어집니다.
이 클래스를 객체로 만들어서 사용해보겠습니다.
fun main() {
val person = Person("gildong", "hong", 20)
println(person.getFullName())
}
class Person(var firstName:String, var lastName:String, var age:Int) {
fun getFullName():String {
return "$firstName $lastName $age"
}
}
자바에서는 객체를 생성할 때 new
연산자로 객체를 생성해주었고, 이를 생성하면서 초기화해줄 수 있었습니다.
코틀린에서는 new
연산자 없이 바로 객체를 생성해주고, 선언 초기화를 할 수 있습니다.
init
생성자를 통해 객체가 만들어질 때 호출되는 함수입니다. 이는 기본생성자라고 하며 constructor
즉 보조생성자를 만들어주기 전에 초기화해주는 역할을 합니다.
다시 말해서 constructor
보조 생성자로 생성자를 만들어줄 때는 init
으로 기본생성자를 만들어 초기화 해 준 이후에 보조 생성자를 만들 수 있는 것입니다.
예를 들어서
class MyClass {
var number:Int
init { // 내부 초기화, 객체 생성시 자동 호출
number = 123
println(number)
}
}
이렇게 init
으로 초기화된 클래스를 메인 함수에서 생성만 해줘도 123이 출력될 것입니다.
class Human {...}
// Human 클래스를 상속받는 Person 클래스
class Person extends Human {...}
자바에서는 클래스 상속간 extends
를 사용했습니다. 코틀린에서는 :
를 사용하여 클래스를 상속해줍니다.
class Human : Person() {}
이 때 상속해주는 부모객체에는 open
이라는 키워드를 붙여야합니다.
그래서
open class Person() {...}
class Human : Person() {...}
이렇게 클래스를 상속해줄 수 있습니다.
자바에서는 다음과 같은 접근 제어자가 있습니다.
접근 제어자 | 사용법 |
---|---|
public | public 접근제어자가 붙은 변수, 메소드는 어떤 클래스에서라도 접근이 가능하다. |
private | 접근제어자가 private 이 붙은 변수, 메소드는 해당 클래스에서만 접근이 가능하다. |
protected | protected 가 붙은 변수, 메소드는 동일 패키지의 클래스 또는 해당 클래스를 상속받은 다른 패키지의 클래스에서만 접근이 가능하다. |
코틀린에서의 접근 제어자는 자바에서의 접근 제어자와 동일하고 여기에 internal
이 추가되는데 이는 가시성 변경자로 같은 모듈 안에서만 볼 수 있게 해줍니다.
만약 다음과 같은 클래스를 생성하였다면
open class Person {
val firstName:String = ""
val lastName:String = ""
private var age:Int = 0
protected var protectedVar:Int = 123
internal var internalVar:Int = 20
private fun privateFunc() {
}
override fun toString(): String {
return "Person(firstName='$firstName', lastName='$lastName', age=$age)"
}
}
firstName
과 lastName
은 기본적으로 아무것도 생성해주지 않았기 때문에 public
으로 선언되어 있으므로 어떤 클래스에서라도 접근할 수 있습니다.
age
는 private
로 선언되어 있으므로 해당 클래스에서만 접근할 수 있습니다. 이를 다른 클래스에서 접근하도록 하기 위해서는 public
으로 변경해주면 됩니다.
protectedVar
는 protected
가 선언되어 있기 때문에 상속받은 다른 클래스에서만 접근할 수 있습니다.
internalVar
는 internal
이 선언되어 있으므로 같은 모듈에서만 접근할 수 있습니다.
그렇지만 접근제어자의 의미가 살짝 퇴색되어 가는 경향이 보이는 것 같아 그렇게 큰 의미는 없는 것 같습니다.
물론 접근 제어자를 사용해서는 안된다는 의미는 아닙니다.
getter
와 setter
&& by lazy
getter
와 setter
자바에서 private
로 선언된 클래스의 변수를 참조하여 값을 바꾸거나 조회하기 위해서는 getter
와 setter
를 사용해야 했습니다.
코틀린에서는 자바에서 public
으로 선언된 값을 조회하거나 수정하는 것처럼 직접 인스턴스로 접근해서 값을 수정해주는 방법을 사용하여 이것이 getter
와 setter
를 대신할 수 있습니다.
그렇지만 직접 getter
와 setter
를 선언하여 사용할 수 있습니다.
예를 들어
class User(_id:Int, _name:String, _age:Int) {
var id:Int = _id
get() = field
var name:String = _name
get() = field
set(value){
field = value
}
var age:Int = _age
get() = field
set(value) {
field = value
}
}
이와 같이 getter
와 setter
를 어떤 값에 대해 직접 선언해줄 수 있는데, 여기에서 field
라는 것에 주목할 필요가 있습니다.
필드는 각각의 값을 가리키는 역할을 합니다. 아이디로 예를 들면 _id
를 id
라는 변수안에 넣어주고 field
는 id
를 가리킵니다. 그래서 get()
에 field
를 선언해서 값에 대한 getter
가 그 값을 가져오도록 해줍니다.
setter
는 들어오는 값 value
를 가리키는 값 field
에 대입하여 그 값을 고칠 수 있도록 해줍니다.
by lazy
란?by lazy
는 늦은 초기화라는 것으로 객체를 생성할 때 반드시 초기화할 필요가 없는 것에 대해 붙여줍니다.
예를 들어 다음과 같은 클래스가 있다고 가정해봅시다.
class LazyClass {
init {
println("init block")
}
fun flow() {
println("subject notflow")
println("subject: $subject")
}
}
이 클래스를 메인 함수에서 객체로 생성하여 실행되는 순서를 확인해보겠습니다.
fun main(args: Array<String>) {
val lazyCls = LazyClass() // 실행순서 1
lazyCls.flow() // 실행순서 3
}
class LazyClass {
init {
println("init block") // 실행순서 2
}
fun flow() {
println("subject notflow") // 실행순서 4
println("subject: $subject") // 실행순서 5
}
}
자 그러면 LazyClass
의 기본 생성자와 멤버 메서드 사이에 by lazy
를 사용하여 늦게 초기화할 생성자를 만들어보겠습니다.
fun main(args: Array<String>) {
val lazyCls = LazyClass() // 실행순서 1
lazyCls.flow() // 실행순서 3
}
class LazyClass {
init {
println("init block") // 실행순서 2
}
val subject by lazy {
println("lazy init") // 실행순서 6
"subject Value" // 실행순서 7
}
fun flow() {
println("subject notflow") // 실행순서 4
println("subject: $subject") // 실행순서 5
}
}
이처럼 by lazy
는 초기화를 늦게 하게 됩니다. 그래서 실행순서도 달라지게 되지요.
by lazy
를 사용하려고 할 때 유의해야 할 사항입니다.
val
변수에 대해 사용 가능getter
와setter
를 지원하지 않음null
을 허용함- 클래스 생성자에서 사용 불가
- 재초기화 되지 않음
결과는 이렇게 출력될 것입니다.