Java와 비교해서 Kotlin 맛보기
먼저 Kotlin을 잠시 맛보고 가도록 하자
클래스(Class)라는 개념의 목적은 데이터를 캡슐화하고 캡슐화한 데이터를 다루는 코드를 한 주체 아래 가두는 것이다.
// java code 1-1
public class Person {
private final String name;
public Person(String name) {
this.name = name;
}
public String getName() {
return name;
}
}
java code 1-1을 코틀린으로 변환해보자
// kotlin code 1-1
class Person(val name: String)
11줄을 차지하던 Person 클래스의 코드가 단 1줄로 변환되었다.
이와 같이 코틀린은 훨씬 간결한 코드가 가능하다.
현재는 Person이라는 클래스에 name이라는 프로퍼티만 들어있지만 둘 이상으로 늘어나는
경우엔 코드량의 차이가 훨씬 더 클 것이다.
또 한가지 차이점에 주목할 수 있다.
자바 코드에 있던 public이 코틀린 코드에서는 사라졌다.
코틀린의 기본 접근 제한자는 public이라는 것을 알 수 있다.
생성자
코틀린의 클래스에서 괄호에 둘러싸인 코드를 주 생성자 (primary constructor)라고 부른다.
주 생성자는 두 가지 목적에 쓰인다.
- 생성자 파라미터를 지정한다.
- 그 생성자 파라미터에 의해 초기화되는 프로퍼티를 정의한다.
말이 좀 어렵고 이해가 안 갈 수 있다. 헷갈린다면 아래 예시를 보자
String형 인자가 생성자 파라미터로 넘어오며, 해당 값으로 name이라는 프로퍼티를 초기화한다.
(여기서 네이밍이 전부 name이라 헷갈린다면 생성자 파라미터의 이름을 바꾸어도 된다!)
// kotlin code 2-1
class Person(name: String) { // 생성자 파라미터가 지정되었다.
val name: String // name 프로퍼티 정의
init {
this.name = name // 생성자 파라미터에 의해 포로퍼티를 초기화한다.
}
}
이제 주 생성자의 두 가지 목적이 이해가 됐는가??
그렇다면 아래 코드를 보자.
어디서 많이 본듯한 아래 코드는 방금 위에서 본 kotlin code 2-1과 같다.
// kotlin code 1-1
class Person(val name: String)
갑자기 진도가 빨라서 어지러워졌다면 아까 봤던 java code 1-1자바 코드를 같이 보자
// java code 1-1
public class Person {
private final String name;
public Person(String name) {
this.name = name;
}
public String getName() {
return name;
}
}
위 코드를 분리해서 보자
// name 프로퍼티를 정의
private final String name;
public String getName() {
return name;
}
// 생성자 파라미터 지정
public Person(String name) {}
// 생성자 파라미터에 의해 프로퍼티 초기화
this.name = name;
코틀린 주 생성자의 두 가지 목적을 자바 코드로 풀어보았다.
어지럼증이 좀 나아졌다면 다시 보자
위 자바 코드들은 java code 1-1코드를 역할별로 분리해서 보여준 것이다.
그리고 java code 1-1는 kotlin code 1-1과 같다.
이제 kotlin code 2-1과 kotlin code 1-1이 같은게 이해가 되는가?
안되면,, 미안!
위에서 우린 코틀린 생성자와 코틀린의 편의성에 대해 알았다.
그럼 계속 언급했었던 프로퍼티는 무엇일까?
해당 문맥만 봤을 땐 마치 필드(클래스 멤버 변수)처럼 보인다.
이제 프로퍼티에 대해 알아보자
Property
- 재산
- 소유물
- (사물의) 속성[특성]
Java
자바에서는 필드와 접근자를 한데 묶어 프로퍼티라고 부른다.
데이터를 필드에 저장하며
멤버 필드의 접근 제한자는 보통 private이고
private인 해당 데이터(필드)들에 접근하기 위해 접근자 메서드를 사용한다.
접근자 메서드로는 읽기 위한 getter, 변경하기 위한 setter가 있다.
public class Person {
private final String name;
private Boolean isMarried;
public Person(String name, Boolean isMarried) {
this.name = name;
this.isMarried = isMarried;
}
public String getName() {
return name;
}
public Boolean isMarried() {
return isMarried;
}
public void setMarried(Boolean isMarried) {
this.isMarried = isMarried;
}
}
// 자바에서는 이름이 is로 시작하는 프로퍼티의 getter에는 get을 붙이지 않고 원래 이름을 그대로 사용하며
// setter에는 is를 set으로 바꾼 이름을 사용한다.
Kotlin
반면 코틀린은 프로퍼티를 언어의 기본 기능으로 제공하여 자바의 필드와 접근자 메서드를 완전히 대신한다.
class Person(
val name: String, // 읽기 전용 프로퍼티, (비공개)필드 + getter
var isMarried: Boolean, // 변경 가능한 프로퍼티 (비공개)필드 + getter + setter
// 하지만 프로퍼티가 꼭 멤버 필드를 포함하는 것은 아니다. 밑에서 확인해보자
)
자바와 코틀린에서 프로퍼티가 무엇인지를 알아보았다.
이제 프로퍼티가 무엇인지, 자바와 코틀린에서는 어떤 차이가 있는지 좀 알 것 같..나? (모르면 미안!)
그렇다면 클래스 외부에서 사용될 때는 어떨까? 아래 코드를 보자
// Java
public class Sample {
public static void main(String[] args) {
Person person = new Person("Bix", false); // 생성자 호출하여 person 클래스 생성
System.out.println(person.getName()); // name 필드의 getter를 호출
System.out.println(person.isMarried()); // isMarried 필드의 getter를 호출
person.setMarried(true); // isMarried 필드의 setter를 호출
}
}
// Kotlin
fun main() {
val person = Person("Bix", false) // new 키워드 없이 생성자 호출
println(person.name) // 프로퍼티 이름을 직접 사용해도 코틀린이 자동으로 getter를 호출해준다.
println(person.isMarried) // 위와 동일
person.isMarried = true // 대입하는 방식으로 setter(프로퍼티에 내장된)가 호출됨
}
코틀린의 name과 isMarried 프로퍼티를 자바에선 getter를 통해 불러온다.
또 코틀린은 등호를 통해 isMarried의 값을 지정하는 반면 자바는 setter를 통해 지정해준다.
커스텀 접근자 (custom getter/setter)
지금까지 코틀린의 프로퍼티에서는 접근자(getter, setter)가 언어에서 기본 기능으로 제공되었다.
하지만 필요하다면 우리가 직접 프로퍼티의 접근자를 작성할 수 있다.
직사각형 클래스인 Rectangle을 정의하면서 자신이 정사각형인지 알려주는 기능을 만들어보자.
직사각형이 정사각형인지를 별도의 필드에 저장할 필요가 없다.
사각형의 너비와 높이가 같은지 검사하면 정사각형 여부를 그때그때 알 수 있다.
class Rectangle(val height: Int, val width: Int) {
val isSquare: Boolean
get() { // 프로퍼티의 getter 선언
return height == width
}
}
class Rectangle(val height: Int, val width: Int) {
val isSquare: Boolean get() = height == width
}
위 코드와 아래 코드는 표현만 다를 뿐 같은 코드이다.
isSquare 프로퍼티에는 자체 값을 저장하는 필드가 필요 없다.
이 프로퍼티에는 자체 구현을 제공하는 getter만 존재한다.
클라이언트가 프로퍼티에 접근할 때마다 getter가 프로퍼티 값을 매번 다시 계산한다.
이와 같은 커스텀 접근자를 이용하면 프로퍼티에 값을 저장하기 위한 backing field를 두지 않고 프로퍼티 값을 그때그때 계산할 수 있다.
(위에서 말했듯) 이처럼 프로퍼티가 꼭 필드를 가지고있어야하는 것은 아니다.
예시
생성자와 프로퍼티에 대해 알아봤는데 여러가지 헷갈릴만한 부분에 대한 예시를 적어보려고한다.
1. 프로퍼티와 생성자 매개변수의 차이
// kotlin code 3-1
class Person(val name: String)
// kotlin code 3-2
class Person(name: String)
위 코드와 아래 코드의 차이를 알겠는가? (알면 넘어가자!)
나는 코틀린을 처음할 때 이 부분이 정말 헷갈렸다,,
kotlin code 3-1의 name은 프로퍼티이고, kotlin code 3-2의 name은 그저 생성자 매개변수이다.
위 코드들을 자바 코드로 디컴파일 해보면 아래와 같이 나온다.
// java code 3-1
import org.jetbrains.annotations.NotNull;
public final class Person {
@NotNull
private final String name;
@NotNull
public final String getName() {
return this.name;
}
public Person(@NotNull String name) {
Intrinsics.checkNotNullParameter(name, "name");
super();
this.name = name;
}
}
// java code 3-2
import org.jetbrains.annotations.NotNull;
public final class Person {
public Person(@NotNull String name) {
Intrinsics.checkNotNullParameter(name, "name");
super();
}
}
즉 kotlin code 3-1처럼생성자 매개변수에 var 혹은 val을 붙이게되면 해당 매개변수로 초기화되는 프로퍼티를 정의하는 것과 같다.
하지만 kotlin code 3-2처럼 다른 키워드 없이 생성자 매개변수만 지정하게되면 name에 담긴 String 값만 있을 뿐 필드와 접근자 메서드 모두 존재하지 않는다.
2. 너무나 헷갈리는 프로퍼티 선언
위에서 비슷한 내용으로 한 번 언급했었지만 한 번 더 해보려고한다.
// kotlin code 4-1
class Person(val name: String)
// kotlin code 4-2
class Person(name: String)
val name: String
init {
this.name = name
}
}
// kotlin code 4-3
class Person()
val name: String = "빅스"
}
위 세 개의 코드는 다 다른 코드일까?
앞에서 말했듯 kotlin code 4-2는 kotlin code 4-1을 풀어서 적은 것 뿐이다.
그러므로 둘은 같은 코드이다.
그렇다면 kotlin code 4-3은 무슨 차이가 있을까?
양쪽 모두 val(read-only)이며 String형의 name이라는 프로퍼티를 갖고 있다.
둘의 차이는 단순히 name 프로퍼티의 backing field 값을 외부에서 가져오느냐, 내부에서 지정해주느냐의 차이일 뿐이다.
그러므로 두 클래스 모두 name 프로퍼티를 갖는다는 것에는 전혀 차이가 없으므로 name에 지정할 값을 외부에서 주입받는다면 kotlin code 4-1, 내부에서 지정해준다면 kotlin code 4-3처럼 프로퍼티를 선언하면 되겠다!
'Kotlin' 카테고리의 다른 글
[Kotlin] 확장 함수! 신기한 당신의 정체는? (0) | 2023.04.24 |
---|---|
[Kotlin] 프로퍼티 용어 정리 (0) | 2023.04.05 |
[Kotlin] 인터페이스와 추상클래스의 차이 (1) | 2023.03.22 |
[Kotlin] 코틀린 인터페이스(Interface) (0) | 2023.02.21 |