Несколько месяцев назад я работал в проекте для клиентов, где мы строили большую систему продаж smartclient для немецкой страховой компании. Мы использовали Riena в качестве среды приложения, затмение для разработки и Java 6 в качестве платформы. Помимо обычных проблем это было довольно забавно, но однажды мы наткнулись на проблему, связанную с ковариантными типами возвращаемых данных. А что это? Начиная с Java 1.5, вы можете использовать более конкретный тип возвращаемого значения при переопределении метода, поэтому вы можете избежать неприятных приведений в иерархиях классов. Например: у нас есть класс Aс методом, getValue()который возвращает объект типа X:
public class X {
     ...
}
public class A {
     private X value;
     public X getValue() {
         return value;
     }
}
Итак, если я наследую класс Bот A, я могу переопределить getValue()и использовать подкласс X в качестве возвращаемого типа:
public class Y extends X {
     ...
}
public class B {
...
     @Override
     public Y getValue() {
         return value;
     }
}
Это нормально, так как Y«это» X. Так какой в этом смысл? Проблема, о которой мы споткнулись, поразит вас, если вы будете использовать бин-самоанализ. Я не нуждаюсь в вонючем бобовом самоанализе! В самом деле? Вы когда-нибудь использовали привязку данных? Ах, так ты ? 
Если я использую боб-самоанализ , чтобы получить свойство valueиз объекта типа B, и попросить его типа, я буду получать … X. Чего-чего? Но .. это Y?!? Да, но это то, что происходит. Есть некоторые открытые проблемы в этой области. У меня никогда не было проблем с дженериками, так когда это вас укусит? Скажем, Y extends Xсо свойством name:
public class Y extends X {
     private String name;
     public String getName() {...}
     public void setName(String name) {...}
}
И теперь вы используете привязку данных вместе с этим свойством:
B b = ...
ridget.bind(b, "value.name")
Привязки данных видов применения боба-самоанализ , чтобы получить собственность valueот B, и от типа valueон пытается получить свойство name. И вот в этот момент ошибка вступает в стадию: из-за проблемы, описанной выше, bean-introspection получает Xтип value. Но так как свойство nameне определено в X(но в Y), вы получите забавное исключение, говорящее, что такого свойства нет name? 
Но если я позвоню B.getValue(), я получу Y. Ну, по крайней мере, компилятор не будет жаловаться, если я отношусь к нему как к Y… и я могу вызывать методы, определенные Yкак, например getName(). Так это какая-то магия компилятора? Ну, там действительно есть методY getValue()… но есть и метод X getValue()! Если вы позвоните getMethods()в класс B, вы получите:
public X B.getValue()
public Y B.getValue()
Что за…? Откуда этот метод X getValue()??? Это было сгенерировано компилятором. Ах …: — | Но почему? Целые тематические ковариантные типы возвращаемых данных придумали обобщения … и стирание типов, необходимое для совместимости. Да нет понятно. Итак, мы должны копать немного глубже здесь. Допустим, наш класс Aбыл бы определен с использованием обобщений:
public class A <T extends X> {
     private T value;
     public T getValue() {
         return value;
     }
}
public class B extends A<Y> {}
Если вы используете рефлексию для получения методов от Aи B, у них обоих будет ОДИН метод X getValue(). Ведь я ожидал, Bчто у меня будет метод Y getValue()? Так что же происходит, если я позвоню getValue()и отнесусь к результату как Y?!? Вот и весь сахар компилятора, значит: компилятор выполняет всю эту проверку типов, оценивая обобщенную информацию и вставляя приведения в ваш код, где это необходимо. Посмотрите на следующий фрагмент:
     public static void main(String[] args) {
         B b = new B();
         Y y = b.getValue();
     }
