참조 비교와 null 문서의 구현 코드다.
// 실행: docker run --rm -v "$PWD":/w -w /w eclipse-temurin:21-jdk java 08_java-reference-equality_impl.java
// 참조 비교와 null 문서의 출력과 값을 확인한다.
import java.util.Arrays;
class ReferenceEqualityImpl {
static void check(boolean ok, String what) {
if (!ok) throw new AssertionError(what);
}
public static void main(String[] args) {
// 1. 문서의 그림: refVar1 -> 객체1, refVar2와 refVar3 -> 객체2
int[] refVar1 = {1, 2, 3};
int[] refVar2 = {1, 2, 3};
int[] refVar3 = refVar2;
check((refVar1 == refVar2) == false, "내용이 같아도 다른 객체면 == 는 false");
check((refVar1 != refVar2) == true, "!= 는 true");
check((refVar2 == refVar3) == true, "같은 객체면 == 는 true");
check((refVar2 != refVar3) == false, "!= 는 false");
// 내용을 비교하려면 따로 비교 메서드를 쓴다
check(Arrays.equals(refVar1, refVar2), "Arrays.equals는 칸을 하나씩 비교한다");
check(refVar1.equals(refVar2) == false, "배열의 equals는 == 와 같다");
// 2. 기본 타입의 == 는 값을 비교한다
int a = 1000, b = 1000;
check(a == b, "기본 타입은 값이 같으면 true");
// 3. null: 아무 객체도 가리키지 않는다
String refVar = null;
check(refVar == null, "null과는 == 로 비교한다");
int[] arr = null;
check(arr == null, "배열 변수도 null을 담을 수 있다");
String s1 = null, s2 = null;
check(s1 == s2, "null == null 은 true");
// 4. null인 변수로 객체를 쓰려 하면 NullPointerException
boolean npe = false;
String message = "";
try {
int len = refVar.length();
} catch (NullPointerException e) {
npe = true;
message = e.getMessage();
}
check(npe, "null.length()는 NullPointerException");
check(message.contains("String.length()"), "메시지가 어떤 호출인지 알려 준다: " + message);
System.out.println("NPE 메시지 1: " + message);
npe = false;
try {
arr[0] = 10;
} catch (NullPointerException e) {
npe = true;
System.out.println("NPE 메시지 2: " + e.getMessage());
}
check(npe, "null 배열의 칸에 넣으면 NullPointerException");
npe = false;
try {
int n = arr.length;
} catch (NullPointerException e) {
npe = true;
System.out.println("NPE 메시지 3: " + e.getMessage());
}
check(npe, "null 배열의 length도 NullPointerException");
// 5. 막는 법: 쓰기 전에 null인지 본다
int length = (refVar != null) ? refVar.length() : 0;
check(length == 0, "null이면 0");
boolean ok = refVar != null && refVar.length() > 0; // &&는 앞이 false면 뒤를 보지 않는다
check(ok == false, "&&로 막는다");
check("자바".equals(refVar) == false, "리터럴 쪽에서 equals를 부르면 안전하다");
npe = false;
try {
boolean r = refVar.equals("자바");
} catch (NullPointerException e) {
npe = true;
}
check(npe, "null 쪽에서 equals를 부르면 NullPointerException");
// 6. 주소를 넣어 주면 해결된다 (refVar에 "자바"의 주소를 넣는다)
refVar = "자바";
check(refVar.length() == 2, "이제 length()를 부를 수 있다");
// 7. null은 "빈 것"과 다르다
String empty = "";
int[] zero = new int[0];
check(empty != null && empty.length() == 0, "빈 문자열은 객체가 있다");
check(zero != null && zero.length == 0, "길이 0 배열도 객체가 있다");
// 8. 문자열에 null을 이어 붙이면 "null"이라는 글자가 된다
String nothing = null;
check(("값: " + nothing).equals("값: null"), "null + 문자열");
// 9. 카드
int[] x = {5};
int[] y = {5};
int[] z = x;
z[0] = 6;
check((x == y) == false && (x == z) == true && x[0] == 6 && y[0] == 5, "카드 추적");
y = z;
check(x == y, "대입 뒤에는 같은 객체");
System.out.println("ALL CHECKS PASSED");
}
}