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

Переопределить метод в Java в целом

Если у меня есть базовый класс, подобный этому, я не мог бы изменить:

public abstract class A {
    public abstract Object get(int i);
}

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

public class B extends A{
    @Override
    public String get(int i){
        //impl
        return "SomeString";
    }
}

все в порядке. Но попытка попытаться использовать более общий сбой, если я попытаюсь:

public class C extends A{
    @Override
    public <T extends Object> T get(int i){
        //impl
        return (T)someObj;
    }
}

Я не могу думать ни о какой причине, почему это должно быть запрещено. В моем понимании общий тип T привязан к объекту - который является запрошенным типом возврата A. Если я могу положить String или AnyObject в качестве типа возврата внутри B, почему мне не разрешено помещать <T extends Object> T в мой класс C?

Еще одно странное поведение в моей точке зрения - это дополнительный метод, подобный этому:

public class D extends A{

    @Override
    public Object get(int i){
        //impl
    }

    public <T extends Object> T get(int i){
        //impl
    }
}

Также не допускается с подсказкой DuplicateMethod. Этот, по крайней мере, смущает меня, и я думаю, что Java должен принять решение: Является ли это ТОЛЬКО возвращенным типом, почему бы не разрешить переопределение, и если это не так, я должен был бы добавить этот метод. Чтобы сказать мне то же самое, но я не могу принять его, чтобы переопределить, очень прост в здравом смысле

4b9b3361

Ответ 1

JLS # 8.4.2. Подпись метода

Подпись метода m1 является подсигналом сигнатуры метода m2, если:

  • m2 имеет ту же подпись, что и m1, или

  • подпись m1 такая же, как стирание (п. 4.6) подпись m2.

В соответствии с приведенным выше правилом, поскольку ваш родитель не имеет стирания, а ваш ребенок имеет один, поэтому он не является допустимым переопределением.

JLS # 8.4.8.3. Требования к переопределению и скрытию

Пример 8.4.8.3-4. Erasure Affects Overriding

Класс не может иметь двух методов-членов с тем же именем и стиранием типа:

class C<T> {
    T id (T x) {...}
}
class D extends C<String> {
    Object id(Object x) {...}
}

Это незаконно, так как D.id(Object) является членом D, C.id(String) объявляется в супертипе D и:

  • Оба метода имеют одно и то же имя, id
  • C.id(String) доступно для D
  • Подпись D.id(Object) не является поднаклейкой подписи C.id(String)
  • Два метода имеют одно и то же стирание

Два разных метода класса не могут переопределять методы с одинаковым стиранием:

 class C<T> {
     T id(T x) {...}
 }
 interface I<T> {
     T id(T x);
 }
 class D extends C<String> implements I<Integer> {
    public String  id(String x)  {...}
    public Integer id(Integer x) {...}
 }

Это также незаконно, так как D.id(String) является членом D, D.id(Integer) объявлен в D и:

  • Оба метода имеют одно и то же имя, id
  • D.id(целое число) доступно для D
  • Оба метода имеют разные подписи (и ни один из них не является подсумка другой)
  • D.id(String) переопределяет C.id(String) и D.id(Integer) переопределяет I.id(Integer), но оба переопределенных метода имеют одинаковые Стирание

Также он дает пример случая, когда он разрешен от супер к дочернему

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

Рассмотрим пример:

class CollectionConverter {
List toList(Collection c) {...}
}
class Overrider extends CollectionConverter {
 List toList(Collection c) {...}

}

Теперь предположим, что этот код был написан до введения дженериков, и теперь автор класса CollectionConverter решает генерировать код, таким образом:

 class CollectionConverter {
   <T> List<T> toList(Collection<T> c) {...}
 }

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

Ответ 2

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

 public <T extends Object> Object get(int i)

Я не знаю, почему Java создает это ограничение (подумал об этом), я просто думаю, что он имеет дело со специальными случаями, реализованными для подклассификации родовых типов.

Второе определение, по существу, переводится на:

public class D extends A{

    @Override
    public Object get(int i){
    //impl
    }

    public Object get(int i){
        //impl
    }

}

что, очевидно, является проблемой.

Ответ 3

