KB 11 동등성 비교 — == 와 === 가 자바와 뒤바뀌었다
고친 사람 github-actions[bot]
0. 첫 예제 — 코틀린의 a == b 는 자바의 a.equals(b) 다
자바 개발자라면 「객체는 == 로 비교하지 마라」를 한 번쯤 버그로 배웠습니다. 코틀린에서는 거꾸로 객체를 == 로 비교하는 것이 기본입니다.
같은 숫자를 담은 BigInteger 두 개를 만들어 비교했습니다.
import java.math.BigInteger
fun main() {
val a = BigInteger("12345678901234567890")
val b = BigInteger("12345678901234567890")
println(a == b) // true
println(a === b) // false
}
fun main()— 프로그램 시작 함수입니다.fun은 함수를 선언하는 키워드입니다. 자바와 달리 클래스로 감싸지 않습니다val— 다시 대입할 수 없는 변수입니다. 타입을 안 적으면 값에서 알아냅니다BigInteger("...")— 생성자 호출입니다. 코틀린에는new가 없습니다. 두 줄이 객체를 하나씩 만듭니다===— 등호가 셋인 연산자입니다. 자바에는 없습니다. 무엇을 하는지는 출력으로 봅니다
Big.kt 로 저장해 코틀린 컴파일러 kotlinc 로 컴파일하고 돌렸습니다. -d out 은 결과물을 out 폴더에 쓰라는 옵션입니다. 실행에 쓴 kotlin 은 코틀린 표준 라이브러리를 classpath 에 스스로 넣어 주는 명령이고, 뒤에 적은 BigKt 는 컴파일러가 클래스 밖에 쓴 함수를 담으려고 만드는 클래스입니다. JVM(자바 가상 머신)에서는 모든 메서드가 어떤 클래스에 속해야 해서 파일 이름 뒤에 Kt 를 붙인 클래스가 생깁니다.
$ kotlinc Big.kt -d out
$ kotlin -cp out BigKt
a == b 는 true 입니다. 객체가 둘인데도 그렇습니다. 등호 셋인 a === b 는 false 입니다.
같은 일을 자바로 옮겼습니다. 자바 쪽 결과물은 섞이지 않게 jout 폴더에 둡니다.
import java.math.BigInteger;
public class Big {
public static void main(String[] args) {
BigInteger a = new BigInteger("12345678901234567890");
BigInteger b = new BigInteger("12345678901234567890");
System.out.println(a == b);
System.out.println(a.equals(b));
}
}
$ javac -d jout Big.java
$ java -cp jout Big
false
true
자바의 a == b 는 false 입니다. true 를 얻으려면 a.equals(b) 를 불러야 합니다. 두 출력을 줄 맞춰 놓으면 연산자가 뒤바뀐 것이 보입니다.
| 묻는 것 | 자바 | 코틀린 |
|---|---|---|
| 내용이 같은가 | a.equals(b) |
a == b |
| 같은 객체인가 | a == b |
a === b |
코틀린 공식 문서는 == 를 구조적 동등성(structural equality)이라 부릅니다(코틀린 문서 「Equality」). equals() 로 내용을 견주는 비교입니다.
=== 는 참조 동등성(referential equality)입니다. 두 변수가 같은 객체를 가리키는지 보는 비교, 곧 자바의 == 입니다. 이 편에서는 줄여서 참조 비교라고 부릅니다.
이 편은 이 표 한 장이 JVM 위에서 무엇이 되는지 풉니다. 널이 끼면 어떻게 되는지, 그리고 조심할 두 곳(Int? 의 === 와 배열의 ==)도 봅니다. 출력은 전부 Kotlin 2.4.10, JDK 21 에서 직접 돌린 결과입니다.
1. == 는 equals 를 부르고 === 는 부르지 않는다
「== 가 equals 에 해당한다」가 비유인지, equals 를 부르는 호출인지 확인합니다. equals 를 직접 정의하고 그 안에 출력 한 줄을 심었습니다.
class Money(val amount: Int) {
override fun equals(other: Any?): Boolean {
println("equals 호출")
return other is Money && other.amount == amount
}
}
fun main() {
val x = Money(100)
val y = Money(100)
println(x == y)
println(x != y)
println(x === y)
}
Money.kt 로 저장해 돌렸습니다.
$ kotlinc Money.kt -d out
$ kotlin -cp out MoneyKt
equals 호출
true
equals 호출
false
false
class Money(val amount: Int)— 클래스 이름 뒤 괄호가 생성자입니다. 괄호 안에val을 붙인 인자는 필드와 게터를 함께 갖는 프로퍼티가 됩니다override fun equals(other: Any?): Boolean— 부모의 메서드를 다시 정의할 때는override를 반드시 적습니다.Any는 자바의Object에 해당합니다. 타입 뒤?는null도 받는다는 표시입니다other is Money— 자바의instanceof입니다. 이 검사를 통과한 뒤에는 캐스트 없이other.amount를 읽습니다!=—==의 부정입니다
equals 호출 이 두 번 찍혔습니다. x == y 와 x != y 가 한 번씩 우리가 쓴 equals 를 불렀습니다. 마지막 x === y 는 equals 를 부르지 않고 false 를 냈습니다.
그러니 코틀린의 == 는 비유가 아니라 equals 를 부르는 호출입니다. 부정인 != 도 같은 equals 를 부르고 결과만 뒤집습니다. === 는 equals 를 거치지 않고 같은 객체인지만 봅니다.
우리가 쓴 equals 가 자바의 Object.equals 를 재정의한 것도 확인했습니다. JDK 에 딸린 javap 는 클래스 파일에 적힌 선언의 모양을 보여 주는 도구이고, -p 는 private 멤버까지 보여 달라는 옵션입니다. javap -p out/Money.class 로 보면 우리가 쓴 equals 는 public boolean equals(java.lang.Object); 입니다. Any? 인자가 Object 가 되어 Object.equals 를 재정의합니다.
타입이 안 맞으면 컴파일이 멈춘다
자바의 equals 는 Object 를 받으니 무엇과도 비교가 컴파일됩니다. 코틀린의 == 는 여기서 한 번 더 따집니다. Int 1 과 Long 1 을 비교했습니다.
fun main() {
val i = 1
val l = 1L
println(i == l) // 컴파일 오류
}
$ kotlinc Mixed.kt -d out
Mixed.kt:4:13: error: operator '==' cannot be applied to 'Int' and 'Long'.
println(i == l)
^^^^^^
같은 비교를 자바로 쓰면 컴파일이 되고, 실행하면 false 가 나옵니다.
public class Mixed {
public static void main(String[] args) {
Integer i = 1;
Long l = 1L;
System.out.println(i.equals(l));
}
}
$ javac -d jout Mixed.java
$ java -cp jout Mixed
false
자바에서는 조용히 false 로 끝나는 비교를 코틀린은 컴파일 단계에서 막았습니다.
막는 범위는 좁습니다. 네 경우를 돌려 보면 두 갈래로 갈립니다.
| 비교한 두 쪽 | 결과 |
|---|---|
서로 관계없는 두 클래스 — Int 와 Long · Money 와 String |
컴파일이 막힌다 |
한쪽이 인터페이스나 Any — Money 와 Shape? · val a: Any = 1 과 1L |
통과하고 false |
Shape 는 Money 가 구현하지 않은 인터페이스입니다. 한쪽이 인터페이스나 Any 이면 컴파일러가 못 막습니다.
2. 널에 안전한 ==
자바에서 a.equals(b) 는 a 가 null 이면 NullPointerException 을 냅니다. 코틀린의 == 는 null 을 넣어도 멈추지 않습니다. Money? 인자 둘에 null 을 번갈아 넣어 봅니다.
1절과 같은 Money 클래스를 두고, 함수 하나를 더 썼습니다.
fun check(m: Money?, n: Money?) {
println(m == n)
println(m == null)
}
fun main() {
check(null, Money(100))
check(Money(100), null)
}
Money 와 check 를 함께 담은 Nulls.kt 를 돌렸습니다.
$ kotlinc Nulls.kt -d out
$ kotlin -cp out NullsKt
false
true
equals 호출
false
false
m: Money?— 인자 이름 뒤에 콜론, 그 뒤에 타입을 씁니다.?가 붙어서null을 넘길 수 있습니다fun check(...)—{ }로 본문을 쓴 함수에서 반환 타입을 안 적으면 돌려주는 값이 없는 함수, 곧 자바의void입니다
두 번의 호출이 다르게 끝났습니다.
- 왼쪽이
null— 첫 호출은equals 호출없이false와true를 찍었습니다.null에는equals를 부를 수 없으니 부르지 않고 답을 냈습니다 - 오른쪽이
null— 두 번째 호출은equals 호출을 찍었습니다. 왼쪽 객체의equals에null이 인자로 넘어갔습니다.other is Money가false라서false가 나왔습니다
그러니 equals 를 직접 쓸 때는 인자로 null 이 들어오는 경우를 자바와 똑같이 처리해야 합니다. other is Money 는 null 에 대해 false 이므로 그 처리를 겸합니다.
출력 다섯 줄이 말하는 규칙은 하나입니다. 왼쪽이 null 이면 오른쪽도 null 인지만 보고, 아니면 왼쪽의 equals 를 부릅니다. 둘 다 null 인 갈래도 첫 호출에 들어 있습니다. m 이 null 인 채로 m == null 을 물은 줄이 equals 없이 true 를 냈습니다.
이 규칙을 갈래로 그리면 이렇습니다.
flowchart TD
A["m == n"] --> B{"m 이 null 인가"}
B -->|예| C{"n 이 null 인가"}
C -->|예| T["true"]
C -->|아니오| F["false"]
B -->|아니오| E["m.equals(n) 의 결과"]
자바에서 같은 비교
자바는 같은 일에 java.util.Objects.equals 를 따로 씁니다. 자바 쪽은 짧게 문자열로 보입니다. null 을 담은 static 필드 m 으로 두 방법을 차례로 불렀습니다.
import java.util.Objects;
public class Nulls {
static String m = null;
public static void main(String[] args) {
System.out.println(Objects.equals(m, "kotlin"));
System.out.println(m.equals("kotlin"));
}
}
$ javac -d jout Nulls.java
$ java -cp jout Nulls
false
Exception in thread "main" java.lang.NullPointerException: Cannot invoke "String.equals(Object)" because "Nulls.m" is null
at Nulls.main(Nulls.java:8)
Objects.equals 는 false 를 냈습니다. m.equals(...) 는 실행 중에 NullPointerException 으로 멈췄습니다. 코틀린의 == 는 자바의 Objects.equals 와 거의 같은 일을 합니다.
다른 곳이 하나 있습니다. 두 인자가 같은 객체이면 Objects.equals 는 equals 를 부르지 않고 true 를 냅니다. 코틀린에서 1절의 Money 로 x == x 를 비교하면 위의 규칙대로 equals 를 불러 equals 호출 을 찍고 true 가 나왔습니다.
코틀린에서 자바 버릇대로 m.equals(...) 를 쓰면 어떨까요. 타입이 Any? 나 Money? 이면 실행까지 가지 못합니다. val m: Any? = null 에 m.equals("kotlin") 을 부른 NullCall.kt 는 error: only safe (?.) or non-null asserted (!!.) calls are allowed on a nullable receiver of type 'Any?'. 로 컴파일이 막혔습니다.
에러가 권하는 둘은 이렇습니다.
?.— 안전 호출입니다. 받는 쪽이null이면 메서드를 부르지 않고null을 냅니다!!.— 널 아님 단언입니다.null이 아니라고 못박고 부르며, 그래도null이면 예외가 납니다
둘 다 널 안전성을 다루는 뒤에 나올 편에서 봅니다.
String? 은 예외입니다. 표준 라이브러리가 널을 받는 equals 를 따로 두었습니다. 그래서 String? 인자 m 에 null 을 넣고 m.equals("kotlin") 을 부르면 컴파일되고 false 가 나옵니다.
3. Integer 캐시 함정 — Int? 둘을 === 로 비교하면
=== 는 같은 객체인지 보는 비교라고 했습니다. 그런데 Int? 에 담은 숫자끼리 === 로 견주면 값이 같아도 결과가 갈립니다. 자주 쓰는 작은 Integer 객체를 미리 만들어 두고 다시 내주는 장치, 곧 Integer 캐시 때문입니다. 어떻게 걸리는지 차례로 봅니다.
이 함정을 이해하려면 먼저 코틀린의 Int 가 JVM 에서 무엇이 되는지 알아야 합니다. 코틀린에는 자바의 int 와 Integer 가 따로 없고 Int 하나뿐입니다. 컴파일러는 이 Int 를 대개 int 로 컴파일합니다.
Int? 는 다릅니다. null 을 받는 Int? 는 박싱된 java.lang.Integer 가 됩니다. int 같은 값을 Integer 객체로 감싸는 것을 박싱(boxing)이라고 부릅니다.
fun main() {
val a: Int? = 127
val b: Int? = 127
val c: Int? = 128
val d: Int? = 128
println(a === b) // true
println(c === d) // false
println(c == d) // true
}
$ kotlinc Cache.kt -d out
Cache.kt:6:13: warning: identity equality for arguments of types 'Int?' and 'Int?' is prohibited.
println(a === b)
^^^^^^^
Cache.kt:7:13: warning: identity equality for arguments of types 'Int?' and 'Int?' is prohibited.
println(c === d)
^^^^^^^
$ kotlin -cp out CacheKt
val a: Int? = 127— 변수 이름 뒤에 콜론과 타입을 적었습니다. 타입을Int?로 못박아Integer가 되게 했습니다
볼 줄은 둘입니다. 첫째, 컴파일러가 === 두 줄에 경고를 냈습니다. 경고 문구는 Int? 끼리의 참조 비교(identity equality)를 금지(prohibited)한다고 적습니다. 그래도 에러가 아니라 경고라서 컴파일은 끝났습니다.
둘째, 실행 결과입니다. 127 끼리는 true, 128 끼리는 false 입니다. 내용 비교인 c == d 는 true 입니다.
127 과 128 을 가르는 것
값을 감싸는 일은 소스에 안 적혀 있으니 컴파일된 클래스 파일을 열어 봅니다. CFR 은 클래스 파일을 거꾸로 읽어 자바 코드로 다시 적는 디컴파일러이고, jar 하나로 된 도구라 java -jar cfr.jar 클래스파일 로 돌립니다. --sugarboxing false 는 박싱 호출을 감추지 말라는 옵션입니다. 이 옵션이 없으면 CFR 은 Integer a = 127; 처럼 호출을 감춘 모양으로 보여 줍니다. 결과 맨 위의 머리 주석과 import, 한 줄이 아주 긴 @Metadata(...)(코틀린 컴파일러가 남기는 부가 정보)는 ... 로 줄여 싣습니다.
$ java -jar cfr.jar --sugarboxing false out/CacheKt.class
...
Integer a = Integer.valueOf((int)127);
Integer b = Integer.valueOf((int)127);
Integer c = Integer.valueOf((int)128);
Integer d = Integer.valueOf((int)128);
boolean bl = a == b;
...
bl = c == d;
...
bl = Intrinsics.areEqual((Object)c, (Object)d);
...
Int? 변수에 넣은 숫자는 전부 Integer.valueOf 로 감싸졌습니다. 그리고 연산자 둘이 서로 다른 모양으로 내려갔습니다. === 는 자바의 == 이고, == 는 Intrinsics.areEqual 이라는 메서드 호출입니다. Intrinsics 는 컴파일러가 만든 코드가 부르는 도우미를 모아 둔 코틀린 표준 라이브러리 클래스입니다. 1절에서 본 equals 호출 은 이 도우미를 거쳐 나온 것입니다.
(int) 캐스트와 변수 이름 bl 은 소스에 없는, CFR 이 타입과 이름을 드러내려고 붙인 표시입니다.
답은 Integer.valueOf 에 있습니다. 이 메서드는 -128~127 을 늘 캐시합니다. Javadoc 원문은 「This method will always cache values in the range -128 to 127, inclusive」입니다(자바 21 Javadoc 의 Integer.valueOf 항목).
-128 부터 127 까지는 valueOf 가 미리 만들어 둔 Integer 객체를 돌려줍니다. 그래서 a 와 b 는 같은 객체이고 === 가 true 입니다. 기본 설정에서 128 은 부를 때마다 새 객체라서 c 와 d 는 다른 객체입니다.
자바의 Integer 도 똑같이 걸립니다. 같은 네 변수를 Integer 로 선언해 == 와 equals 로 비교했습니다.
public class Cache {
public static void main(String[] args) {
Integer a = 127;
Integer b = 127;
Integer c = 128;
Integer d = 128;
System.out.println(a == b);
System.out.println(c == d);
System.out.println(c.equals(d));
}
}
$ javac -d jout Cache.java
$ java -cp jout Cache
true
false
true
같은 결과입니다. -XX:AutoBoxCacheMax 는 캐시의 위 끝을 바꾸는 JVM 옵션입니다. 같은 클래스를 java -XX:AutoBoxCacheMax=1000 -cp jout Cache 로 돌리자 세 줄 모두 true 가 나왔습니다. 캐시의 위 끝인 127 은 이렇게 실행 옵션에 따라 움직입니다.
그러니 박싱된 숫자의 === 결과에 기대면 안 됩니다.
? 없는 Int 둘은 128 이어도 === 가 true 입니다. int 로 컴파일돼 감쌀 객체가 없기 때문입니다(코틀린 문서 「Equality」). val a: Int = 128 둘을 비교한 Plain.kt 가 true 를 찍었습니다.
이때 나온 경고 문구는 identity equality for arguments of types 'Int' and 'Int' is deprecated. 입니다. deprecated 는 아직 허용하지만 쓰지 말라는 표시로, Int? 의 prohibited 보다 한 단계 약합니다.
숫자는 == 로 비교합니다. Int? 의 === 는 자바 Integer 의 == 와 같은 함정이고, 코틀린 컴파일러는 그 줄에 경고를 냅니다.
4. 문자열은 == 로 비교해도 된다 — 자바의 흔한 버그가 사라졌다
자바에서 if (answer == "yes") 는 흔한 버그입니다. 고약한 점은 테스트에서는 통과하는 때가 많다는 것입니다. 소스에 직접 쓴 문자열과 실행 중에 이어 붙인 문자열을 나란히 비교해 봅니다.
먼저 자바입니다. lit 는 문자열 리터럴입니다. built 는 변수 head 에 "lin" 을 실행 중에 이어 붙인 문자열입니다.
public class Str {
public static void main(String[] args) {
String lit = "kotlin";
String head = "kot";
String built = head + "lin";
System.out.println(lit == "kotlin");
System.out.println(built == "kotlin");
System.out.println(built.equals("kotlin"));
}
}
$ javac -d jout Str.java
$ java -cp jout Str
true
false
true
내용이 같은 "kotlin" 인데 lit == "kotlin" 은 true, built == "kotlin" 은 false 입니다.
갈린 이유는 객체가 몇 개인가입니다. 같은 내용의 문자열 리터럴은 JVM 안에서 한 객체를 함께 씁니다(자바 언어 명세 §3.10.5). 그래서 lit 와 "kotlin" 은 같은 객체입니다. built 는 실행 중에 새로 만든 객체라 다른 객체입니다.
리터럴로 짠 테스트에서는 == 가 true 로 통과합니다. 사용자 입력처럼 실행 중에 만들어진 문자열이 들어오면 false 가 됩니다. 이것이 자바의 문자열 == 버그가 늦게 발견되는 이유입니다.
같은 코드를 코틀린으로 옮겼습니다.
fun main() {
val lit = "kotlin"
val head = "kot"
val built = head + "lin"
println(lit == "kotlin") // true
println(built == "kotlin") // true
println(built === "kotlin") // false
}
$ kotlinc Str.kt -d out
$ kotlin -cp out StrKt
코틀린의 built == "kotlin" 은 true 입니다. 코틀린에서는 == 가 곧 내용 비교이니, 자바 버릇대로 == 를 써도 버그가 되지 않습니다. 객체가 다르다는 사실은 built === "kotlin" 의 false 에만 남았습니다.
문자열이라고 규칙이 따로 있는 것은 아닙니다. built == "kotlin" 은 1절의 Money 와 똑같이 equals 를 부르는 내용 비교이고, built === "kotlin" 은 같은 객체인지만 보는 참조 비교입니다. 문자열끼리의 === 에는 3절 같은 경고가 없었습니다.
코틀린에서 문자열은 == 로 비교하면 됩니다.
5. 배열의 == 는 참조 비교다 — contentEquals
지금까지는 == 가 내용을 비교했습니다. 그런데 배열에 쓰면 내용이 같아도 false 가 나옵니다. 같은 원소를 담은 배열 둘과 리스트 둘을 == 로 비교해 봅니다.
fun main() {
val a = arrayOf(1, 2, 3)
val b = arrayOf(1, 2, 3)
println(a == b) // false
println(a.contentEquals(b)) // true
// true
println(listOf(1, 2, 3) == listOf(1, 2, 3))
}
$ kotlinc Arr.kt -d out
Arr.kt:4:15: warning: '==' on arrays compares only references. Replace '==' with 'contentEquals' to compare the arrays' contents or use `===` to remove the warning.
println(a == b)
^^
$ kotlin -cp out ArrKt
arrayOf(1, 2, 3)— 원소를 받아 배열을 만드는 표준 라이브러리 함수입니다. 자바의Integer[]가 됩니다a.contentEquals(b)— 두 배열의 원소를 순서대로 견주는 함수입니다listOf(1, 2, 3)— 원소를 받아 읽기 전용 리스트를 만드는 함수입니다. JVM 에서는java.util.List입니다
컴파일러가 먼저 경고했습니다. 배열의 == 는 참조만 비교하니 contentEquals 로 바꾸라는 문구입니다. 출력도 그대로 a == b 는 false, contentEquals 는 true 입니다. 리스트끼리의 == 는 true 입니다.
배열에만 다른 규칙이 붙은 것은 아닙니다. == 는 배열에서도 equals 를 부릅니다. 달라진 것은 어느 equals 가 불렸는가입니다. 자바 배열은 equals 를 재정의하지 않아 Object.equals, 곧 참조 비교가 불립니다. java.util.List 의 equals 는 원소를 순서대로 견주도록 정의돼 있어 리스트는 true 가 나옵니다.
자바에서도 배열의 equals 는 같은 함정입니다.
import java.util.Arrays;
public class Arr {
public static void main(String[] args) {
Integer[] a = {1, 2, 3};
Integer[] b = {1, 2, 3};
System.out.println(a.equals(b));
System.out.println(Arrays.equals(a, b));
}
}
$ javac -d jout Arr.java
$ java -cp jout Arr
false
true
코틀린과 같은 결과입니다. 자바의 Arrays.equals 가 코틀린의 contentEquals 입니다.
배열 안에 배열이 들면 한 번 더 걸립니다. arrayOf(arrayOf(1, 2)) 로 만든 a 와 b 를 비교한 Deep.kt 에서 a.contentEquals(b) 는 false 였습니다. 원소인 안쪽 배열끼리를 다시 equals, 곧 참조로 견주기 때문입니다.
안쪽까지 내려가 견주는 a.contentDeepEquals(b) 는 true 였습니다. 자바에서 Integer[][] 둘을 견준 Deep.java 도 Arrays.equals 는 false, Arrays.deepEquals 는 true 를 찍었습니다.
배열은 == 대신 contentEquals 로 비교합니다. 내용 비교가 필요하면 배열보다 리스트를 쓰는 편이 == 를 그대로 쓸 수 있어 편합니다.
6. 한 장 요약
코틀린의
==는null을 먼저 가리고equals를 부르는 내용 비교다.===가 자바의==인 참조 비교다. 그래서 자바의 문자열==버그는 사라진다. 대신equals가 참조 비교인 배열은contentEquals를 써야 한다.Integer로 박싱되는Int?의===는 127 과 128 에서 결과가 갈린다.
| 하는 일 | 자바 | 코틀린 | 확인한 방법 |
|---|---|---|---|
| 내용이 같은가 | a.equals(b) |
a == b |
실행(equals 호출) |
| 내용이 다른가 | !a.equals(b) |
a != b |
실행(equals 호출) |
| 같은 객체인가 | a == b |
a === b → 자바 a == b |
실행 · CFR |
왼쪽이 null 일 수 있을 때 |
Objects.equals(a, b) (a.equals(b) 는 NPE) |
a == b 그대로 |
실행(세 갈래) |
| 관계없는 두 클래스 | Integer.equals(Long) 컴파일 ✓, false |
Int == Long 컴파일 ✗ |
kotlinc 에러 · 실행 |
| 박싱된 정수의 참조 비교 | Integer 127 → true · 128 → false |
Int? 127 → true · 128 → false, 경고 |
실행 · Integer.valueOf |
| 문자열 내용 | s.equals("kotlin") |
s == "kotlin" |
리터럴 vs 이어 붙인 문자열 실행 |
| 배열 내용 | Arrays.equals(a, b) |
a.contentEquals(b) |
경고 · 실행(코틀린·자바) |
| 중첩 배열 내용 | Arrays.deepEquals(a, b) |
a.contentDeepEquals(b) |
실행(코틀린·자바) |
관련 항목
코틀린 동등성 비교를 이루는 연산자와 함수
구조적 동등성 · 참조 동등성 · equals · hashCode · contentEquals · contentDeepEquals
동등성 비교가 컴파일되어 바뀌는 JVM 코드와 자바 대응
Intrinsics · Objects.equals · Arrays.equals · Arrays.deepEquals · Object.equals
박싱된 정수의 참조 비교를 흔드는 요인
박싱 · Integer 캐시 · Integer.valueOf · AutoBoxCacheMax · 래퍼 클래스
문자열 == 버그를 만드는 문자열 객체의 성질
문자열 리터럴 · 문자열 인터닝 · 문자열 연결
널을 다루는 코틀린 문법 요소와 자바 예외
널 안전성 · 널 허용 타입 · 안전 호출 연산자 · 널 아님 단언 연산자 · NullPointerException · 스마트 캐스트
이 편의 코드를 이루는 코틀린 문법 요소
주 생성자 · 프로퍼티 · override · Any · is 연산자 · val
내용 비교 방식이 갈리는 배열과 컬렉션
배열 · arrayOf · listOf · 컬렉션 · List
컴파일 결과를 열어 보는 도구
javap · 디컴파일 · 디컴파일러 · CFR · 클래스 파일