부울, 조건부 연산자 및 오토 박싱


132

왜 이런 일이 발생합니까? NullPointerException

public static void main(String[] args) throws Exception {
    Boolean b = true ? returnsNull() : false; // NPE on this line.
    System.out.println(b);
}

public static Boolean returnsNull() {
    return null;
}

이 동안하지 않습니다

public static void main(String[] args) throws Exception {
    Boolean b = true ? null : false;
    System.out.println(b); // null
}

?

용액을 대체하는 방식으로 인 false으로 Boolean.FALSE방지하기 null에 박싱되는 boolean불가능 실현합니다. 그러나 그것은 질문이 아닙니다. 문제는 ? JLS에이 동작, 특히 두 번째 경우를 확인하는 참조가 있습니까?


28
와우, 오토 박싱은 끝없는 소스입니다 ... 어 ... 자바 프로그래머에게는 놀라지 않습니까? :-)
leonbloy

비슷한 문제가 있었고 놀랍게도 OpenJDK VM에서는 실패했지만 HotSpot VM에서 작동했습니다 ... 한 번 쓰고 어디서나 실행하십시오!
kodu

답변:


92

차이점은 명시 적 유형의 returnsNull()메소드가 컴파일시 표현식의 정적 타이핑에 영향을 준다는 것입니다.

E1: `true ? returnsNull() : false` - boolean (auto-unboxing 2nd operand to boolean)

E2: `true ? null : false` - Boolean (autoboxing of 3rd operand to Boolean)

