Java, привязка статического метода и дженерики все свернуты с некоторой перегрузкой метода
так как название подразумевает, что мой вопрос немного странный и сложный. Я знаю, что я собираюсь сделать, нарушает все правила "хорошей" практики программирования, но эй, что такое жизнь, если мы не живем мало?
Итак, я создал следующую программу. (Не это было частью большего эксперимента, чтобы действительно попытаться понять дженерики, поэтому некоторые имена функций, возможно, немного не в порядке)
import java.util.*;
public class GenericTestsClean
{
public static void test2()
{
BigCage<Animal> animalCage=new BigCage<Animal>();
BigCage<Dog> dogCage=new BigCage<Dog>();
dogCage.add(new Dog());
animalCage.add(new Cat());
animalCage.add(new Dog());
animalCage.printList(dogCage);
animalCage.printList(animalCage);
}
public static void main(String [] args)
{
//What will this print
System.out.println("nTest 2");
test2();
}
}
class BigCage<T> extends Cage<T>
{
public static <U extends Dog> void printList(List<U> list)
{
System.out.println("*************"+list.getClass().toString());
for(Object obj : list)
System.out.println("BigCage: "+obj.getClass().toString());
}
}
class Cage<T> extends ArrayList<T>
{
public static void printList(List<?> list)
{
System.out.println("*************"+list.getClass().toString());
for(Object obj : list)
System.out.println("Cage: "+obj.getClass().toString());
}
}
class Animal
{
}
class Dog extends Animal
{
}
class Cat extends Animal
{
}
теперь меня смущает то, что это прекрасно компилируется с javac 1.6.0_26 но когда я запускаю его, я получаю следующее исключение класса cast:
Test 2
*************class BigCage
BigCage: class Dog
*************class BigCage
Exception in thread "main" java.lang.ClassCastException: Cat cannot be cast to Dog
at BigCage.printList(GenericTestsClean.java:31)
at GenericTestsClean.test2(GenericTestsClean.java:13)
at GenericTestsClean.main(GenericTestsClean.java:21)
ряд вещей, чтобы отметить здесь:
- два списка печати являются не переопределение, но перегрузка друг друга, как ожидалось(они имеют разные типы, потому что общие типы их аргументов разные). Это можно проверить с помощью аннотации @Override
- изменение
void printList(List<?>)
метод клетка класс чтобы быть нестатическим, генерирует соответствующую ошибку времени компиляции - изменение метода
void <U extends Dog> printList(List<U>)
на класс BigCage доvoid <U> printList(List<U>)
выдает ошибку. - на main () вызов printList() до класс BigCage (ie BigCage.printList(...)) генерирует ту же ошибку выполнения
- на main () вызов printList() до класс Кейдж!--20--> (клетки т. е..printList(...)) работает, как и ожидалось, только вызывая версию printList на клетка
- если я скопирую определение
printList(List<?>)
to класс BigCage С клетка класс, который скроет определение в клетка класс, я получаю соответствующую ошибку компилятора
теперь, если бы мне пришлось сделать снимок в темноте о том, что здесь происходит, я бы сказал, что компилятор завинчивается потому что он работает в несколько этапов: Тип Проверки и Перегруженное Разрешение Метода. Во время фазы проверки типа мы проходим через оскорбительную линию, потому что класс BigCage унаследовала void printList(List<?>)
С class Cage
который будет соответствовать любому старому списку, который мы бросим на него, поэтому у нас есть метод, который будет работать. Однако, как только придет время решить с помощью метода, чтобы фактически вызвать у нас есть проблема из-за стирания типа, которое вызывает оба BigCage.printList
и Cage.printList
иметь точно такую же подпись. Это означает, что компилятор ищет соответствие для animalCage.printList(animalCage);
он выберет первый метод, который он соответствует (и если мы предположим, что он начинается внизу с BigCage и работает его почему до объекта), он найдет void <U extends Dog> printList(List<U>)
вместо правильного void printList(List<?>)
теперь мой настоящий вопрос: насколько я близок к истине? Это известная ошибка? Это вообще Жук? Я знаю, как обойти эту проблему, это скорее академическое вопрос.
* * EDIT**
как мало людей разместили ниже, этот код будет работать в Eclipse. Мой конкретный вопрос касается javac версии 1.6.0_26. Кроме того, я не конечно, если я полностью согласен с Eclipse в этом случае, хотя это работает, потому что добавление
printList(List<?>)
to BigCage будет результат в ошибке времени компиляции в Eclipse, и я не вижу причины, почему он должен работать, когда тот же метод наследуется стихами вручную добавлено (см.Примечание 6 выше).
4 ответов
рассмотрим эту тривиальную проблему:
class A
{
static void foo(){ }
}
class B extends A
{
static void foo(){ }
}
void test()
{
A.foo();
B.foo();
}
Предположим, мы убираем foo
метод B
, а мы только перекомпилируем B
сам, что может произойти, когда мы бежим test()
? Должен ли он бросить ошибку связи, потому что B.foo()
не нашли?
согласно JLS3 #13.4.12, удаление B.foo
не ломать бинарную совместимость, потому что A.foo
по-прежнему определяется. Это значит, когда B.foo()
выполнено, A.foo()
вызывается. Помните, что нет никакой перекомпиляции test()
, поэтому эта пересылка должна обрабатываться JVM.
наоборот, давайте уберем foo
метод B
, и перекомпилировать все. Хотя компилятор знает статически, что B.foo()
на самом деле означает A.foo()
, он все еще генерирует B.foo()
в байт-код. На данный момент JVM будет вперед B.foo()
to A.foo()
. Но если в будущем B
получает новый foo
метод, новый метод будет вызываться во время выполнения, даже если test()
не перекомпилируется.
в этом смысле есть переопределение связи между статическими методами. При компиляции видит B.foo()
, он должен скомпилировать его в B.foo()
в байт-коде, независимо от того,B
есть foo()
сегодня.
в вашем примере, когда компилятор видит BigCage.printList(animalCage)
, он правильно делает вывод, что он на самом деле вызывает Cage.printList(List<?>)
. Поэтому он должен скомпилировать вызов в байт-код как BigCage.printList(List<?>)
- целевой класс должен быть BigCage
здесь вместо Cage
.
Упс! Формат байт-кода не был обновлен для обработки подписи метода как это. Информация дженериков сохраняется в байт-коде как вспомогательная информация, но для вызова метода это старый способ.
стирание происходит. Вызов фактически скомпилирован в BigCage.printList(List)
. Жаль BigCage
также printList(List)
после стирания. Во время выполнения этот метод вызывается!
эта проблема связана с несоответствием между спецификациями Java и спецификациями JVM.
Java 7 немного затягивается; понимая, что байт-код и JVM не могут обрабатывать такие ситуации, он больше не компилирует код:
ошибка: столкновение название : printList (список) в BigCage и printList(список) в клетке есть же стирания, но и не скрывает другое
еще один интересный факт: если два метода имеют разные типы возврата, ваша программа будет работать правильно. Это связано с тем, что в байтовом коде сигнатура метода включает тип возврата. Так что нет никакой путаницы между Dog printList(List)
и Object printList(List)
. См. также тип стирания и перегрузки в Java: почему это работает? этот трюк только в Java 6. Java 7 запрещает это, вероятно, по причинам, отличным от технических.
это не ошибка. Метод статический. Вы не можете переопределить статические методы, вы только скрываете их.
когда вы называете "printList
" на bigCage, вы действительно звоните printList
в классе BigCage, а не в объекте, который всегда будет вызывать ваш статический метод, объявленный в классе BigCage.
это самая простая версия этого кода с той же проблемой:
import java.util.*;
public class GenericTestsClean {
public static void main(String[] args) {
List<Animal> animalCage = new ArrayList<Animal>();
animalCage.add(new Cat());
animalCage.add(new Dog());
BigCage.printList(animalCage);
}
}
class Animal {}
class Dog extends Animal {}
class Cat extends Animal {}
class BigCage extends Cage {
public static <U extends Dog> void printList(List<U> list) {
System.out.println("BigCage#printList");
for (Object obj : list) {
System.out.println("BigCage: " + obj.getClass().toString());
}
}
}
class Cage {
public static void printList(List list) {
System.out.println("Cage#printList");
for (Object obj : list) {
System.out.println("Cage: " + obj.getClass().toString());
}
}
}
Я думаю, что compiller должен вернуть ошибку:
GenericTestsClean.java:8: <U extends Dog>printList(java.util.List<U>) in BigCage cannot be applied to (java.util.List<Animal>)
BigCage.printList(animalCage);
^
1 error
(или sth о столкновении имя с тем же errasure), но это не так.
После dissasembling (javap и -с GenericTestsClean) у нас есть:
invokestatic #9; //Method BigCage.printList:(Ljava/util/List;)V
вызов java GenericTestsClean
:
версии javac 1.6.0_10
BigCage#printList
Exception in thread "main" java.lang.ClassCastException: Cat cannot be cast to Dog
at BigCage.printList(GenericTestsClean.java:19)
at GenericTestsClean.main(GenericTestsClean.java:8)
Eclipse compiller версия
BigCage#printList
BigCage: class Cat
BigCage: class Dog
IMHO эти результаты оба неверны.
IMHO этот код может быть неверным. метод printList в классе BigCage должен вызвать имя clash coz printList в клетке имеют одинаковое стирание, но ни один не переопределяет другой. странно, что compiller компилирует его :)
полученный байт-код (javac 1.6.0_10) эквивалентен этому:
class BigCage extends Cage {
public static void printList(List list){
System.out.println((new StringBuilder()).append("*************").append(list.getClass().toString()).toString());
Dog dog;
for(Iterator iterator = list.iterator(); iterator.hasNext(); System.out.println((new StringBuilder()).append("BigCage: ").append(dog.getClass().toString()).toString()))
dog = (Dog)iterator.next();
}
}
приведение в цикле вызывает исключение. Встроенный компиллер Eclipse генерирует такой код (который работает без исключение):
class BigCage extends Cage{
public static void printList(List list){
System.out.println((new StringBuilder("*************")).append(list.getClass().toString()).toString());
Object obj;
for(Iterator iterator = list.iterator(); iterator.hasNext(); System.out.println((new StringBuilder("BigCage: ")).append(obj.getClass().toString()).toString()))
obj = iterator.next();
}
}
или, может быть, source в порядке, но компилятор создает плохой байт-код?
Дело в том, что мы называем методом <U extends Dog> void printList(List<U> list)
с параметром BigCage<Animal> animalCage
и животные не продлить собаки.