Java: универсальные интерфейсы и функции в таких - PullRequest
2 голосов
/ 22 октября 2010

Я все еще экспериментирую с тем, как Java обрабатывает дженерики.Я наткнулся на тот факт / проблема / вещь, что если у вас есть универсальный интерфейс, такой как A<T>, вы не сможете потом проверить, действительно ли какой-то объект реализует A<B> или A<C>.

.может вызвать реальные проблемы.

Теперь я попробовал этот код:

static interface A<T> { void foo(T obj); }
static class B implements A<B> {
    public void foo(B obj) { obj.bar(); }       
    void bar() {}
}
static {
    assert (new B() instanceof A<?>);
    ((A<?>) new B()).foo(new Object());
}

Это дает мне эту ошибку (для foo -call):

The method foo(capture#1-of ?) in the type Main.A<capture#1-of ?> is not applicable for the arguments (Object)

Интересно, почему это так.Затмение говорит мне, что подпись foo после приведения к A<?> равна foo(? obj), что, как я думал, совпадает с foo(Object obj).

assert успешно.

Я попытался выяснить, в какой момент он точно вызывает объект, когда я вызываю функцию foo.

Кроме того, как я могу вызвать foo из A<?>?Это то, что я действительно должен уметь делать.Или это невозможно с каким-либо другим параметром, кроме null?

Более реальный пример, где я действительно задаюсь вопросом: я часто использую интерфейс Comparable<T>.Этот случай на самом деле еще сложнее;Я мог бы открыть другой вопрос об этом, если это здесь не отвечает.

Ответы [ 7 ]

10 голосов
/ 22 октября 2010

Интересно, почему это так?Eclipse говорит мне, что подпись foo после приведения к A - это foo (? Obj), который, как я думал, совпадает с foo (Object obj).Представьте, что A<T> равно List<T>, foo(T) равно add(T), и поэтому A<?> равно List<?>.Должны ли вы быть в состоянии это сделать?

 List<String> strList = new ArrayList<String>();
 List<?> wcList = strList;
 wcList.add(Integer.valueOf(6));  //possible if add(?) is same as add(Object)

 //...
 String str = strList.get(0);

Конечно, нет, так как вы получите ClassCastException в последней строке.

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

3 голосов
/ 22 октября 2010

Если у вас есть "foo(? obj)", то ? может быть любого типа. Если это сказать String, то вы не можете передать, скажем, Integer ему. Все, что вы можете передать, это null.

Как правило, следует избегать применения и использования instanceof, за исключением случаев, когда это неизбежно (например, реализация equals), особенно при использовании дженериков.

0 голосов
/ 24 октября 2010

Многие разъяснили этот момент по поводу ?, но никто не ответил на главный вопрос, так что вот оно:

Можно назвать A#foo так:

((A) new B()).foo(new Object());

Затем выполняется приведение внутрь из foo.

0 голосов
/ 23 октября 2010

Это простое объяснение:

A<?> означает A неизвестного параметризованного типа. Дано

A<?> ref = new B()

ref может указывать на любой A: A<Object, A<String>, что угодно. Поэтому вы не можете вызвать A.foo(new Object()), потому что в случае ссылки A<?> нет способа узнать, какой параметр ref.foo() принимает. На самом деле единственная действительная вещь - это ref.foo(null).

0 голосов
/ 22 октября 2010

Нет, foo(? obj) на самом деле не то же самое, что foo(Object obj). Разница в том, что когда тип параметра Object, он явно заявляет, что любой тип объекта является допустимым. С типом параметра ? метод утверждает, что не знает , какой тип объекта является допустимым ... поэтому ничего не допустимо, кроме null.

Причина этого становится очевидной, когда вы рассматриваете List, а не произвольный интерфейс. Посмотрите на этот метод:

public void foo(List<?> list) {
  list.add(...); // what can we add here?
}

? указывает, что любой тип List является приемлемым ... переданный List может быть List<String>, List<Integer> или List<Map<Foo, Bar>>. Там нет никакого способа узнать. Обратите внимание, что это проблема только методов, которые используют универсальных параметров, таких как add или ваш метод foo. Для методов, которые производят (возвращают) объект универсального типа, можно использовать такой метод и назначить результат как Object. Это, в отличие от метода потребителя, не может повредить внутреннее состояние универсального объекта.

Также обратите внимание, что когда вы звоните ((A<?>) new B()).foo(new Object()), вы пытаетесь сделать что-то незаконное ... если что-то (например, Object), которое не является B, должно быть передано B ' s foo метод, он взорвется во время выполнения. Компилятор правильно запрещает вам делать это.

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

0 голосов
/ 22 октября 2010

Когда вы проверяете, является ли экземпляр B экземпляром A<?>, вы просто делаете то же самое, что и new B() instanceof A.Вы на самом деле не проверяете, как установлена ​​T, это просто «что-то».

Позже в коде с приведением вы скажете, что вы будете использовать B как A<?>, поэтомупеременная будет иметь характеристику A, но универсальный тип по-прежнему «что-то».Это «что-то» существует и, вероятно, является указанным классом, но вам не нужен точный тип.

Вот почему, когда вы используете метод foo(), который принимает T в параметре, вы можете 't передать параметр «что-то», потому что вы не знаете, что это такое, это может быть Object, но это может быть что-то еще.

Из-за этого компилятор сообщает вам foo(capture#1-of ?) isnнеприменимо для аргумента Object.Необходимым параметром является «что-то», но не обязательно Object.


Тогда, когда вам понадобится эта функция?

Например, если вы работаете с картой, если вына самом деле не заботится о типе ключа (например, если вы работаете только с методом values()), вы можете сделать что-то вроде этого:

Map<?, V> m 

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

0 голосов
/ 22 октября 2010

Компилятор корректен, поскольку он выполняет приведение к компиляции во время компиляции.

((A<?>) new B()).foo(new Object());

ошибочно, потому что ожидаемый компилятор

((A<?>) new B()).foo(A object)....

означает, что ему нужно что-то типа А или его потомков. Object является родителем A и не соответствует типу параметра теста компиляции для компиляции.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...