2016-08-04 3 views
0

Почему я получаю ClassCastException только при раскомментировании третьего оператора в Main.main()? И никаких исключений, но хорошо выполненных первых и вторых утверждений?Java Generics «upcast» для непараметризированного типа

public class Tuple<K, V> { 
    public final K first; 
    public final V second; 

public Tuple(K first, V second) { 
    this.first = first; 
    this.second = second; 
} 

@Override public String toString() { 
    return "Tuple{" + "first = " + first + ", second = " + second + '}'; 
    } 
} 

class Test { static Tuple f(){return new Tuple("test", 8);} } 

class Bar {} 

class Main{ 
    public static void main(String[] args) { 
     Tuple<String, Bar> t = Test.f(); 
     System.out.println(t); 
     //System.out.println(t.second.getClass().getSimpleName()); 
    } 
} 

Заранее благодарен.

+0

Я понимаю, что не будет работать в Java. Вашим дженерикам не дают тип, они - Дженерики. Это сработало бы, если бы у вас было что-то наподобие «public final K », и оно вернет String. –

+0

'Test.f()' возвращает необработанный тип. Эта ошибка будет легче поймать, если вы избежите использования необработанных типов. Тип возвращаемого значения 'Test.f()' должен быть определен как 'Tuple ' для соответствия возвращаемому значению. Это приведет к ошибке компиляции в 'Tuple t = Test.f()'. Тип безопасности - это мощная функция. –

+0

Вы должны публиковать полную ошибку: 'Исключение в потоке" main "java.lang.ClassCastException: java.lang.Integer не может быть отброшен в Bar' - теперь он становится намного яснее ... – alfasin

ответ

3

Когда вы пишете цепочку вызовов метода:

System.out.println(t.second.getClass().getSimpleName()); 

компилятор эффективно расширяет это:

TypeOfTSecond tmpTSecond = t.second; 
Class<?> clazzTmp = tmp.getClass(); 
String nameTmp = clazzTmp.getSimpleName(); 
System.out.println(nameTmp); 

Теперь, если это произойдет, что t.second является универсальным типом, компилятор вставит литой к типу, что это вещи t.second будет:

Bar tmpTSecond = (Bar) t.second; 

Так что, даже если вы никогда не обращаетесь к какой-либо функциональной функции Bar, вы получите ClassCastException.


Чтобы продемонстрировать это, вот байткодом:

public static void main(java.lang.String[]); 
    Code: 
     0: invokestatic #2  // Method Test.f:()LTuple; 
     3: astore_1 
     4: getstatic  #3  // Field java/lang/System.out:Ljava/io/PrintStream; 
     7: aload_1 
     8: getfield  #4  // Field Tuple.second:Ljava/lang/Object; 
     11: checkcast  #5  // class Bar 
     14: invokevirtual #6  // Method java/lang/Object.getClass:()Ljava/lang/Class; 
     17: invokevirtual #7  // Method java/lang/Class.getSimpleName:()Ljava/lang/String; 
     20: invokevirtual #8  // Method java/io/PrintStream.println:(Ljava/lang/String;)V 
     23: return 

линия 8, где t.second помещается в стек; строка 11, где происходит отливка до Bar.


Это только происходит из-сырьевых типов, используемых при объявлении test.f():

static Tuple f(){return new Tuple("test", 8);} 

Если бы это было правильно объявлен

static Tuple<String, Integer> f(){return new Tuple<>("test", 8);} 

тогда эта линия

Tuple<String, Bar> t = Test.f(); 

не будет компилироваться. Но использование необработанных типов отключает проверку типа компилятора, поэтому невозможно избежать ошибок времени выполнения, подобных этому.


Основная уборка - never use raw types.

Вторичный урок обращает внимание на предупреждения вашего компилятора (или IDE). Компиляция кода, мне сказали:

Note: Main.java uses unchecked or unsafe operations. 
Note: Recompile with -Xlint:unchecked for details. 

и когда я перекомпилировать с этим флагом:

Main.java:19: warning: [unchecked] unchecked call to Tuple(K,V) as a member of the raw type Tuple 
     return new Tuple("test", 8); 
      ^
    where K,V are type-variables: 
    K extends Object declared in class Tuple 
    V extends Object declared in class Tuple 
Main.java:26: warning: [unchecked] unchecked conversion 
    Tuple<String, Bar> t = Test.f(); 
           ^
    required: Tuple<String,Bar> 
    found: Tuple 
2 warnings 
+0

Thnks, это совершенно ясный ответ! –

+0

Обратите внимание, что в этом случае компилятор не * требуется *, чтобы вставить листинг 't.second' в' Bar'. То есть это зависит от реализации. – newacct

0

Согласно Java Docs для getClass() метод Object класса

Фактический тип результат Class<? extends |X|> где | X | - это стирание статического типа выражения, на которое вызывается getClass.

В декларации стороны, тип Бар

Tuple<String, Bar> t = Test1.f(); 

Поскольку нет родитель-ребенок отношения между Integer и Bar, при попытке бросить Integer в бар, вы получите ClassCastException (т.е. Integer extends Bar не правильно)

Вот затруднительное положение

Tuple<String, Object> t = Test1.f(); 

Примечания :It is not encouraged to use raw types. Этот ответ просто объясняет причину неудачи и исправление для этого.

+0

Голосов в дневное время бесполезно, если только один избиратель не уточнил, в чем проблема: – JavaHopper

+0

Я не спустил вниз, но я предполагаю, что это связано с тем, что ваше исправление основано на использовании сырых типов. Если 'Test.f()' был объявлен с аргументами типа, которые соответствуют возвращаемому значению (''), вы получите ошибку компиляции для использования ''. Несмотря на то, что он работает, он поощряет плохой дизайн, поскольку он позволяет избежать реальной проблемы (отсутствие безопасности типа), которая показана с помощью предупреждающего сообщения, которое вы получаете от этого. По этой причине я лично не мог бы спуститься вниз (он все еще работает), но некоторые люди более строгие, чем другие. –

+0

Я понял! Итак, здесь, в SO, вместо того, чтобы просто фиксировать и объяснять причину исправления, мы также должны предлагать лучшие практики. Как вы сказали, я пропустил сказать, что использование сырых типов не рекомендуется. Благодаря! – JavaHopper

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