Из раздела JLS 8.4.8.3 Перекрытие и скрытие:

Это ошибка времени компиляции, если объявление типа T имеет метод-член m1 и существует метод m2, объявленный в T, или супертип T такой, что выполняются все следующие условия:

  • m1 и m2 имеют одно и то же имя.

  • m2 доступен из T.

  • Подпись m1 не является поднаклейкой (§8.4.2) сигнатуры m2.

  • Подпись m1 или некоторого переопределения m1 метода (прямо или косвенно) имеет такое же стирание, что и подпись m2 или некоторый метод m2 переопределяет (прямо или косвенно).

1 и 2.

3 также выполняется потому, что (цитата из JLS раздел 8.4.2):

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

И у вас есть другой способ: метод с общим типом, который переопределяет один без общего.

4 выполняется также, потому что стертые сигнатуры одинаковы: public Object get(int i)

Ответ 4

Представьте себе следующий вызов.

A a = new C();
a.get(0);

Фактически вы вызываете общий метод, но вы не передаете аргументы какого-либо типа. Как бы то ни было, это не проблема. Эти аргументы типа исчезают во время генерации кода. Тем не менее, овеществление никогда не было снято со стола, а стюарды Java, которые попробовали язык, и продолжают пытаться и держать эту дверь открытой. Если аргументы типа были подтверждены, ваш вызов не будет предоставлять какой-либо метод, который требует его.

Ответ 5

Исходный код @RafaelT приводит к этой ошибке компиляции...


C.java:3: error: C is not abstract and does not override abstract method get(int) in A
public class C extends A { 
       ^
C.java:5: error: name clash: <T>get(int) in C and get(int) in A have the same erasure, yet neither overrides the other
    public  <T extends Object> T  get ( int i ){ 
                              ^
  where T is a type-variable:
    T extends Object declared in method <T>get(int)

Самое простое, самое прямолинейное решение для компиляции подкласса @RafaelT C для компиляции...


public abstract class A {
    public abstract Object get(int i);
}

public class C<T> extends A {
    @Override
    public T get(int i){
        //impl
        return (T)someObj;
    }
}

Хотя я уверен, что другие люди хорошо понимали свои ответы. Тем не менее, некоторые из ответов, похоже, в основном неверно истолковали JLS.

Вышеизложенное изменение только объявления класса приводит к успешной компиляции. Этот факт сам по себе означает, что оригинальные подписи @RafaelT действительно идеально подходят как подписи - вопреки тому, что предложили другие.

Я не собираюсь делать ту же ошибку, что и другие, кто ответил, и сделал попытку сделать вид, что полностью заполняю документацию по дженерикам JLS. Признаюсь, что я не выяснил с 100% уверенностью, первопричиной неудачи оригинальной компиляции OP.

Но я подозреваю, что он имеет что-то для неудачной комбинации использования OP Object как типа возврата поверх типичных запутанных и причудливых тонкостей Java-дженериков.

Ответ 6

Вы должны прочитать erasure.

В вашем примере:

public class D extends A{

  @Override
  public Object get(int i){
      //impl
  }

  public <T extends Object> T get(int i){
      //impl
  }
}

Компилятор сгенерированный байт-код для вашего общего метода будет идентичен вашему не-универсальному методу; то есть T будет заменено верхней границей, т.е. Object. Вот почему вы получаете предупреждение DuplicateMethod в своей среде разработки.

Ответ 7

Объявление метода как <T extends Object> T get(int i) не имеет смысла, не объявляя T где-то еще - в аргументах метода или в поле охватывающего класса. В последнем случае просто параметризуйте весь класс с типом T.

Ответ 8

Есть концептуальная проблема. Предположим, что C#get() переопределяет A#get()

A a = new C();
Object obj = a.get();  // actually calling C#get()

но C#get() требуется T - что должно быть T? Невозможно определить.

Вы можете возразить, что T не требуется из-за стирания. Это верно сегодня. Однако стирание считалось "временным" обходным решением. Система типов в большинстве случаев не предполагает стирания; он на самом деле тщательно разработан так, чтобы он мог быть полностью "reifiable", то есть без стирания, в будущей версии Java без нарушения существующего кода.