Java 8 autoboxing + generics: different behaviour with variable vs. method

前端 未结 4 753
日久生厌
日久生厌 2021-02-07 07:43

I found a piece of code that after switching from Java 7 to Java 8 stopped compiling. It does not feature any of the new Java 8 stuff like lambda or streams.

I narrowed

4条回答
  •  栀梦
    栀梦 (楼主)
    2021-02-07 07:54

    Have to claim this is not an answer, merely a reasoning. With my brief experience in compiler (not Javac specific), it could has something to do with how the code is parsed.

    In the following decompiled code, you see either calling the method GenericData.getData:()Ljava/lang/Object or referring to field GenericData.data:Ljava/lang/Object, they both first get the value/method with Object returned, followed by a cast.

      stack=4, locals=4, args_size=1
         0: new           #2                  // class general/GenericData
         3: dup
         4: dconst_1
         5: invokestatic  #3                  // Method java/lang/Double.valueOf:(D)Ljava/lang/Double;
         8: invokespecial #4                  // Method general/GenericData."":(Ljava/lang/Object;)V
        11: astore_1
        12: aload_1
        13: ifnonnull     23
        16: dconst_0
        17: invokestatic  #3                  // Method java/lang/Double.valueOf:(D)Ljava/lang/Double;
        20: goto          30
        23: aload_1
        24: invokevirtual #5                  // Method general/GenericData.getData:()Ljava/lang/Object;
        27: checkcast     #6                  // class java/lang/Double
        30: astore_2
        31: aload_1
        32: ifnonnull     39
        35: dconst_0
        36: goto          49
        39: aload_1
        40: getfield      #7                  // Field general/GenericData.data:Ljava/lang/Object;
        43: checkcast     #6                  // class java/lang/Double
        46: invokevirtual #8                  // Method java/lang/Double.doubleValue:()D
        49: invokestatic  #3                  // Method java/lang/Double.valueOf:(D)Ljava/lang/Double;
        52: astore_3
        53: return
    

    If compare the ternary operator expression with an equivalent if-else:

    Integer v = 10;
    v = v != null ? 1 : 0;
    
         0: bipush        10
         2: invokestatic  #2                  // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
         5: astore_1
         6: aload_1
         7: ifnull        14
        10: iconst_1
        11: goto          15
        14: iconst_0
        15: invokestatic  #2                  // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
        18: astore_1
        19: return
    
    Integer v = 10;
    if (v != null)
        v = 1;
    else
        v = 0;
    
         0: bipush        10
         2: invokestatic  #2                  // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
         5: astore_1
         6: aload_1
         7: ifnull        18
        10: iconst_1
        11: invokestatic  #2                  // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
        14: astore_1
        15: goto          23
        18: iconst_0
        19: invokestatic  #2                  // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
        22: astore_1
        23: return
    

    There is no significant difference in the two versions. So I don't think there is a hidden secret doing all the black magic. It's a result of how the compiler parse the whole expression, and based on the context to figure out a type to make all components equally happy. E.g.,

    Double val = 0; // compilation error: context is clear, 0 is an integer, so Integer.valueOf(i), but don't match expected type - Double
    val = 0 + g.getData(); // OK, enough context to figure out the type should be Double
    

    Still, the confusion is in that why the generic field works but not the generic method...

    val = val == null ? 0 : g.data; // OK
    val = val == null ? 0 : g.getData(); // Compilation error
    

    EDIT: the document Holger quoted seems to be a good clarification.

提交回复
热议问题