최근 "Thinking in Java"를 읽다가 다음 구절을 보았습니다.
클래스의 필드인 프리미티브는 자동으로 0으로 초기화됩니다. 모든 것은 객체이다 장. 그러나 객체 참조는 null로 초기화되며, 이들 중 하나에 대해 메서드를 호출하려고 하면 런타임 오류가 발생합니다. 편리하게도 null 참조를 발생시키지 않고 인쇄할 수 있습니다. 예외.
주요 아이디어는 기본 유형이 자동으로 0으로 초기화되지만 개체 참조는 null로 초기화된다는 것입니다. 개체의 메서드를 호출하려고 하면 null 포인터 예외가 발생합니다. 일반적으로 예외를 발생시키지 않고 null 개체를 인쇄할 수 있습니다.
첫 번째 문장은 누구나 쉽게 이해할 수 있을 거라 믿습니다. 이는 유형 초기화에 대한 기본 지식이지만 두 번째 문장은 나를 매우 혼란스럽게 만듭니다. null 객체를 인쇄하면 왜 예외가 발생하지 않습니까? 이 질문을 염두에 두고 나는 이해의 여정을 시작했습니다. 아래에서는 이 문제를 해결하기 위한 내 아이디어를 자세히 설명하고 JDK 소스 코드를 분석하여 문제에 대한 답을 찾을 것입니다.
이 문제에는 실제로 여러 가지 상황이 있음을 알 수 있으므로, 카테고리별로 다양한 상황을 논의하여 답을 얻을 수 있는지 알아보겠습니다. 끝.
우선 이 문제를 세 가지 작은 문제로 나누어 하나씩 해결해 나가겠습니다.
null String 객체를 직접 인쇄하면 어떤 결과가 나오나요?
String s = null; System.out.print(s);
의 결과는
null
가 책에서 말하는 것처럼 예외를 발생시키지 않고 null
를 인쇄한다는 것입니다. 분명히 문제의 단서는 print
함수의 소스 코드에 있습니다. print
의 소스 코드를 찾았습니다:
public void print(String s) { if (s == null) { s = "null"; } write(s); }
소스 코드를 봤을 때 단순한 판단일 뿐이라는 것을 깨달았습니다. 단순한 구현에 조금 실망하셨을 수도 있습니다. JDK. 걱정하지 마십시오. 첫 번째 질문은 단지 전채요리일 뿐이며 잔치는 아직 오지 않았습니다.
Integer와 같은 null이 아닌 문자열 개체를 인쇄합니다.
Integer i = null; System.out.print(i);
작업 결과는 놀랍지 않습니다.
null
print
의 소스코드를 살펴보겠습니다.
public void print(Object obj) { write(String.valueOf(obj)); }
는 조금 다른 것 같습니다. valueOf
에 그 비밀이 숨겨져 있는 것 같습니다.
public static String valueOf(Object obj) { return (obj == null) ? "null" : obj.toString(); }
이것을 보고 마침내 예외를 발생시키지 않고 null 개체를 인쇄하는 비결을 발견했습니다. print
메서드는 String 개체와 String이 아닌 개체를 별도로 처리합니다.
문자열 개체 : null인지 직접 확인합니다. null인 경우 해당 값을 "null"
으로 할당합니다.
문자열이 아닌 객체 : String.valueOf
메서드를 호출하여 null 객체인 경우 "null"
를 반환하고, 그렇지 않으면 객체의 toString
를 호출합니다. 방법.
위의 처리를 통해 null 개체 인쇄 시 오류가 발생하지 않음을 보장할 수 있습니다.
여기서 이 글을 마무리해야 합니다.
뭐? 약속한 저녁은 어디 있지? 치아 사이에 들어갈 정도로 충분하지 않습니다.
농담이에요. 아래의 세 번째 질문을 살펴보겠습니다.
Null 객체와 문자열을 연결하면 어떤 결과가 나올까요?
String s = null; s = s + "!"; System.out.print(s);
결과를 추측하셨을 수도 있습니다.
null!
왜요? 코드 실행을 추적해 보면 이번에는 print
과 아무런 관련이 없음을 알 수 있습니다. 그런데 위의 코드는 print
함수를 호출하는데, 또 누구일 수 있을까요? +
가 가장 의심스럽습니다. 그런데 +
는 함수가 아닙니다. 소스 코드를 어떻게 볼 수 있나요? 이 경우 유일한 설명은 컴파일러가 이를 변조했다는 것입니다. 컴파일러가 생성한 바이트코드를 보면 소스 코드를 찾을 수 없습니다.
L0 LINENUMBER 27 L0 ACONST_NULL ASTORE 1 L1 LINENUMBER 28 L1 NEW java/lang/StringBuilder DUP INVOKESPECIAL java/lang/StringBuilder.<init> ()V ALOAD 1 INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder; LDC "!" INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder; INVOKEVIRTUAL java/lang/StringBuilder.toString ()Ljava/lang/String; ASTORE 1 L2 LINENUMBER 29 L2 GETSTATIC java/lang/System.out : Ljava/io/PrintStream; ALOAD 1 INVOKEVIRTUAL java/io/PrintStream.print (Ljava/lang/String;)V
위 바이트코드를 읽고 혼란스러우셨나요? 여기서는 주제를 바꿔서 +
스트링 스플라이싱의 원리에 대해 이야기해보겠습니다.
컴파일러는 먼저 StringBuilder
을 인스턴스화한 다음 추가된 문자열을 append
순서로 배치하고 마지막으로 toString
를 호출하여 String
개체를 반환합니다. 믿을 수 없다면 위의 바이트코드를 보고 StringBuilder
이 나타나는지 확인해 보세요. 자세한 설명은 Java 세부 정보: 문자열 접합 문서를 참조하세요.
String s = "a" + "b"; //等价于 StringBuilder sb = new StringBuilder(); sb.append("a"); sb.append("b"); String s = sb.toString();
문제로 돌아가서, 이제 우리는 비밀이 StringBuilder.append
함수의 소스 코드에 있다는 것을 알았습니다.
//针对 String 对象 public AbstractStringBuilder append(String str) { if (str == null) return appendNull(); int len = str.length(); ensureCapacityInternal(count + len); str.getChars(0, len, value, count); count += len; return this; } //针对非 String 对象 public AbstractStringBuilder append(Object obj) { return append(String.valueOf(obj)); } private AbstractStringBuilder appendNull() { int c = count; ensureCapacityInternal(c + 4); final char[] value = this.value; value[c++] = 'n'; value[c++] = 'u'; value[c++] = 'l'; value[c++] = 'l'; count = c; return this; }
이제 append
함수가 개체가 null이라고 판단하면 appendNull
를 호출하고 "null"
을 채울 것이라는 사실을 갑자기 깨달았습니다.
위에서 Java에서 String null 개체의 내결함성 처리로 이어지는 세 가지 문제를 논의했습니다. 위의 예는 모든 처리 상황을 다루지는 않으며 소개로 간주됩니다.
프로그램에서 null 개체를 어떻게 제어할 수 있는지는 프로그래밍할 때 항상 주의해야 할 사항입니다.
위 내용은 Java String의 null 객체에 대한 내결함성 처리 요약의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!