Вопрос по java, language-design, raw-types, generics, generic-method – Обобщенные методы Java в обобщенных классах

28

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

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

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

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

  public  List 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 (На самом деле это можно объяснить - см. Ответ ниже):

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

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

MyGenericClass wildcard = new MyGenericClass();
String string = wildcard.doSomething("String"); // this compiles

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

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

РЕДАКТИРОВАТЬ:

Чтобы уточнить, я бы ожидал, что нетипизированный или типизированный необработанный типовой класс не будет соблюдать универсальный класс ».Параметры типа s (потому что они нет) Тем не менее, это 'Мне не понятно, почему нетипизированный или необработанный типовой класс будет означать, что универсальные методы не соблюдаются.

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

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

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

РЕДАКТИРОВАТЬ - обсуждение JLS:

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

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

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

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

// bound class types
public class MyBoundedGenericClass {
  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 применяется к универсальным методам. Универсальный метод »тип s ( в более раннем примере) не является нетипизированным, так какТип s определяется параметрами метода - только класс является нетипизированным / необработанным.

stackoverflow.com/questions/14882003/... :) Affe
Извините, немного ОТ. Но я попытался найти его, является ли статический класс новой конструкцией? AFAIK он не будет компилироваться в Java 6 (будет ли в Java 7?). H.Rabiee
@hajder - извините, я изначально написал их как вложенные классы в тестовом классе (следовательно, сделав их статичными), и пренебрег их удалением, когда яПед к вопросу. Сейчас удалено. amaidment
@PaulBellora - спасибо. Первый очень связан - хотя мой обильный поиск SOнайти его, так что спасибо за ссылку. Второй менее актуален, так как он охватывает только общие классы, а не универсальные методы. amaidment

Ваш Ответ

6   ответов
8

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

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

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

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

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

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

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

Некоторые мысли разработчиков Java очевидны здесь:

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

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

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

Запрос состоит в том, чтобы изменить стирание типа так, чтобы в объявлении типаFoo, стирание только удаляетT из параметризованных типов. Затем так случилось, что внутриMapдекларация,Set

есть также этот запрос:bugs.sun.com/bugdatabase/view_bug.do?bug_id=6256320 когда кто-то запрашивает описанное вами поведение - стирает только общие параметры класса, а не другие общие - но оно было отклонено Graham Griffiths
Действительно - заслуженная награда за награду. amaidment
+1 Хорошее исследование - подумайте о цитировании оценок связанных ошибок / запросов в вашем ответе. @maidment Я рекомендую этот ответ за награду, потому что это 'самый близкий тысобираемся узнать намерения дизайнеров языка. Paul Bellora
Я бы согласился, что обратная совместимость является хорошей причиной - этопросто этоне ясно, почему стирание типавсе генерики были бы необходимы для достижения этой цели. +1 за отчет об ошибке, в котором эта проблема признана ошибкой, но ее исправление влияет на совместимость (что не совсем то же самое, что сказать, что это было необходимо для совместимости ...). К сожалению, похоже, что исправление не далоT еще не был включен в выпущенную версию ... amaidment
0

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

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

List list = new ArrayList();

Это предварительный универсальный код, и как только он был представлен, он был интерпретирован как необработанный универсальный тип, эквивалентный:

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

Поскольку ? Безразлично»не иметьextends или жеsuper Ключевое слово после него преобразуется в это:

List<object> list = new ArrayList<>();
<p>Версия<code>List</code> который был использован до того, как дженерики использовали<code>Object</code> для ссылки на элемент списка, и эта версия также использует<code>Object</code> для ссылки на элемент списка, поэтому обратная совместимость сохраняется.</p></object>
Фрагмент JLS из 4.8 (из вопроса, который вы связываете) охватывает конструкторы, методы экземпляра и поля-члены - общие методы - это всего лишь частный случай методов экземпляра в целом. Так мне кажется тбодтьs ответ охватывает вашу ситуацию? Graham Griffiths
не 'для обратной совместимости достаточная причина? Некоторые мысли здесь очевидны:bugs.sun.com/view_bug.do?bug_id=6400189 (ошибка + исправление, показывающее, что методтип возвращаемого значения рассматривается как часть методатип s для целей стирания этого типа) Graham Griffiths
Ваш ответ дает хорошее объяснение того, почему Java игнорирует универсальные типы классов в нетипизированном классе. Тем не менее, вопрос на самом деле касается универсальных методов, для которых типизация также игнорируется в нетипизированном классе и которые у вас нет.т покрыты. amaidment
0

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

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

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

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

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

Теперь класс и этоs метод сделан общим:

public class MyGenericClass<t> {
    public <k extends="" ktype=""> K doSomething(K k){
        return k;
    }
}
</k></t>

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

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

1

makeSingletonList(...) компилируется покаdoSomething(...) не:

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

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

...эквивалентно:

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

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

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

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

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

4

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

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

Спасибо - в моем случае мне нужны универсальные параметры типа класса. Oни'не используется в приведенном выше примере, как я хотел краткий SSCCE. Кроме того, этоМне не ясно, почему стирание всех обобщений в классе raw обеспечивает обратную совместимость. amaidment
@amaidment предположим, что у вас есть этот интерфейс:interface C { K foo(); }, Поведение, объясненное в ответе, гарантирует, что некоторые предварительныеC может быть безопасно автоматически "обобщен» без изменений старого кода. В противном случае пользователиC интерфейс должен вызватьfoo как это:c.foo() MAnyKey
@ MAnyKey - вы можете опубликовать свой ответ как ответ ... в любом случае, я думаю, что вы 'мы упустили суть. Если у вас нетипизированная реализацияCне только класс дженериков (T) относиться какObject (что и следовало ожидать), но также общий метод ( K foo()) будет рассматриваться как нетипизированный, поэтому реализацияfoo() должно бытьpublic Object foo(){...}, который возвращается к первоначальному вопросу. amaidment
2

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

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);
    }
}
</k></k></k></t>

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

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

Это нене противоречит всему, что я написал. Да, типы методов не зависят от типов классов. Но если типы методов ограничены типами классов, компилятор должен отказаться от всего этого, когда класс используется без обобщений. MAnyKey
+1 Один пример использования типа метода, который зависит от типа класса, является достаточной причиной для исключения обобщенных элементов в любом месте класса, когда используется необработанный тип. yair
Я не согласен - компилятор недолжен отбрасыватьвсе общие типы (хотя, кажется, это то, что было сделано). Скорее компилятор мог бы лечитьтолько нетипизированные дженерики какObject, Тот'что я ожидал, но этоне то, что происходит, и, следовательно, остается вопрос ... почемувсе общие типы удалены? amaidment
Нет - типы методов () не зависят от типов классов () (если нет других ограничений, например, согласно вашему примеру) и независимы друг от друга. Таким образом, мы могли бы иметь классC с методомpublic T doSomething(T t){ return t; }и это можно назватьnew C().doSomething("String"), Это скомпилируется (но будет не очень понятно для будущих разработчиков ...), поскольку универсальный тип метода берется вместо универсального типа класса, и они независимы. amaidment
Я думаю, что полное стирание типа имеет смысл в сочетании с упомянутой обратной совместимостью. Когда объект создается без аргументов универсального типа, можно предположить, что использование объекта происходит в не универсальном коде, и поэтому все универсальные типы заменяются наObject сразу. Я думаю этоКомпилятор не может угадать каждый вызов, если он должен быть универсальным. Что произойдет, если вы сделаете методы статичными? André Stannek

Похожие вопросы