2017-01-11 93 views
17

Я нашел фрагмент кода, который после переключения с Java 7 на Java 8 прекратил компиляцию. В нем нет ни одного нового материала Java 8, такого как лямбда или потоки.Java 8 autoboxing + generics: различное поведение с переменной по сравнению с методом

я сузил проблемный код вниз к следующей ситуации:

GenericData<Double> g = new GenericData<>(1d); 
Double d = g == null ? 0 : g.getData(); // type error!!! 

Вы можете, вероятно, предположить, что конструктор GenericData «s имеет один параметр этого общего типа и метод getData() возвращает только, что общий тип. (Полный исходный код см. Ниже)

Теперь то, что меня беспокоит то, что в Java 7, что код компилируется нормально, тогда как с Java 8, я получаю следующее сообщение об ошибке:

CompileMe.java:20: error: incompatible types: bad type in conditional expression 
Double d = g == null ? 0 : g.getData(); 
        ^
int cannot be converted to Double 

кажется, что Java 7 был способен выполнить переход от int -> double -> Double, но Java 8 терпит неудачу, пытаясь немедленно перейти от int -> Double.

То, что я нахожу забавным, в частности, в том, что Java 8 делает принимает код, когда я могу изменить его от getData() к data, т.е. доступ к значению в GenericData «сек с помощью самого переменного вместо газопоглощающей-методы:

Double d2 = g == null ? 0 : g.data; // now why does this work... 

Так два вопроса, у меня есть здесь:

  1. Почему Java-не выводить типы, такие как Java 7 и бросить мой Int удвоить, прежде чем Autoboxing двойного Double?
  2. Почему эта проблема возникает только с общим методом, но не с общей переменной?

Полный исходный код:

public class CompileMe { 
    public void foo() { 
     GenericData<Double> g = new GenericData(1d); 
     Double d = g == null ? 0 : g.getData(); // type error!!! 
     Double d2 = g == null ? 0 : g.data; // now why does this work... 
    } 
} 

class GenericData<T> { 
    public T data; 
    public GenericData(T data) { 
     this.data = data; 
    } 
    public T getData() { 
     return data; 
    } 
} 

Чтобы проверить это запустить компилятор следующим образом:

javac -source 1.7 -target 1.7 CompileMe.java # ok (just warnings) 
javac -source 1.8 -target 1.8 CompileMe.java # error (as described above) 

Наконец, в случае имеет значение: я бегу Windows 8 и Java 1.8.0_112 (64 -немного).

+2

Только для записи: вы используете сырой тип там; на строке 'g = new GenericData'. Наверное, не первопричина, но точно не помогает. И кстати: я просто перепробовал проблему с IBM JDK 1.8 ... действительно интересно. – GhostCat

+0

@Quota действительно интересен, кстати компилируется отлично с последним jdk-9 – Eugene

+1

