2013-08-01 7 views
27

Если вы создаете общий класс в Java (у класса есть общие параметры типа), можете ли вы использовать общие методы (метод принимает общие параметры типа)?Общие методы 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. this question. Ответы на это объясняют, что, когда класс является нетипизированным/в его исходной форме, все дженериков удаляются из класса, включая типизацию общих методов.

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

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

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

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

тип конструктора (§8.8), метод экземпляра (§8.4, п. 9.4) или нестатическое поле (§8.3) М исходного типа С, не унаследованными от своего суперкласса или суперинтерфейсов является исходным типом, который соответствует стиранию его типа в родовой декларации, соответствующие 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> в более раннем примере) не является нетипизированным, так как его тип определяется параметрами метода - только класс является нетипизированным/необработанным.

+5

Возможный дубликат [Объединение сырых типов и общих методов] (http://stackoverflow.com/questions/11007723/combining-raw-types-and-generic-methods). См. Также: [Почему этот общий код Java не компилируется?] (Http://stackoverflow.com/questions/662191/why-wont-this-generic-java-code-compile) –

+0

http://stackoverflow.com/questions/14882003/generic-screw-up-non-related-collection/14882590 # 14882590 :) – Affe

+0

@PaulBellora - спасибо.Первое очень сильно связано - хотя мой обильный поиск SO не нашел его, поэтому спасибо за ссылку. Второе менее актуально, поскольку оно охватывает только общие классы - не общие методы. – amaidment

ответ

7

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

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

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

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

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

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

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

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

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

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

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

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

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

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

+0

Я бы согласился, что совместимость с совместимостью является хорошей причиной - просто неясно, зачем нужно стирать _all_ generics для достижения этого. +1 для отчета об ошибке, в котором эта проблема распознается как ошибка, но это исправление влияет на совместимость (что не совсем то же самое, что сказать, что это необходимо для совместимости ...). К сожалению, похоже, что исправление еще не включено в выпущенную версию ... – amaidment

+0

есть также этот запрос: http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6256320, где кто-то запрашивает поведение, которое вы описываете, - только стереть общие параметры класса, а не другие дженерики, но было отклонено. –

+0

+1 Хорошее исследование - подумайте о цитировании оценок связанной ошибки/запроса в вашем ответе. @amaidment Я рекомендую этот ответ за щедрость, потому что он ближе всего, когда вы узнаете намерение разработчиков языка. –

4

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

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

+0

Спасибо - в моем случае использования мне нужны общие параметры типа класса. Они не используются в приведенном выше примере, поскольку мне нужен сжатый SSCCE. Кроме того, мне непонятно, почему стирание всех дженериков в необработанном классе обеспечивает обратную совместимость. – amaidment

+0

@amaidment предположим, что у вас есть этот интерфейс: 'интерфейс C { K foo(); } '. Поведение, объясняемое в ответе, гарантирует, что некоторые предварительные «C» можно безопасно автоматически «генерировать» без старых изменений кода. В противном случае пользователи интерфейса 'C' должны вызывать' foo' следующим образом: 'c. foo() ' – MAnyKey

+0

@MAnyKey - вы можете отправить ответ в качестве ответа ... в любом случае, я думаю, вы пропустили это. Если у вас есть нетипизированная реализация 'C', не только класс generics (' T') будет обрабатываться как «Object» (что и следовало ожидать), но и общий метод (' K foo()') будет рассматриваться как нетипизированный, поэтому реализация 'foo()' должна быть 'public Object foo() {...}', которая возвращается к исходному вопросу. – amaidment

0

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

В качестве примера, рассмотрим следующий код:

List list = new ArrayList(); 

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

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

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

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

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

+0

Ваш ответ дает хорошее объяснение тому, почему Java игнорирует общие типы классов в нетипизированном классе. Тем не менее, вопрос действительно о общих методах, для которых типизация также игнорируется в нетипизированном классе и которую вы не охватили. – amaidment

+0

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

+0

не является «для обратной совместимости» достаточной причиной? Некоторые из мыслей очевидны здесь: http://bugs.sun.com/view_bug.do?bug_id=6400189 (ошибка + исправление, показывающая, что возвращаемый тип метода рассматривается как часть типа метода для целей стирания этого типа) –

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); 
    } 
} 

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

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

+0

Нет - типы методов ('') не зависят от типов классов ('') (если не указано иное, например '' в соответствии с вашим примером) и независимы друг от друга. Таким образом, мы могли бы иметь класс 'C ' с методом 'public T doSomething (T t) {return t; } ', и это можно было бы назвать« новым C () .doSomething («String») '. Это скомпилировало бы (но было бы не совсем понятно для будущих разработчиков ...), поскольку тип generic метода берется вместо класса generic type, и они независимы. – amaidment

+0

Это не противоречит тому, что я написал. Да, типы методов не зависят от типов классов. Но если типы методов ограничены классами, компилятор должен отбросить все это при использовании класса без дженериков. – MAnyKey

+0

+1 один пример использования типа метода, который зависит от типа класса, является достаточной причиной для исключения генериков в любом месте класса, когда используется тип raw. – yair

1

Это не дает ответ на основной вопрос, но решить вопрос о том, почему 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); 

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

0

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

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

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

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

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

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

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

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

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

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