Общие методы Java в классах generics

если вы создаете универсальный класс в Java (класс имеет параметры универсального типа), можете ли вы использовать общие методы (метод принимает параметры универсального типа)?

рассмотрим следующий пример:

public class MyClass {
  public <K> K doSomething(K k){
    return k;
  }
}

public class MyGenericClass<T> {
  public <K> K doSomething(K k){
    return k;
  }

  public <K> List<K> makeSingletonList(K k){
    return Collections.singletonList(k);
  }
}

как и следовало ожидать с общим методом, я могу вызвать doSomething(K) экземпляров MyClass С любого объекта:

MyClass clazz = new MyClass();
String string = clazz.doSomething("String");
Integer integer = clazz.doSomething(1);

однако, если я попытаюсь использовать экземпляры MyGenericClass без определение универсального типа, Я зову doSomething(K) возвращает Object, независимо от того, какой K был принят в:

MyGenericClass untyped = new MyGenericClass();
// this doesn't compile - "Incompatible types. Required: String, Found: Object"
String string = untyped.doSomething("String");

как ни странно, он будет компилироваться, если возвращаемый тип является универсальным классом, например List<K> (на самом деле, это можно объяснить - см. ответ ниже):

MyGenericClass untyped = new MyGenericClass();
List<String> list = untyped.makeSingletonList("String"); // this compiles

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

MyGenericClass<?> wildcard = new MyGenericClass();
String string = wildcard.doSomething("String"); // this compiles
  • есть ли веская причина, по которой вызов универсального метода в нетипизированном универсальном классе не должен работа?

  • есть ли какой-то умный трюк, связанный с общими классами и общими методами, которые мне не хватает?

EDIT:

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

это оказывается, этот вопрос уже поднимался на SO, c.f. этот вопрос. Ответы на это объясняют, что когда класс нетипизирован / в его сырой форме,все дженерики удаляются из класса, включая набор универсальных методов.

однако на самом деле нет объяснения, почему это так. Поэтому позвольте мне уточнить мой вопрос:

  • почему Java удаляет общий метод ввода на нетипизированном или сыром типе generic занятия? Есть ли для этого веская причина, или это просто недосмотр?

EDIT-обсуждение JLS:

было предложено (в ответ на предыдущий вопрос SO и на этот вопрос), что это рассматривается в JLS 4.8, в которой говорится:

тип конструктора (§8.8), метода экземпляра (§8.4, §9.4) или нестатического поля (§8.3) M необработанного типа C, который не наследуется от его суперклассов или суперинтерфейсов, является необработанный тип, соответствующий стиранию его типа В общем объявлении, соответствующем C.

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

// unbound class types
public class MyGenericClass<T> {
  public T doSomething(T t) { return t; }
}
MyGenericClass untyped = new MyGenericClass();
Object t = untyped.doSomething("String");

// bound class types
public class MyBoundedGenericClass<T extends Number> {
  public T doSomething(T t) { return t; }
}
MyBoundedGenericClass bounded = new MyBoundedGenericClass();
Object t1 = bounded.doSomething("String"); // does not compile
Number t2 = bounded.doSomething(1); // does compile

хотя общие методы являются методами экземпляра, мне это не ясно это JLS 4.8 применяется к общим методам. Тип универсального метода (<K> в предыдущем примере) не является нетипизированным, так как его тип определяется параметрами метода - только класс нетипизирован / raw-типизирован.

6 ответов


"для обратной совместимости" кажется достаточной причиной для стирания типов классов generic types - это необходимо, например, чтобы позволить вам вернуть нетипизированный список и передать его в какой-то устаревший код. Расширение этого до общих методов кажется сложным подзапросом.

фрагмент JLS из 4.8 (который вы цитируете) охватывает конструкторы, методы экземпляра и поля - члены-общие методы являются лишь частным случаем методов экземпляра в целом. Так что, похоже, ваше дело покрытый этим фрагментом.

адаптация JLS 4.8 к этому конкретному случаю:

тип универсального метода является необработанным типом, который соответствует стирание его типа В общем объявлении, соответствующем C.

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

