본문 바로가기

공부방/Kotlin

코틀린에서 클래스를 다루는 방법

코틀린에서 클래스를 만드는 방법


코틀린에서는 클래스를 어떻게 만들까요?

코틀린에서 클래스 만드는 방법을 알아보기 위해 자바로 클래스를 만들고 그 코드를 코틀린으로 변환해보면서 연습해보도록 하겠습니다.

 

[Java]

일반적인 자바 클래스입니다.

name과 age를 필드 변수로 가지고 있으면서 getter와 setter를 구현해준 모습입니다.

이것을 코틀린으로 변환해 보겠습니다.

 

package lec09;

public class JavaPerson {

	// name은 변경할 수 없다고 가정합니다.
	private final String name;
	private int age;

	public JavaPerson(String name, int 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;
	}
}

 

코틀린으로 클래스를 만들때는 아래와 같은 특성이 있습니다.

[Kotlin]

  • 기본이 public이기 때문에 클래스 앞에 존재하는 public은 생략 가능합니다.
  • 코틀린에서는 constructor 라는 코드를 통해 생성자를 구현할 수 있습니다.
  • 코틀린에서는 필드만 만들면 getter, setter를 자동으로 만들어줍니다.
  • constructor라는 지시어는 생략 가능합니다.
  • 생성자에서 프로퍼티를 만들 수 있기 때문에 필드도 생략 가능합니다.

위 특성을 바탕으로 코틀린으로 클래스를 만들어보겠습니다.

 

[getter setter 생략]

코틀린에서는 getter와 setter를 만들지 않아도 자동으로 생성이됩니다.

name 변수는 val 로 선언을 하였기 때문에 setter는 없고, getter만 존재할 것이고

age란 변수는 var로 선언을 해줬기 때문에 getter와 setter가 모두 있을 것입니다.

 

public class KotlinPerson constructor(name: String, age: Int) {
	val name: String = name
	var age: Int = age
}

 

[Public 생략]

코틀린은 public 생략할 수 있습니다.

기본 접근제한자가 public이기 때문입니다.

 

class KotlinPerson constructor(name: String, age: Int) {
	val name: String = name
	var age: Int = age
}

 

[생성자 커맨드 생략]

코틀린은 생성자를 생략할 수 있습니다.

 

class KotlinPerson(name: String, age: Int) {
	val name: String = name
	var age: Int = age
}

 

[필드 변수 생략 (생성자에서 초기화 가능)]

생성자 안에서 초기화를 할 수 있기 때문에 필드 변수를 선언하지 않아도 됩니다. 

생성자 안에 val과 var을 선언한 모습을 볼 수 있습니다.

 

class KotlinPerson(val name: String, var age: Int) {
	
}

 

[중괄호 생략]

바디값에 아무것도 없기 때문에 중괄호를 생략해도 무방합니다.

최종 코드입니다.

 

class KotlinPerson(val name: String, var age: Int)

 

getter와 setter 호출


코틀린에서 getter와 setter를 호출하는 방법은 간단합니다.

"변수.속성

 

fun main() {
	val person = KotlinPerson("name", 100)
	// getter 호출
	val name = person.name
	// setter 호출
	person.age = 10
	println(person.age)
}

 

코틀린에서 자바의 getter, setter를 호출할 때도 위와 같이 동일한 문법이 적용됩니다.

 

Java 생성자 로직 검증


[Java]

자바에서는 아래와 같이 생성자에서 값을 검증할 수 있습니다.

코틀린에서는 어떻게 구현할 수 있을까요?

 

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 String getName() {
    return name;
  }

  public int getAge() {
    return age;
  }

  public void setAge(int age) {
    this.age = age;
  }
}

 

[Kotlin]

코틀린에서는 init이라는 특별한 블록이 존재합니다.

init 블록은 클래스가 초기화되는 시점에 한 번 호출되는 블록입니다.

init이라는 블록에서 검증 로직을 만들어줄 수 있습니다.

 

class KotlinPerson(
    val name: String,
    var age: Int
) {
    init {
        if (age < 0)
            throw IllegalArgumentException("나이는 ${this.age} 일 수 없습니다")
    }
}

 

