Подтвердить что ты не робот

Почему невозможно реализовать общий интерфейс несколько раз с разными параметрами типа?

У меня есть интерфейс:

public static interface Consumer<T> {
    void consume(T t);
}

И я хочу иметь:

public static class Foo implements Consumer<String>, Consumer<Integer> {
    public void consume(String t) {..}
    public void consume(Integer i) {..}
}

Это не работает - компилятор не позволяет вам реализовать один и тот же интерфейс дважды.

Вопрос: Почему?

Люди задавали подобные вопросы здесь, но ответ был всегда "стиранием типа", т.е. вы не можете этого сделать, потому что типы стираются во время выполнения.

И это не так - некоторые типы сохраняются во время выполнения. И они сохраняются в этом конкретном случае:

public static void main(String[] args) throws Exception {
    ParameterizedType type = (ParameterizedType) Foo.class.getGenericInterfaces()[0];
    System.out.println(type.getActualTypeArguments()[0]);
}

Это печатает class java.lang.String (если я компилирую только Consumer<String>)

Таким образом, стирание в его простейшем объяснении не является причиной или, по крайней мере, нуждается в разработке - там есть тип, а также, вы не заботитесь о разрешении типа, потому что у вас уже есть два метода с четкими подпись. Или, по крайней мере, так кажется.

4b9b3361

Ответ 1

Ответ по-прежнему "стирает тип", но это не так просто. Ключевые слова: необработанные типы

Представьте себе следующее:

Consumer c = new Foo();
c.consume(1);

Что бы это сделало? Похоже, что consume(String s) на самом деле не consume(String s) - он все еще consume(Object o), хотя он определен как принимающий String.

Итак, приведенный выше код неоднозначен - среда выполнения не может знать, какой из двух методов consume(..) вызывать.

Примером смешного примера является удаление Consumer<Integer>, но сохраняйте метод consume(Integer i). Затем вызовите c.consume(1) на raw Consumer. throws ClassCastException - невозможно передать из Integer в String. Любопытная вещь об этом исключении состоит в том, что это происходит в строке 1.

Причина заключается в использовании методов bridge. Компилятор генерирует метод моста:

public void consume(Object o) {
    consume((String) o);
}

Сгенерированный байт-код:

public void consume(java.lang.String);

public void consume(java.lang.Integer);

public void consume(java.lang.Object);
  Code:
     0: aload_0
     1: aload_1
     2: checkcast     #39                 // class java/lang/String
     5: invokevirtual #41                 // Method consume:(Ljava/lang/String;)V

Итак, даже если методы, которые вы определили, сохраняют свои подписи, каждый из них имеет соответствующий метод моста, который фактически вызывается при работе с классом (независимо от того, является ли он необработанным или параметризованным).

Ответ 2

@Bozho: Спасибо за ваш глубокий вопрос и ваш собственный ответ. Даже я был на этом сомнении на определенном этапе.

Плюс добавить еще к своему ответу, я бы не согласился с вами по этому вопросу в вашем вопросе:

"некоторые типы сохраняются во время выполнения" .

Ответ НЕТ из-за стирания типа (о котором вы уже указали). Ничего не сохранилось.

Теперь, чтобы ответить на это ключевое сомнение: ", как он тогда знает точный тип после стирания?", проверьте само определение ParameterizedType.

Когда создается параметризованный тип p, объявляется объявление общего типа, которое создает p,

Возможно, это разрешение точного TypeVariable получается через то, что вы указали как мост