Java 언어 사양, 15.25 절을 참조하십시오. 조건부 연산자? :

  • E1의 경우, 두 번째 및 세 번째 피연산자의 유형은 각각 Booleanboolean각각 이므로이 절이 적용됩니다.

    두 번째 및 세 번째 피연산자 중 하나가 부울 유형이고 다른 유형의 부울이 부울 유형 인 경우 조건식의 유형은 부울입니다.

    식의 유형이이므로 boolean두 번째 피연산자는로 강제 변환되어야합니다 boolean. 컴파일러는 자동 unboxing 코드를 두 번째 피연산자 (return value of returnsNull())에 삽입하여 type을 만듭니다 boolean. 이것은 물론 NPE를 null런타임에 반환합니다.

  • E2의 경우, 두 번째 및 세 번째 피연산자의 유형은 각각 ( E1과 <special null type>같지 않음 Boolean), boolean특정 타이핑 절이 적용되지 않으며 ( 'em!로 이동 ), 마지막 "otherwise"절이 적용됩니다.

    그렇지 않으면, 두 번째 및 세 번째 피연산자는 각각 S1 및 S2 유형입니다. T1을 복싱 변환을 S1에 적용한 결과 유형으로, T2를 복싱 변환을 S2에 적용한 결과 유형으로 설정합니다. 조건식의 유형은 캡처 변환 (§5.1.10)을 lub (T1, T2) (§15.12.2.7)에 적용한 결과입니다.

    • S1 == <special null type>( §4.1 참조 )
    • S2 == boolean
    • T1 == box (S1) == <special null type>( §5.1.7 의 권투 변환 목록에서 마지막 항목 참조 )
    • T2 == 상자 (S2) ==`부울
    • 윤활유 (T1, T2) == Boolean

    따라서 조건식의 유형은 Boolean세 번째 피연산자로 강제 변환되어야합니다 Boolean. 컴파일러는 3 번째 피연산자 ( false)에 대한 자동 복싱 코드를 삽입합니다 . 두 번째 피연산자는에서 E1와 같이 자동 Unboxing이 필요하지 않으므로 null반환 될 때 자동 Unboxing NPE가 없습니다 .


이 질문에는 비슷한 유형 분석이 필요합니다.

Java 조건부 연산자? : 결과 유형


4
말이되는 것 같아요. §15.12.2.7는 고통이다.
BalusC

쉽지만 ... 후시에서만. :-)
Bert F

@BertF 어떤 기능을 수행 lublub(T1,T2)대한 독립?
Geek

1
@Geek-lub ()-최소 상한-기본적으로 가장 가까운 수퍼 클래스; 널 ( "특별 널 유형") 유형을 내재적으로 임의의 유형으로 변환 (확장) 할 수 있으므로 특수 널 유형을 lub ()의 목적에 따라 모든 유형 (클래스)의 "수퍼 클래스"로 간주 할 수 있습니다.
Bert F

25

라인 :

    Boolean b = true ? returnsNull() : false;

내부적으로 다음과 같이 변환됩니다.

    Boolean b = true ? returnsNull().booleanValue() : false; 

언 박싱을 수행하기 위해; 따라서 : null.booleanValue()NPE를 산출합니다

이것은 오토 박싱을 사용할 때의 주요 함정 중 하나입니다. 이 동작은 실제로 5.1.8 JLS에 문서화되어 있습니다.

편집 : unboxing은 (암시 적 캐스트 추가)와 같이 세 번째 연산자가 부울 유형이기 때문에 믿습니다.

   Boolean b = (Boolean) true ? true : false; 

2
궁극적 인 값이 부울 객체 일 때 왜 그런 식으로 개봉을 시도합니까?
Erick Robertson

16

에서 Java 언어 사양, 섹션 15.25 :

  • 두 번째 및 세 번째 피연산자 중 하나가 부울 유형이고 다른 유형의 부울이 부울 유형 인 경우 조건식의 유형은 부울입니다.

그래서 첫 번째 예제의 시도는 호출 Boolean.booleanValue()로 변환하기 위해 Booleanboolean첫 번째 규칙에 따라.

두 번째 경우 첫 번째 피연산자는 null 유형이고 두 번째 피연산자는 참조 유형이 아닌 경우 오토 박싱 변환이 적용됩니다.

  • 그렇지 않으면, 두 번째 및 세 번째 피연산자는 각각 S1 및 S2 유형입니다. T1을 복싱 변환을 S1에 적용한 결과 유형으로, T2를 복싱 변환을 S2에 적용한 결과 유형으로 설정합니다. 조건식의 유형은 캡처 변환 (§5.1.10)을 lub (T1, T2) (§15.12.2.7)에 적용한 결과입니다.

첫 번째 경우에는 대답하지만 두 번째 경우에는 대답하지 않습니다.
BalusC

값 중 하나가 인 경우 예외가있을 수 null있습니다.
Erick Robertson

@Erick : JLS가이를 확인합니까?
BalusC

1
@ 에릭 : boolean참조 유형이 아니기 때문에 적용 할 수 없다고 생각합니다 .
axtavt

1
그리고 내가 추가 할 수 있습니다 ... 그래서 필요한 경우 명시 적 호출로 삼항의 양쪽을 동일한 유형으로 만들어야합니다. 사양을 암기하고 어떤 일이 일어날 지 알더라도 다음 프로그래머가 코드를 읽지 못할 수도 있습니다. 겸손한 견해로는 일반 필사자들이 예측하기 어려운 작업을 수행하는 것보다 컴파일러가 이러한 상황에서 오류 메시지를 생성하는 것이 좋습니다. 글쎄, 아마도 그 행동이 정말로 유용한 경우가 있지만, 아직 안타깝습니다.
Jay

0

바이트 코드에서이 문제를 볼 수 있습니다. main 바이트 코드의 3 번째 줄 3: invokevirtual #3 // Method java/lang/Boolean.booleanValue:()Z에서 boxing Boolean of null 값인 invokevirtual메소드 java.lang.Boolean.booleanValue는 NPE를 발생시킵니다.

    public static void main(java.lang.String[]) throws java.lang.Exception;
      descriptor: ([Ljava/lang/String;)V
      flags: ACC_PUBLIC, ACC_STATIC
      Code:
        stack=2, locals=2, args_size=1
           0: invokestatic  #2                  // Method returnsNull:()Ljava/lang/Boolean;
           3: invokevirtual #3                  // Method java/lang/Boolean.booleanValue:()Z
           6: invokestatic  #4                  // Method java/lang/Boolean.valueOf:(Z)Ljava/lang/Boolean;
           9: astore_1
          10: getstatic     #5                  // Field java/lang/System.out:Ljava/io/PrintStream;
          13: aload_1
          14: invokevirtual #6                  // Method java/io/PrintStream.println:(Ljava/lang/Object;)V
          17: return
        LineNumberTable:
          line 3: 0
          line 4: 10
          line 5: 17
      Exceptions:
        throws java.lang.Exception

    public static java.lang.Boolean returnsNull();
      descriptor: ()Ljava/lang/Boolean;
      flags: ACC_PUBLIC, ACC_STATIC
      Code:
        stack=1, locals=0, args_size=0
           0: aconst_null
           1: areturn
        LineNumberTable:
          line 8: 0
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.