요약
생성자의 형태가 다르다.
- 자바 버전
public class EmailAddress {
private final String localPart;
private final String domain;
public EmailAddress(String localPart, String domain) {
this.localPart = localPart;
this.domain = domain;
}
public String getLocalPart() {
return localPart;
}
public String getDomain() {
return domain;
}
public static EmailAddress parse(String value) {
var atIndex = value.lastIndexOf('@');
if (atIndex < 1 || atIndex == value.length() - 1) {
throw new IllegalArgumentException(
"EmailAddress must be two parts separated by @"
);
}
return new EmailAddress(
value.substring(0, atIndex),
value.substring(atIndex + 1)
);
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
EmailAddress that = (EmailAddress) o;
return Objects.equals(localPart, that.localPart) && Objects.equals(domain, that.domain);
}
@Override
public int hashCode() {
return Objects.hash(localPart, domain);
}
@Override
public String toString() {
return localPart + "@" + domain;
}
}
- 코틀린 버전
class EmailAddress(val localPart: String, val domain: String) {
override fun equals(o: Any?): Boolean {
if (this === o) return true
if (o == null || javaClass != o.javaClass) return false
val that = o as EmailAddress
return localPart == that.localPart && domain == that.domain
}
override fun hashCode(): Int {
return Objects.hash(localPart, domain)
}
override fun toString(): String {
return "$localPart@$domain"
}
companion object {
fun parse(value: String): EmailAddress {
val atIndex = value.lastIndexOf('@')
require(!(atIndex < 1 || atIndex == value.length - 1)) { "EmailAddress must be two parts separated by @" }
return EmailAddress(
value.substring(0, atIndex),
value.substring(atIndex + 1)
)
}
}
}
- 프로퍼티, 생성자, Getter 가 사라졌다.
- 프로퍼티는 클래스 이름 옆에
val localPart
와 같은 형식으로 정의된다. - 생성자는
private constructor
키워드를 입력하지 않는 한 자동으로 공개 생성자가 만들어진다. - Getter 는 프로퍼티 선언 시
private val localPart
와 같이 선언하지 않는다면, 자동으로 만들어진다.- 다만
.getXxx()
의 형태로 사용하지 않고, 단순히emailAddress.localPart
와 같은 형태로 사용한다.
- 다만
- 프로퍼티는 클래스 이름 옆에
- 정적 메서드는
companion object
라는 곳 내부로 들어갔다.
기존의 자바의 관습에 익숙한 자바 개발자라면, 코틀린의 클래스 구문에 금방 적응할 수 있을 것이다. 짧은데도 가독성을 해치지 않고 무슨 일을 하는지도 명확히 보인다.
확장함수 팁
BigDecimal.setScale()
은 객체의 상태를 변경시키지 않음에도set
이라는 접두사가 붙은 메서드이다.- 새로 설정한 정밀도에 맞춘
BigDecimal
을 반환하는 역할이다.
- 새로 설정한 정밀도에 맞춘
- 요즘은 수신 객체 상태를 변화시키지 않는 메서드엔
set
이라는 접두사를 붙이지 않는다. - 요즘 일반적인 관습으로는
with
접두사를 붙이는 것이 맞다.
amount.withScale(currency.getDefaultFractionDigits())
fun BigDecimal.withScale(int scale, RoundingMode mode) = setScale(scale, mode)
코틀린의 확장 함수를 이용해 역사적인 실수를 바로잡을 수 있다.
코틀린의 Data 클래스
data class EmailAddress private constructor (
private val localPart: String,
val domain: String
) {
companion object {
fun parse(value: String): EmailAddress {
val atIndex = value.lastIndexOf('@')
require(!(atIndex < 1 || atIndex == value.length - 1)) { "EmailAddress must be two parts separated by @" }
return EmailAddress(
value.substring(0, atIndex),
value.substring(atIndex + 1)
)
}
}
}
- kotlin 의 데이터 클래스는
toString()
,equals()
,hashCode()
를 대신 구현해준다. - 다만 프로퍼티 사이에 불변 조건을 유지해야 하는 값 타입을 데이터 클래스를 사용해 정의하면 안 된다.
위 EmailAddress
생성자의 접근자는 private
이다. parse()
정적 메서드를 통해 클래스를 생성하면 적합한 프로퍼티 값을 가진 클래스만 생성된다. 하지만 data class
를 잘못 사용하여 아래와 같은 일이 일어날 수 있다.
fun main() {
var email = EmailAddress.parse("me@gmail.com");
var email2 = email.copy("@@@@", "abc");
println(email);
println(email2)
}
data class
는 public copy()
메서드를 생성해내기 때문에 조건을 지키지 않는 새 EmailAddress
값을 만들어낼 수 있다.
또한 값 의미론이 어긋나는 경우에도
data class
를 쓰는 대신 직접 구현하는 편이 좋을 것이다.
소감
- 자바식으로 코딩할 때 생기는 보일러 플레이트 코드를 많이 줄여주어 우리가 쓸 데 없는 코드를 읽거나 생성하는데 낭비하는 시간을 줄여준다.
- 다만
data class
는 유의해서 사용하자.
반응형
'코틀린 (Kotlin) > 자바에서 코틀린으로' 카테고리의 다른 글
자바에서 코틀린으로 6장 - 자바에서 코틀린 컬렉션으로 요약 (0) | 2022.12.06 |
---|---|
자바에서 코틀린으로 5장 - 빈에서 값으로 요약 (0) | 2022.11.30 |
자바에서 코틀린으로 4장 - 옵셔널에서 널이 될 수 있는 타입으로 요약 (0) | 2022.11.30 |