
- 코틀린에서는 클래스 내부의 멤버 변수를 Property라고 부릅니다.
- 자바에서는 클래스 내부의 멤버 변수를 Field라고 부릅니다.
코틀린의 var/val을 자바로 디컴파일하면 private/private final이 붙는다.
저는 생성자 학습을 위해 임의의 클래스를 만들고, 코틀린 클래스 파일을 자바 파일로 디컴파일하여 비교를 해보았습니다. 그리고 다음과 같은 사실을 알아냈습니다.
- 코틀린에서 var 로 선언한 멤버 변수가 디컴파일 된 자바 파일에서 private으로 변경됩니다.
- getter 접근자가 추가 됩니다.
- setter 접근자가 추가 됩니다.
- 코틀린에서 private var로 선언한 멤버 변수가 디컴파일 된 자바 파일에서 private 으로 변경됩니다.
- getter 접근자가 추가 되지 않습니다.
- setter 접근자는 추가 되지 않습니다.
- 코틀린에서 val 로 선언한 멤버 변수가 디컴파일 된 자바 파일에서 private final로 변경됩니다.
- getter 접근자가 추가 됩니다.
- setter 접근자는 추가 되지 않습니다.
- 코틀린에서 private val 로 선언한 멤버 변수가 디컴파일 된 자바 파일에서 private final로 변경됩니다.
- getter 접근자가 추가 되지 않습니다.
- setter 접근자는 추가 되지 않습니다.
왜 무조건 private 또는 private final이 붙을까
우선 코틀린 → 자바로 디컴파일 되는 과정에서 private과 final 이라는 키워드가 붙는 이유를 알기 위해서는 자바의 Field 개념과 캡슐화에 대해서 알아야 합니다.
자바의 Field
필드는 클래스에 포함된 변수를 의미합니다. 주로, 클래스의 속성값을 저장하기 위해 선언하는 변수들을 필드라고 부릅니다.
캡슐화
캡슐화는 한 줄로 표현할 수 없을 정도로 많은 내용을 가지고 있지만, 글의 주제를 이해할 정도로만 간단하게 소개하고 넘어가겠습니다!
캡슐화란 아주 쉽게 말하면 객체의 속성인 필드, 행위인 메소드와 같은 여러 요소들을 외부로부터 숨기는 개념입니다.
캡슐화를 통해 객체의 구체적인 정보를 드러내지 않고 숨기는 이유는 외부의 무분별한 접근을 피해 필드의 값이 변하지 않게 하는 데이터의 무결성(데이터 정확성 및 일관성을 유지, 보증)을 누리기 위함 입니다.
자바에서는 이렇게 숨겨진 필드에 대해 Getter와 Setter라고 불리는 접근자/수정자 메서드를 통해서 캡슐화된 필드에 대한 접근을 허용합니다.
코틀린은 자바랑 상호 운용적이다.
위 내용을 정리하면, 자바는 캡슐화를 위해 필드의 직접적인 접근을 접근제어자 private으로 막고, Getter/Setter를 열어두어 간접적인 접근/수정을 허용하는 방식으로 코딩을 합니다.
무분별한 Setter/Getter 사용을 막고, 다른 방법으로 코딩을 해야 한다는 말이 많이 나오지만 초보자용 강의에서는 거의 이렇게 알려주고 사용하라고 합니다.
public class Book {
// 필드
private String title;
private int totalPage;
// 접근/수정자
public String getTitle() {
return this.title;
}
public void setTitle(String title) {
this.title = title;
}
public int getTotalPage() {
return this.totalPage;
}
public void setTotalPage(int totalPage) {
this.totalPage = totalPage;
}
}
코틀린에서는 자바와의 상호 운용을 위해 이러한 코딩 스타일을 최대한 반영하여 만들어졌습니다.
따라서 자바로 디컴파일된 코틀린 프로퍼티의 필드는 다음과 같이 바뀌게 됩니다.
- var의 경우
- 값의 연속적인 초기화가 가능한 var는 private이 됩니다.
- 접근 제어자에 따라서 getter나 setter를 생성합니다.
- val의 경우
- 한 번만 초기화 가능한 val의 경우에는 private final이 됩니다.
- 자바에서는 한 번만 할당 가능한(Write once), 상수의 의미로 final 키워드를 사용합니다.
- 접근 제어자에 따라서 getter를 생성합니다.
- 한 번만 초기화 가능한 val의 경우에는 private final이 됩니다.
이 내용을 토대로 자바의 Primitive 타입과 Reference 타입으로 나누어 각 접근 제어자 별로 디컴파일 된 결과를 확인해보겠습니다.
각 접근 제어자 별 디컴파일 결과
코틀린 코드
package property
class PropertyExample(
var stringWithVar: String, // reference Type
val stringWithVal: String,
var intWithVar: Int, // primitive Type
val intWithVal: Int,
protected var stringWithVarProtected: String,
internal var stringWithVarInternal: String,
private var stringWithVarPrivate: String,
protected var intWithVarProtected: Int,
internal var intWithVarInternal: Int,
private var intWithVarPrivate: Int,
// val 이라면 setter 접근자를 Java에 만들지 않음
) {
}
자바 코드(17버전)
public final class PropertyExample {
// var stringWithVar: String
@NotNull
private String stringWithVar;
@NotNull
public final String getStringWithVar() {
return this.stringWithVar;
}
public final void setStringWithVar(@NotNull String var1) {
Intrinsics.checkNotNullParameter(var1, "<set-?>");
this.stringWithVar = var1;
}
// val stringWithVal: String,
@NotNull
private final String stringWithVal;
@NotNull
public final String getStringWithVal() {
return this.stringWithVal;
}
// var intWithVar: Int,
private int intWithVar;
public final int getIntWithVar() {
return this.intWithVar;
}
public final void setIntWithVar(int var1) {
this.intWithVar = var1;
}
// val intWithVal: Int,
private final int intWithVal;
public final int getIntWithVal() {
return this.intWithVal;
}
// protected
@NotNull
private String stringWithVarProtected;
@NotNull
protected final String getStringWithVarProtected() {
return this.stringWithVarProtected;
}
protected final void setStringWithVarProtected(@NotNull String var1) {
Intrinsics.checkNotNullParameter(var1, "<set-?>");
this.stringWithVarProtected = var1;
}
// internal
@NotNull
private String stringWithVarInternal;
@NotNull
public final String getStringWithVarInternal$textRpg() {
return this.stringWithVarInternal;
}
public final void setStringWithVarInternal$textRpg(@NotNull String var1) {
Intrinsics.checkNotNullParameter(var1, "<set-?>");
this.stringWithVarInternal = var1;
}
// private
private String stringWithVarPrivate;
// 접근자 없음
// protected
private int intWithVarProtected;
protected final int getIntWithVarProtected() {
return this.intWithVarProtected;
}
protected final void setIntWithVarProtected(int var1) {
this.intWithVarProtected = var1;
}
// internal
private int intWithVarInternal;
public final int getIntWithVarInternal$textRpg() {
return this.intWithVarInternal;
}
public final void setIntWithVarInternal$textRpg(int var1) {
this.intWithVarInternal = var1;
}
// private
private int intWithVarPrivate;
// 접근자 없음
결과
- public의 경우(Primitive Type & Reference Type)
- public var
- getter/setter가 모두 생성됩니다.
- public val
- getter가 생성됩니다.
- public var
- protected의 경우(Primitive Type & Reference Type)
- protected var
- getter/setter가 모두 생성됩니다.
- protected val
- getter가 생성됩니다.
- protected var
- internal의 경우(Primitive Type & Reference Type)
- protected var
- getter/setter가 모두 생성됩니다.
- protected val
- getter가 생성됩니다.
- protected var
- private의 경우(Primitive Type & Reference Type)
- private var
- getter/setter가 모두 생성되지 않습니다.
- private val
- getter/setter가 모두 생성되지 않습니다.
- private var
정리
- 우선, 상속 관계를 가지는 클래스, 같은 모듈이 아닌 클래스를 만들지 않고 하나의 클래스로만 디컴파일을 진행했기 때문에 protected와 internal의 경우가 public과 같은 결과를 보이는 것 같습니다.
- 하지만 실제로 위 경우를 모두 적용하여 다시 디컴파일 한다면 다음과 같은 결과를 보여줄 것이라고 생각합니다.
- protected 경우
- 상속관계가 없는 경우에는 자바 파일에 getter나 setter를 생성하지 않았을 것이고
- 상속 관계가 있는 경우에는 자바 파일에 getter와 setter를 생성할 것입니다.
- internal의 경우
- 같은 모듈이 아닌 경우에는 자바 파일에 getter나 setter를 생성하지 않았을 것이고
- 같은 모듈인 경우에는 자바 파일에 getter와 setter를 생성할 것입니다.
- protected 경우
- private의 경우에는 무조건 setter/getter 접근을 허용하지 않는 것을 알게 되었습니다.
번외(Reference 타입에만 @NotNull이 붙는다.)
- title에 @NotNull이 붙은 이유
- Kotlin은 Null-Safety한 언어입니다. 반면에 자바는 그렇지 않습니다. 따라서 자바로 디컴파일된 코드에서는 어노테이션 @NotNull을 사용해서 String에 값이 Null이 들어오면 안된다고 명시하고 있습니다.
- 하지만 자바의 Primitive 타입에는 null이 들어갈 수 없습니다. 따라서 Reference 타입에만 @NotNull 어노테이션이 붙게 됩니다.
'Kotlin & Java' 카테고리의 다른 글
Mockito 예외 - org.mockito.exceptions.misusing.MissingMethodInvocationException:when() requires an argument which has to be 'a method call on a mock'. (0) | 2024.08.23 |
---|---|
@WebMvcTest EntityManagerFactory 빈 주입 이슈 - A component required a bean named 'entityManagerFactory' that could not be found. (0) | 2024.08.22 |
코틀린 코딩 컨벤션 (3) | 2023.12.18 |
코틀린의 자료형과 자바의 자료형 (1) | 2023.12.07 |
코틀린의 생성자 (0) | 2023.12.05 |

- 코틀린에서는 클래스 내부의 멤버 변수를 Property라고 부릅니다.
- 자바에서는 클래스 내부의 멤버 변수를 Field라고 부릅니다.
코틀린의 var/val을 자바로 디컴파일하면 private/private final이 붙는다.
저는 생성자 학습을 위해 임의의 클래스를 만들고, 코틀린 클래스 파일을 자바 파일로 디컴파일하여 비교를 해보았습니다. 그리고 다음과 같은 사실을 알아냈습니다.
- 코틀린에서 var 로 선언한 멤버 변수가 디컴파일 된 자바 파일에서 private으로 변경됩니다.
- getter 접근자가 추가 됩니다.
- setter 접근자가 추가 됩니다.
- 코틀린에서 private var로 선언한 멤버 변수가 디컴파일 된 자바 파일에서 private 으로 변경됩니다.
- getter 접근자가 추가 되지 않습니다.
- setter 접근자는 추가 되지 않습니다.
- 코틀린에서 val 로 선언한 멤버 변수가 디컴파일 된 자바 파일에서 private final로 변경됩니다.
- getter 접근자가 추가 됩니다.
- setter 접근자는 추가 되지 않습니다.
- 코틀린에서 private val 로 선언한 멤버 변수가 디컴파일 된 자바 파일에서 private final로 변경됩니다.
- getter 접근자가 추가 되지 않습니다.
- setter 접근자는 추가 되지 않습니다.
왜 무조건 private 또는 private final이 붙을까
우선 코틀린 → 자바로 디컴파일 되는 과정에서 private과 final 이라는 키워드가 붙는 이유를 알기 위해서는 자바의 Field 개념과 캡슐화에 대해서 알아야 합니다.
자바의 Field
필드는 클래스에 포함된 변수를 의미합니다. 주로, 클래스의 속성값을 저장하기 위해 선언하는 변수들을 필드라고 부릅니다.
캡슐화
캡슐화는 한 줄로 표현할 수 없을 정도로 많은 내용을 가지고 있지만, 글의 주제를 이해할 정도로만 간단하게 소개하고 넘어가겠습니다!
캡슐화란 아주 쉽게 말하면 객체의 속성인 필드, 행위인 메소드와 같은 여러 요소들을 외부로부터 숨기는 개념입니다.
캡슐화를 통해 객체의 구체적인 정보를 드러내지 않고 숨기는 이유는 외부의 무분별한 접근을 피해 필드의 값이 변하지 않게 하는 데이터의 무결성(데이터 정확성 및 일관성을 유지, 보증)을 누리기 위함 입니다.
자바에서는 이렇게 숨겨진 필드에 대해 Getter와 Setter라고 불리는 접근자/수정자 메서드를 통해서 캡슐화된 필드에 대한 접근을 허용합니다.
코틀린은 자바랑 상호 운용적이다.
위 내용을 정리하면, 자바는 캡슐화를 위해 필드의 직접적인 접근을 접근제어자 private으로 막고, Getter/Setter를 열어두어 간접적인 접근/수정을 허용하는 방식으로 코딩을 합니다.
무분별한 Setter/Getter 사용을 막고, 다른 방법으로 코딩을 해야 한다는 말이 많이 나오지만 초보자용 강의에서는 거의 이렇게 알려주고 사용하라고 합니다.
public class Book {
// 필드
private String title;
private int totalPage;
// 접근/수정자
public String getTitle() {
return this.title;
}
public void setTitle(String title) {
this.title = title;
}
public int getTotalPage() {
return this.totalPage;
}
public void setTotalPage(int totalPage) {
this.totalPage = totalPage;
}
}
코틀린에서는 자바와의 상호 운용을 위해 이러한 코딩 스타일을 최대한 반영하여 만들어졌습니다.
따라서 자바로 디컴파일된 코틀린 프로퍼티의 필드는 다음과 같이 바뀌게 됩니다.
- var의 경우
- 값의 연속적인 초기화가 가능한 var는 private이 됩니다.
- 접근 제어자에 따라서 getter나 setter를 생성합니다.
- val의 경우
- 한 번만 초기화 가능한 val의 경우에는 private final이 됩니다.
- 자바에서는 한 번만 할당 가능한(Write once), 상수의 의미로 final 키워드를 사용합니다.
- 접근 제어자에 따라서 getter를 생성합니다.
- 한 번만 초기화 가능한 val의 경우에는 private final이 됩니다.
이 내용을 토대로 자바의 Primitive 타입과 Reference 타입으로 나누어 각 접근 제어자 별로 디컴파일 된 결과를 확인해보겠습니다.
각 접근 제어자 별 디컴파일 결과
코틀린 코드
package property
class PropertyExample(
var stringWithVar: String, // reference Type
val stringWithVal: String,
var intWithVar: Int, // primitive Type
val intWithVal: Int,
protected var stringWithVarProtected: String,
internal var stringWithVarInternal: String,
private var stringWithVarPrivate: String,
protected var intWithVarProtected: Int,
internal var intWithVarInternal: Int,
private var intWithVarPrivate: Int,
// val 이라면 setter 접근자를 Java에 만들지 않음
) {
}
자바 코드(17버전)
public final class PropertyExample {
// var stringWithVar: String
@NotNull
private String stringWithVar;
@NotNull
public final String getStringWithVar() {
return this.stringWithVar;
}
public final void setStringWithVar(@NotNull String var1) {
Intrinsics.checkNotNullParameter(var1, "<set-?>");
this.stringWithVar = var1;
}
// val stringWithVal: String,
@NotNull
private final String stringWithVal;
@NotNull
public final String getStringWithVal() {
return this.stringWithVal;
}
// var intWithVar: Int,
private int intWithVar;
public final int getIntWithVar() {
return this.intWithVar;
}
public final void setIntWithVar(int var1) {
this.intWithVar = var1;
}
// val intWithVal: Int,
private final int intWithVal;
public final int getIntWithVal() {
return this.intWithVal;
}
// protected
@NotNull
private String stringWithVarProtected;
@NotNull
protected final String getStringWithVarProtected() {
return this.stringWithVarProtected;
}
protected final void setStringWithVarProtected(@NotNull String var1) {
Intrinsics.checkNotNullParameter(var1, "<set-?>");
this.stringWithVarProtected = var1;
}
// internal
@NotNull
private String stringWithVarInternal;
@NotNull
public final String getStringWithVarInternal$textRpg() {
return this.stringWithVarInternal;
}
public final void setStringWithVarInternal$textRpg(@NotNull String var1) {
Intrinsics.checkNotNullParameter(var1, "<set-?>");
this.stringWithVarInternal = var1;
}
// private
private String stringWithVarPrivate;
// 접근자 없음
// protected
private int intWithVarProtected;
protected final int getIntWithVarProtected() {
return this.intWithVarProtected;
}
protected final void setIntWithVarProtected(int var1) {
this.intWithVarProtected = var1;
}
// internal
private int intWithVarInternal;
public final int getIntWithVarInternal$textRpg() {
return this.intWithVarInternal;
}
public final void setIntWithVarInternal$textRpg(int var1) {
this.intWithVarInternal = var1;
}
// private
private int intWithVarPrivate;
// 접근자 없음
결과
- public의 경우(Primitive Type & Reference Type)
- public var
- getter/setter가 모두 생성됩니다.
- public val
- getter가 생성됩니다.
- public var
- protected의 경우(Primitive Type & Reference Type)
- protected var
- getter/setter가 모두 생성됩니다.
- protected val
- getter가 생성됩니다.
- protected var
- internal의 경우(Primitive Type & Reference Type)
- protected var
- getter/setter가 모두 생성됩니다.
- protected val
- getter가 생성됩니다.
- protected var
- private의 경우(Primitive Type & Reference Type)
- private var
- getter/setter가 모두 생성되지 않습니다.
- private val
- getter/setter가 모두 생성되지 않습니다.
- private var
정리
- 우선, 상속 관계를 가지는 클래스, 같은 모듈이 아닌 클래스를 만들지 않고 하나의 클래스로만 디컴파일을 진행했기 때문에 protected와 internal의 경우가 public과 같은 결과를 보이는 것 같습니다.
- 하지만 실제로 위 경우를 모두 적용하여 다시 디컴파일 한다면 다음과 같은 결과를 보여줄 것이라고 생각합니다.
- protected 경우
- 상속관계가 없는 경우에는 자바 파일에 getter나 setter를 생성하지 않았을 것이고
- 상속 관계가 있는 경우에는 자바 파일에 getter와 setter를 생성할 것입니다.
- internal의 경우
- 같은 모듈이 아닌 경우에는 자바 파일에 getter나 setter를 생성하지 않았을 것이고
- 같은 모듈인 경우에는 자바 파일에 getter와 setter를 생성할 것입니다.
- protected 경우
- private의 경우에는 무조건 setter/getter 접근을 허용하지 않는 것을 알게 되었습니다.
번외(Reference 타입에만 @NotNull이 붙는다.)
- title에 @NotNull이 붙은 이유
- Kotlin은 Null-Safety한 언어입니다. 반면에 자바는 그렇지 않습니다. 따라서 자바로 디컴파일된 코드에서는 어노테이션 @NotNull을 사용해서 String에 값이 Null이 들어오면 안된다고 명시하고 있습니다.
- 하지만 자바의 Primitive 타입에는 null이 들어갈 수 없습니다. 따라서 Reference 타입에만 @NotNull 어노테이션이 붙게 됩니다.
'Kotlin & Java' 카테고리의 다른 글
Mockito 예외 - org.mockito.exceptions.misusing.MissingMethodInvocationException:when() requires an argument which has to be 'a method call on a mock'. (0) | 2024.08.23 |
---|---|
@WebMvcTest EntityManagerFactory 빈 주입 이슈 - A component required a bean named 'entityManagerFactory' that could not be found. (0) | 2024.08.22 |
코틀린 코딩 컨벤션 (3) | 2023.12.18 |
코틀린의 자료형과 자바의 자료형 (1) | 2023.12.07 |
코틀린의 생성자 (0) | 2023.12.05 |