На уровне байт-кода X getValue()вызывается (только) метод , и результат Xприводится к Y:
public static void main(java.lang.String[] args);
0 new B [29]
3 dup
4 invokespecial B() [31]
7 astore_1 [b]
8 aload_1 [b]
9 invokevirtual B.getValue() : X [174] // X getValue() called
12 checkcast Y [133] // cast to Y
15 astore_2 [y]
16 return
Во время выполнения вся обобщенная информация стирается, и используется только необработанный тип X. Это сделано для совместимости. То же самое, если вы используете коллекции: если вы определите List <String> в вашем источнике, компилятор преобразует это в нетипизированный список объектов. Так почему тип нашего универсального типа Tв классе был Aскомпилирован X, а не в Object? Это потому, что мы определили нижнюю границу нашего типа T:
public class A <T extends X> {
...
Коллекции, такие как List, не определяют нижнюю границу, поэтому необработанным типом является Object:
public interface List<T> ...
До сих пор вы даже не замечаете все это; Вы просто используете это, и это кажется вполне естественным. Но все изменится, если мы переопределим getValue()в B:
public class B extends A<Y> {
     @Override
     public Y getValue() {
        ...
     }
}
Теперь компилятор создает Y getValue()ожидаемый метод , который переопределяет код суперкласса A. Для языка Java все хорошо. Но с точки зрения JVM Y getValue()это нечто иное, чем X getValue(). Поэтому, если вы назначите объект типа Bдля ссылки на тип Aи вызовете getValue(), JVM будет искать метод, X getValue()который определен в A:
B b = new B();
A ba = b;
ba.getValue(); // -> JVM wants to invoke X getValue()
Так что здесь есть пробел. А что делает наш модный компилятор? Он исправляет этот недостаток, используя синтетический метод X getValue():
public class B extends A {
...
   public Y getValue();
     0  aload_0 [this]
     ...
   public bridge synthetic X getValue();
     0  aload_0 [this]
     1  invokevirtual B.getValue() : Y [21]   // delegate to Y getValue()
     4  areturn
       Line numbers:
         [pc: 0, line: 1]
}
Это метод моста. В этом случае это всего лишь делегат нашего переопределенного метода Y getValue(). Так что здесь у нас есть два метода getValue(). (Между прочим: ‘bridge’ и ‘синтетический’ — оба атрибута метода, которые могут быть запрошены с помощью отражения.) Этот предмет становится более понятным, если вы подумаете о переопределении метода с общими параметрами:
public class A <T extends X> {
     public void setValue(T value) {
       ....
     }
...
}
public class B extends A<Y> {
     @Override
     public void setValue(Y value) {
        ...
     }
}
Итак, снова компилятор правильно создает метод с подписью setValue(Y)для метода. Опять же, это будет метод, отличный от того, который определен в A, поэтому для заполнения пробела вставляется метод моста:
public class B extends A {
...
   public void setValue(Y value);
     0  aload_0 [this]
     ...
   public bridge synthetic void setValue(X arg0);
     0  aload_0 [this]
     1  aload_1 [arg0]
     2  checkcast Y [21]                         // cast to Y
     5  invokevirtual B.setValue(Y) : void [23]  // call setValue(Y)
     8  return
       Line numbers:
         [pc: 0, line: 1]
}
Обычно вам не нужно даже думать об этом, потому что компилятор управляет всем этим для вас и выдает предупреждения или даже ошибки, если вы уходите с пути безопасности типов.
Но, несмотря на это, есть еще некоторые подводные камни, ожидающие. Одно замечание: в API отражения не существует определенного порядка (например getMethods()) для доставки методов класса. Средства: в зависимости от того, когда и как запрашиваются методы, вы получите либо первый, X getValue()либо Y getValue()первый! И здесь у нас есть ошибка JDK, входящая в стадию: bean-introspection не обращает на это внимания, поэтому, если вы запросите тип свойства valueв классе, Bвы получите либо XИЛИY! У нас была именно такая ситуация в проекте заказчика. Когда мы запустили приложение из нашей IDE, все было хорошо. Но в развернутом приложении мы получили ошибку привязки данных, описанную выше: — / 
Но таких забавных проблем еще больше. И не в теории, мы получили следующую проблему в том же проекте. Следующий сценарий: мы хотели облегчить некоторые вещи, чтобы избавиться от кода котельной плиты. Например, вместо выполнения асинхронного выполнения программы с использованием потоков мы оценили решение на основе аннотаций. Значит: вы помечаете метод как@Asyncи он будет выполняться асинхронно, пока цикл событий пользовательского интерфейса все еще работает. Мы использовали (Class-) прокси для перехвата, который анализировал аннотации и выполнял асинхронное выполнение при необходимости. Теперь давайте возьмем наш пример и отметим переопределенный метод getValue()как @Async:
public class B extends A<Y> {
     @Override
     @Async
     public Y getValue() {
        ...
     }
}
Теперь проблема. Иногда это выполнялось асинхронно, иногда нет: — Причина была в том, что аннотация иногда присутствовала, а иногда нет?!? Если вы думаете о причине мостовых методов, вы, возможно, уже знаете ответ. Это зависит от того, КАК вы звоните getValue(). Посмотри:
7: B b = new B();
8: A ba = b;
9:
10: b.getValue(); // Executed asynchronously.
11: ba.getValue(); // In this case not 🙁
Опять же, мостовые методы являются причиной проблемы. Если вы установите точку останова в переопределенном методе Y getValue()в классе B, вы получите следующий стек выполнения в отладчике для вызова b.getValue():
Thread [main] (Suspended)
B.getValue() line: 7
BridgeMethodTest.main(String[]) line: 10
При втором вызове — ba.getValue () — это выглядит так:
Thread [main] (Suspended)
B.getValue() line: 7
B.getValue() line: 1 // <- bridge-method X getValue()
BridgeMethodTest.main(String[]) line: 11
Таким образом, в вызове ba.getValue()мы попали в метод моста. Подумайте об этом: для компилятора объект baявляется A, и в Aметоде X getValue()определен, так что именно это использует компилятор -> наш метод моста. И поскольку это синтетический метод, который не имеет никакого представления исходного кода, строка: 1 показана как информация о номере строки … что выглядит довольно любопытно, поскольку в нашей исходной строке ничего нет. Хорошо, а как насчет иногда отсутствующей аннотации? Опять же, это ошибка JDK: когда компилятор генерирует метод bridge, он не копирует аннотации из исходного метода, что означает: метод bridge не аннотируется. Таким образом, наш прокси не найдет никакой аннотации в этом случае и, следовательно, не выполнит вызов асинхронно: — /
Вывод
В конце концов, вы вряд ли попадете в эти ловушки, так что вам не нужно слишком о них заботиться. Но очень полезно знать эти странности … по крайней мере, чтобы понять, почему отладчик останавливается в строке 1 ? Кстати, ошибка в интроспекции бина была исправлена в Java 7, но недостающие аннотации все еще остаются проблема. И наверняка будут еще несколько забавных проблем в этой области.
Когда вы смотрите в пропасть, 
пропасть также смотрит в вас. 
Фридрих Ницше
Ресурсы