2015-12-16 2 views
9

Предположим, мы имеем следующий общий классКакой из перегруженных методов будет вызываться во время выполнения, если мы применим стирание типа и почему?

public class SomeType<T> { 
    public <E> void test(Collection<E> collection){ 
     System.out.println("1st method"); 

     for (E e : collection){ 
      System.out.println(e); 
     } 
    } 

    public void test(List<Integer> integerList){ 
     System.out.println("2nd method"); 

     for (Integer integer : integerList){ 
      System.out.println(integer); 
     } 
    } 

} 

Теперь внутри основного метода мы следующий фрагмент кода

SomeType someType = new SomeType(); 
List<String> list = Arrays.asList("value"); 
someType.test(list); 

В результате выполнения someType.test(list) мы получим «2-й метод» в нашей консоли как а также java.lang.ClassCastException. Насколько я понимаю, причина того, почему выполняется второй метод test, заключается в том, что мы не используем generics для SomeType. Таким образом, компилятор мгновенно удаляет всю информацию о генериках из класса (то есть как <T>, так и <E>). После этого второго метода test в качестве параметра будет List integerList, и, конечно, List лучше соответствует List, чем Collection.

Теперь рассмотрим, что внутри основного метода мы имеем следующий фрагмент кода

SomeType<?> someType = new SomeType<>(); 
List<String> list = Arrays.asList("value"); 
someType.test(list); 

В этом случае мы получим «1-й метод» в консоли. Это означает, что выполняется первый тестовый метод. Вопрос в том, почему?

Из моего понимания во время выполнения у нас никогда не было никакой информации о дженериках из-за стирания стилей. Итак, почему тогда второй метод test не может быть выполнен. Для меня второй метод test должен быть (во время выполнения) в следующей форме public void test(List<Integer> integerList){...} Не так ли?

+3

У нас нет данных генериков во время выполнения, но выбор метода не выполняется во время выполнения. – user2357112

+0

Хорошо, но как выбрать способ? Есть ли какая-либо конкретная информация в байт-коде, которая сообщает jvm, какой метод вызывать? – ruvinbsu

+1

@ruvinbsu, поскольку компилятор действительно решает, какой метод он должен назвать, да. – SomeJavaGuy

ответ

3

The JLS is a bit of a rat's nest on this one, но есть неофициальные (их слова, а не мои) правила, которые вы можете использовать:

[O] методы п более специфичны, чем другой, если любой вызов обрабатывается первым способом может быть передается другому, без ошибки времени компиляции.

Ради аргумента, давайте назовем <E> test(Collection<E>) метод 1, и test(List<Integer>) 2. Метод

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

Другая часть, чтобы это связано с тем, что List является более точным, чем Collection, если метод передается List, он будет стремиться принять, что более легко, чем Collection, с той оговоркой, что тип должен быть проверено во время компиляции. Поскольку он не с этим необработанным типом, я считаю, что эта конкретная проверка пропущена, а Java обрабатывает List<Integer> как более конкретно, чем Collection<capture(String)>.

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

Переезд; с вашим вторым примером, вы даете нам любезность вводить ваш экземпляр в качестве подстановочного знака, что позволяет Java делать правильное утверждение времени компиляции, что test(Collection<E>) - это самый безопасный способ.

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

Мораль истории: Не используйте необработанные типы. Они злы. Это приводит к тому, что система типов ведет себя странным образом, и на самом деле она поддерживает только обратную совместимость.

+0

Теперь я понимаю, что компилятор не только проверяет ошибки/ошибки и создает байт-код. Он также предоставляет jvm некоторую конкретную информацию о том, какой метод вызывать в будущем. Таким образом, это означает, что компилятор не тот, кто просто выполняет прямой перевод исходного кода, но также принимает решения на будущее. – ruvinbsu

4

Применяемые методы сопоставлены до тип стирание (see JSL 15.12.2.3). (Стирание означает, что типы среды выполнения не параметризованные, но этот метод был выбран во время компиляции, когда параметры типа были доступны)

Типа list является List<String>, поэтому:

  • test(Collection<E>) является применимо , потому что List<Integer> совместим с Collection<E>, где E является Integer (формально, ограничение формула List<Integer> → Collection<E> [E:=Integer] сводится к true, потому что List<Integer> является подтипом Collection<Integer>).

  • test(List<String>) не применяется, поскольку List<String> не совместим с List<Integer> (формально, ограничение формула List<String>List<Integer> сводится к false, потому что String не супертипом Integer).

Детали объяснил скрыты в JSL 18.5.1.

Для test(Collection<E>):

Пусть θ будет замена [Е: = Целое]

[...]

Набор ограничений формул, C, строится следующим образом: пусть F1, ..., Fn - формальные типы параметров m, а e1, ..., ek - фактические аргументы выражения вызова.

В этом случае мы имеем F1 = Collection<E> и e1 = List<Integer>

Тогда: [множество ограничений формул] включает в себя <эи → Fi, θ>

В этом случае мы имеем List<Integer> → Collection<E> [E:=Integer] (где → означает, что e1 совместим с F1 после переменной типа E)

Для test(List<String>), нет никакой замены (поскольку нет переменных вывода), а ограничение - только List<String>List<Integer>.

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