возможно, могут возникнуть проблемы, когда общие параметры класса взаимодействуют с общими параметрами метода - в вашем коде они полностью независимы, но вы можете представить себе другие случаи, когда они назначаются / смешиваются вместе. Я думаю, стоит отметить, что использование raw-типов не рекомендуется, согласно JLS:

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

некоторые из мышления разработчиков java очевидны здесь :

http://bugs.sun.com/view_bug.do?bug_id=6400189

(ошибка + исправление, показывающее, что тип возврата метода рассматривается как часть типа метода для целей стирания этого типа)

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

в запрос заключается в изменении стирания типа так, чтобы в объявлении типа Foo<T>, стирания удаляет только T из параметризованных типов. Тогда так бывает, что внутри Map<K,V>с декларацией, Set<Map.Entry<K,V>> стирает в Set<Map.Entry>.

но если Map<K,V> был метод, который взял type Map<String,V>, его стирание будет просто Map<String>. Для стирания типа изменить количество параметров типа ужасно, особенно для разрешения метода во время компиляции. Мы абсолютно не собираемся принимать это запрос.

слишком много ожидать, чтобы иметь возможность использовать необработанные типы (Map), все еще получая некоторую типобезопасность дженериков (Set<Map.Entry>).


С Java Generics, если вы используете сырую форму универсального класса, то все дженерики в классе, даже несвязанные общие методы, такие как ваш makeSingletonList и doSomething методы, становятся сырыми. Причина, как я понимаю, заключается в обеспечении обратной совместимости с Java-кодом, написанным pre-generics.

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


я нашел одну причину полностью отказаться от дженериков (однако это не совсем хорошо). Причина: дженериков может быть ограничен. Рассмотрим этот класс:

public static class MyGenericClass<T> {
    public <K extends T> K doSomething(K k){
        return k;
    }

    public <K> List<K> makeSingletonList(K k){
        return Collections.singletonList(k);
    }
}

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

makeSingletonList компилируется, потому что Java не снят слепок с List to List<K> (однако компилятор показывает предупреждение).


это не отвечает на фундаментальный вопрос, но решает вопрос о том, почему makeSingletonList(...) компилируется в то время как doSomething(...) нет:

в нетипичной реализации MyGenericClass:

MyGenericClass untyped = new MyGenericClass();
List<String> list = untyped.makeSingletonList("String");

...составляет:

MyGenericClass untyped = new MyGenericClass();
List list = untyped.makeSingletonList("String");
List<String> typedList = list;

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

действительно, это также аналогично:

MyGenericClass untyped = new MyGenericClass();
List<Integer> list = untyped.makeSingletonList("String"); // this compiles!!!
Integer a = list.get(0);

...который компилируется, но будет бросать runtime ClassCastException при попытке и получить String значение и бросить его в Integer.


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

в качестве примера, рассмотрим этот код:

List list = new ArrayList();

это pre-generic код, и после введения дженериков это интерпретировалось как необработанный общий тип, эквивалентный:

List<?> list = new ArrayList<>();

потому что ? нет extends или super ключевое слово после него, он преобразуется в это:

List<Object> list = new ArrayList<>();

версия List который использовался до того, как дженерики использовали Object для ссылки на элемент списка, и эта версия также использует Object для ссылки на элемент списка, поэтому обратная совместимость сохраняется.


из моего комментария на MAnyKeys ответ:

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

рассмотрим этот устаревший код:

public class MyGenericClass {
    public Object doSomething(Object k){
        return k;
    }
}

называются так:

MyGenericClass foo = new MyGenricClass();
NotKType notKType = foo.doSomething(new NotKType());

теперь класс и его метод сделаны generic:

public class MyGenericClass<T> {
    public <K extends KType> K doSomething(K k){
        return k;
    }
}

сейчас выше вызывающий код не компилировать как NotKType - это не suptype из KType. Чтобы избежать этого, общие типы заменяются на Object. Хотя есть случаи, когда это не имело бы никакого значения (например, ваш пример), для компилятора, по крайней мере, очень сложно проанализировать, когда. Возможно, даже невозможно.

Я знаю, что этот сенарио кажется немного сконструированным, но я уверен, что это происходит время от времени.