부 생성자

 

[Java]

최초로 태어나는 아기는 무조건 1살이니, 생성자를 하나 더 만들어보겠습니다.

아래와 같이 자바는 생성자를 하나 더 만들어 줄 수 있습니다.

그리고 새로 만들어진 생성자는 기존 생성자를 호출하는 형식으로 되어 있습니다.

 

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;
  }
}

 

[Kotlin]

코틀린에서는 어떻게 생성자를 하나 더 만들 수 있을까요?

코틀린에서는 constructor 라는 명령어를 통해 생성자를 하나 더 만들 수 있습니다.

바로 아래와 같이 말이죠.

 

주 생성자

아래 빨간 박스에 있는 것이 주 생성자입니다.

 

부 생성자

코틀린에서는 아래 빨간 박스가 부 생성자입니다.

 

 

부 생성자는 body를 가질 수 있습니다.

부 생성자는 추가로 계속해서 만들 수 있습니다.

아래와 같이 말이죠

 

class KotlinPerson(
    val name: String,
    var age: Int
) {
    init {
        if (age < 0)
            throw IllegalArgumentException("나이는 ${this.age} 일 수 없습니다")
    }

    constructor(name: String): this(name, 1) {
        println("첫번째 부 생성자")
    }
}

 

부 생성자를 위와 같이 만들 수는 있지만 default parameter나 정적 팩토리 메소드를 사용하는 것을 추천합니다.

 

커스텀 getter, setter


이 내용을 이해하기 위해서는 프로퍼티에 대한 개념을 먼저 이해해야합니다.

 

property 요약

자바에는 필드와 프로퍼티가 존재.

필드: 대부분의 사람들이 알고 있는 것처럼 필드 변수 (클래스 안에 존재하는 변수)를 의미합니다.

프로퍼티: 필드와 접근자(getter/setter)를 통틀어 말하는 것입니다.

 

코틀린에서 자바와 동일하게 필드 변수를 선언하면 자동으로 getter와 setter가 만들어 지기 때문에 

코틀린에서는 필드가 존재하지 않고 모두 프로퍼티라고 할 수 있습니다.

 

코틀린에서 접근자 (getter/ setter)를 자동으로 만들어 준다고 하였는데, 이를 만드는 문법은 다음과 같습니다.

 

변수 선언 : 타입 선언
    get()
    set()

 

코틀린 코드로 표현한다면 다음과 같을 것입니다.

 

var name: String = "weekyear"
    get() = field
    set(value) = { field = value }

 

 

자, 프로퍼티를 설명을 읽었다면, custom getter와 custom setter를 만드는 방법이 이해되셨을 겁니다.

예제를 통해 알아보도록 합시다.

 

[Java]

자바에서 아래와 같은 코드가 있습니다.

성인여부를 확인하는 코드입니다.

코틀린에서 구현 방법을 알아봅시다.

 

public boolean isAdult() {
	return this.age >= 20;
}

 

[Kotlin]

 

 

 

BackingField


자기 자신을 가리키는 field 예약어인데 이것을 backing field라고 합니다.

getter에서 backingfield가 필요한 이유는 무엇일까요?

아래의 코드가 있다고 가정해봅시다.

 

 

외부에서 객체.name 이라는 명령어를 통해 getter를 호출한 경우

getter에 있는 name 변수가 다시 get()을 호출하게 되어 재귀함수가 되어 버리는 문제가 발생합니다.

이를 해결하기 위해서는 name.uppercase() -> field.uppercase() 로 고쳐야하는데 이때 사용되는 것이 field 예약어, backing field 입니다.

 

 

setter에서도 똑같은 문제가 발생할 수 있기 때문에 backing field를 사용해서 문제를 해결하면 됩니다.

예시를 통해 문제를 확인하고 어떻게 해결하면 될 지 생각해 봅시다.

 

 

Backingfield를 사용하지 않고, upperCaseName이라는 property를 새로 만들어주는 방법을 택하면 backing field를 사용하지 않아도 됩니다.