2013-11-26 4 views
3

Я читаю раздел «Дженерики и коллекции Java» 8.4. Автор определяет следующий код при попытке объяснить бинарную совместимость:Невозможно воспроизвести результат примера стирания типа

interface Name extends Comparable { 
    public int compareTo(Object o); 
} 
class SimpleName implements Name { 
    private String base; 
    public SimpleName(String base) { 
     this.base = base; 
    } 
    public int compareTo(Object o) { 
     return base.compareTo(((SimpleName)o).base); 
    } 
} 
class ExtendedName extends SimpleName { 
    private String ext; 
    public ExtendedName(String base, String ext) { 
     super(base); this.ext = ext; 
    } 
    public int compareTo(Object o) { 
     int c = super.compareTo(o); 
     if (c == 0 && o instanceof ExtendedName) 
     return ext.compareTo(((ExtendedName)o).ext); 
     else 
     return c; 
    } 
} 
class Client { 
    public static void main(String[] args) { 
     Name m = new ExtendedName("a","b"); 
     Name n = new ExtendedName("a","c"); 
     assert m.compareTo(n) < 0; 
    } 
} 

и затем переговоры о создании интерфейса Имени и SimpleName класса родового и оставляя ExtendedName как есть. В результате новый код:

interface Name extends Comparable<Name> { 
    public int compareTo(Name o); 
} 
class SimpleName implements Name { 
    private String base; 
    public SimpleName(String base) { 
     this.base = base; 
    } 
    public int compareTo(Name o) { 
     return base.compareTo(((SimpleName)o).base); 
    } 
} 
// use legacy class file for ExtendedName 
class Test { 
    public static void main(String[] args) { 
     Name m = new ExtendedName("a","b"); 
     Name n = new ExtendedName("a","c"); 
     assert m.compareTo(n) == 0; // answer is now different! 
    } 
} 

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

Сказать, что мы generify имя и SimpleName так, что они определяют CompareTo (имя), но у нас нет источника для ExtendedName. Поскольку он определяет только compareTo (Object), клиентский код, который вызывает compareTo (Name), а не compareTo (Object), вызывается методом SimpleName (где он определен), а не ExtendedName (где он не определен), поэтому базовые имена будут сравниваться, но расширения игнорируются.

Однако, когда я делаю только имя и SimpleName generic, я получаю ошибку времени компиляции, а не то, что автор описывает выше. Ошибка:

конфликта имен: CompareTo (объект) в NameHalfMovedToGenerics.ExtendedName и CompareTo (Т) в сравнимых имеют тот же стирание, но ни один не перекрывает другой

И это не первый я столкнулся с такой проблемой. Раньше, пытаясь прочитать документацию Sun о стирании, я столкнулся с аналогичной проблемой, когда мой код не показывает тот же результат, что и автор.

Я допустил ошибку в понимании того, что автор пытается сказать?

Любая помощь будет очень признательна.

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

+1

Отправьте код, в котором вы «* укажите только имя и простое имя» *. –

+0

У меня уже есть - во втором кодовом блоке выше. Вы просите что-то еще? 1. Сопоставимый преобразуется в Сопоставимый и 2.В классе SimpleName метод compareTo (Object) становится compareTo (Name). – Mustafa

+2

Вы сделали источник для 'ExtendedName' недоступным, как предполагал автор? –

ответ

2

Это пример проблемы, которая может возникнуть под отдельная компиляция.

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

В примере Name и SimpleName были изменены и перекомпилированы, но старый, скомпилированный двоичный код ExtendedName по-прежнему используется. Это действительно означает, что «исходный код для ExtendedName недоступен». Когда программа скомпилирована против модифицированной иерархии классов, она записывает различную информацию, чем она имела бы, если бы она была скомпилирована против старой иерархии.

Позвольте мне выполнить шаги, которые я выполнил, чтобы воспроизвести этот пример.

В пустом каталоге я создал два подкаталога: v1 и v2. В v1 я помещал классы из первого примера кода в отдельные файлы Name.java, SimpleName.java и ExtendedName.java.

Обратите внимание, что я не использую каталоги v1 и v2 как пакеты. Все эти файлы находятся в неназванном пакете. Кроме того, я использую отдельные файлы, так как, если они все вложенные классы, трудно перекомпилировать некоторые из них отдельно, что необходимо для того, чтобы этот пример работал.

Кроме того, я переименовал основную программу в Test1.java и модифицировать его следующим образом:

class Test1 { 
    public static void main(String[] args) { 
     Name m = new ExtendedName("a","b"); 
     Name n = new ExtendedName("a","c"); 
     System.out.println(m.compareTo(n)); 
    } 
} 

В v1 я собрал все и побежал Test1:

$ ls 
ExtendedName.java Name.java SimpleName.java Test1.java 
$ java -version 
java version "1.7.0_45" 
Java(TM) SE Runtime Environment (build 1.7.0_45-b18) 
Java HotSpot(TM) 64-Bit Server VM (build 24.45-b08, mixed mode) 
$ javac *.java 
$ java Test1 
-1 

Теперь, в v2 я поместил Name.java и SimpleName.java, модифицированные с использованием дженериков, как показано во втором примере кода. Я также скопировал в v1/Test1.java по номеру v2/Test2.java и переименовал класс соответственно, но в остальном код тот же.

$ ls 
Name.java SimpleName.java Test2.java 
$ javac -cp ../v1 *.java 
$ java -cp .:../v1 Test2 
0 

Это показывает, что результат m.compareTo(n) отличается после Name и SimpleName были изменены, при использовании старого ExtendedName двоичную. Что случилось?

Мы можем увидеть разницу, глядя на разобранном выходе из Test1 класса (составитель против старых классов) и Test2 класс (скомпилированный против новых классов), чтобы увидеть, что байт-код генерируется для m.compareTo(n) вызова. Еще в v2:

$ javap -c -cp ../v1 Test1 
... 
29: invokeinterface #8, 2  // InterfaceMethod Name.compareTo:(Ljava/lang/Object;)I 
... 

$ javap -c Test2 
... 
29: invokeinterface #8, 2  // InterfaceMethod Name.compareTo:(LName;)I 
... 

При компиляции Test1, информация копируется в файл Test1.class призыв к compareTo(Object), потому что это метод интерфейса Name имеет в этой точке. С измененными классами, компиляция Test2 приводит к байт-коду, который вызывает compareTo(Name), так как теперь имеет измененный интерфейс Name. Когда пробегает Test2, он ищет способ compareTo(Name) и, таким образом, обходит метод compareTo(Object) в классе ExtendedName, вместо этого он вызывает SimpleName.compareTo(Name). Вот почему поведение отличается.

Обратите внимание, что поведение старого Test1бинарного не меняется:

$ java -cp .:../v1 Test1 
-1 

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

+0

Я бы отправился в горы, отдав все остальное, чтобы изучить java, если кто-то сказал мне, что я могу быть как вы. Спасибо, человек, ты потрясающий. Я успешно воспроизвел проблему сейчас. Мне интересно, пытаюсь ли я понять второстепенные детали всего, что вы сказали выше, но я перейду к другим более простым темам, которые я не знаю, и вернемся к этому чуть позже. Но ты справился с работой! Спасибо – Mustafa

+1

Ты мне льстишь. Воистину, я не эксперт. (Например, ребята, которые писали эту книгу). Но спасибо, я рад, что помог. Удостоверьтесь, чтобы учиться немного каждый день, и держите его в течение долгого времени. –

+0

Я постараюсь изо всех сил. Спасибо за этот совет. – Mustafa

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