Попробуйте взглянуть на этот вопрос SO: [Java 8 и обобщенный вывод типа цели] (http://stackoverflow.com/questions/19934204/ java-8-and-generalized-target-type-inference) –

ответ

1

Должны утверждать, что это не ответ, а просто рассуждение. С моим кратким опытом работы с компилятором (не Javac specific), это может иметь отношение к тому, как анализируется код.

В следующем декомпилированном коде, вы видите, как вызов метода GenericData.getData:()Ljava/lang/Object или ссылку на поле GenericData.data:Ljava/lang/Object, они оба первого получить значение/метод с Возвращаемого объектом, за которым следует 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."<init>":(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 

Если сравнить тройное выражение оператора с эквивалентом, если-то еще:

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 

Там нет существенной разницы в двух версиях. Поэтому я не думаю, что есть скрытый секрет, делающий всю черную магию. Это результат того, как компилятор разбирает все выражение и основывается на контексте, чтобы выяснить тип, чтобы сделать все компоненты одинаково счастливыми. Например.,

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 

Тем не менее, путаница в том, почему общие работы на местах, но не общий метод ...

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

EDIT: документ Хольгер цитируемый кажется хорошим осветления.

14

Выражения вызова метода являются особенными в том, что они могут быть Поли выражения, при условии ввода целевого ввода.

Рассмотрим следующие примеры:

static Double aDouble() { 
    return 0D; 
} 
… 
Double d = g == null ? 0 : aDouble(); 

компилируется без каких-либо проблем

static <T> T any() { 
    return null; 
} 
… 
Double d = g == null ? 0 : any(); 

здесь, призывание any() является Poly Expression и компилятор должен вывести T := Double. Это воспроизводит ту же ошибку.

Это первая непоследовательность. В то время как ваш метод getData() относится к параметру типа T из GenericData, это не общий метод (есть/не должно быть никакого вывода типа участвуют, чтобы определить, что T является Double здесь.

JLS §8.4.4. Generic Methods

A method is generic if it declares one or more type variables

getData() не объявлять переменные типа, он использует только один

JLS §15.12. Method Invocation Expressions:.

A method invocation expression is a poly expression if all of the following are true:

  • The method to be invoked, as determined by the following subsections, is generic (§8.4.4) and has a return type that mentions at least one of the method's type parameters.

Поскольку этот вызов метода не является поли выражением, он должен вести себя как пример с вызовом aDouble(), а не any().

§15.25.3 Но обратите внимание:

Note that a reference conditional expression does not have to contain a poly expression as an operand in order to be a poly expression. It is a poly expression simply by virtue of the context in which it appears. For example, in the following code, the conditional expression is a poly expression, and each operand is considered to be in an assignment context targeting Class<? super Integer> :

Class<? super Integer> choose(boolean b, 
           Class<Integer> c1, 
           Class<Number> c2) { 
    return b ? c1 : c2; 
} 

Таким образом, это ссылка условное или числовое условное выражение?

§15.25. Conditional Operator ? : говорит:

There are three kinds of conditional expressions, classified according to the second and third operand expressions: boolean conditional expressions, numeric conditional expressions, and reference conditional expressions. The classification rules are as follows:

  • If both the second and the third operand expressions are boolean expressions, the conditional expression is a boolean conditional expression.
  • If both the second and the third operand expressions are numeric expressions, the conditional expression is a numeric conditional expression.
    For the purpose of classifying a conditional, the following expressions are numeric expressions:
    • An expression of a standalone form (§15.2) with a type that is convertible to a numeric type (§4.2, §5.1.8).
    • A parenthesized numeric expression (§15.8.5).
    • A class instance creation expression (§15.9) for a class that is convertible to a numeric type.
    • A method invocation expression (§15.12) for which the chosen most specific method (§15.12.2.5) has a return type that is convertible to a numeric type.
    • A numeric conditional expression.
  • Otherwise, the conditional expression is a reference conditional expression.

Итак, в соответствии с этими правилами, не исключающее общие вызовов методов, все из показанных условных выражений являются числовым условным выражением и должны работать, так как только «иначе» они должны считаться условным условным выражением. Версия Eclipse, которую я тестировал, скомпилировала все из них, не сообщая об ошибках.

Это приводит к странной ситуации, что для any() случая нам нужны целевые печатать, чтобы узнать, что он имеет числовой тип возвращаемого значения и выводя, что условный является числового условного выражения, т.е. АВТОНОМНЫХ выражениями ,Обратите внимание, что для логических условных выражений, есть замечание:

Note that, for a generic method, this is the type before instantiating the method's type arguments.

но для числового условного выражения, там нет такой записки, намеренно или нет.

Но как было сказано, это относится только к any() например, в любом случае, так как метод getData() является не родовым.

+2

помните меня, но я дважды прочел ваш ответ, но до сих пор нет очевидной подсказки, почему это все еще не удается с компилятором jdk-8. В нижней строке это ошибка компилятора jdk-8? – Eugene

+1

@Eugene: точно. Случай 'getData()' должен компилироваться, как и пример 'aDouble()'. Должен ли компилятор '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' ' – Holger

+0

хорошо. Я собирался надеяться, что ты принесешь немного света. Thx – Eugene

5

Это, кажется, известная проблема компилятора Oracle: Bug ID: JDK-8162708

Цитата:

A DESCRIPTION OF THE PROBLEM :
If you have a method in a generic class declared as follow:

class Foo<T> { 
    public T getValue() { 
    // returns a value ... 
    } 
} 

and you call the method above inside a ternary operator as follow

Foo<Integer> foo = new Foo<>(); 
Float f = new Random().nextBoolean() ? foo.getValue() : 0f; 

you get a syntax error from the javac 1.8 compiler.

But the code above compiles with no errors and warnings with both javac 1.7 and 1.9.

Разрешение: Нерешенные

Затронутые Версии 8

Из Комментарии:

This issue is only applicable to 8u, there is no issue in 7 and 9

0

CompileMe.java:4: error: incompatible types: bad type in

conditional expression

 Double d = g == null ? 0 : g.getData(); // type error!!! 
int cannot be converted to Double 

Здесь 0 является целым числом, и вы помещаете его внутри Double.

Попробуйте

public class CompileMe { 
    public static void main(String[] args) { 
     GenericData<Double> g = new GenericData(1d); 
     Double d = g == null ? 0d : g.getData(); // type error!!! 
     Double d2 = g == null ? 0d : g.data; // now why does this work... 
     System.out.println(d); 
     System.out.println(d2); 
    } 
} 

class GenericData<T> { 
    public T data; 
    public GenericData(T data) { 
     this.data = data; 
    } 
    public T getData() { 
     return data; 
    } 
} 

Или использовать двойной литерал вместо двойного класса обертки

public class CompileMe { 
    public static void main(String[] args) { 
     GenericData<Double> g = new GenericData(1d); 
     double d = g == null ? 0 : g.getData(); // type error!!! 
     double d2 = g == null ? 0 : g.data; // now why does this work... 
     System.out.println(d); 
     System.out.println(d2); 
    } 
} 

class GenericData<T> { 
    public T data; 
    public GenericData(T data) { 
     this.data = data; 
    } 
    public T getData() { 
     return data; 
    } 
} 

Поскольку расширение и бокс не будет происходить одновременно.

 Смежные вопросы

  • Нет связанных вопросов^_^