Какой смысл разрешать свидетелям типов во всех вызовах методов?
Скажем, у нас есть два метода:
public static <T> T genericReturn() { /*...*/ }
public static String stringReturn() { /*...*/ }
При вызове любого метода вы можете предоставить свидетеля типа независимо от наличия или отсутствия каких-либо требований:
String s;
s = Internet.<String>genericReturn(); //Type witness used in return type, returns a String
s = Internet.<Integer>stringReturn(); //Type witness ignored, returns a String
Однако я не вижу реалистичного использования этого в Java вообще, если тип не может быть выведен (что обычно указывает на большую проблему). Кроме того, тот факт, что он просто игнорируется, когда он не используется должным образом, кажется противоречивым. Так какой смысл иметь это в Java вообще?
Ответы
Ответ 1
Из JLS §15.2.12.1:
- Если вызов метода содержит явные аргументы типа, а элемент - общий метод, то количество аргументов типа равно количеству параметров типа метода.
В этом разделе подразумевается, что не общий метод может быть потенциально применим к вызову, который предоставляет аргументы явного типа. Действительно, это может оказаться применимым. В этом случае аргументы типа будут просто проигнорированы.
Далее следует обоснование
Это правило связано с проблемами совместимости и принципами взаимозаменяемости. Поскольку интерфейсы или суперклассы могут генерироваться независимо от их подтипов, мы можем переопределить общий метод с не общим. Однако переопределяющий (не общий) метод должен быть применим к вызовам общего метода, включая вызовы, явно передающие аргументы типа. В противном случае подтип не будет подставляться под свой обобщенный супертип.
Вдоль этой линии рассуждений, позвольте построить exmaple. Предположим, что в Java1.4 JDK имеет класс
public class Foo
{
/** check obj, and return it */
public Object check(Object obj){ ... }
}
Некоторые пользователи написали собственный класс, который расширяет Foo
и переопределяет метод check
public class MyFoo extends Foo
{
public Object check(Object obj){ ... }
}
Когда Java1.5 вводит дженерики, Foo.check
генерируется как
public <T> T check(T obj)
Цель амбициозной обратной сопоставимости требует, чтобы MyFoo
все еще компилируется в Java1.5 без изменений; и MyFoo.check[Object->Object]
по-прежнему является основным методом Foo.check[T->T]
.
Теперь, согласно вышеупомянутому обоснованию, поскольку этот компилятор
MyFoo myFoo = new MyFoo();
((Foo)myFoo).<String>check("");
Это тоже должно компилироваться
myFoo.<String>check("");
хотя MyFoo.check
не является общим.
Это звучит как растяжка. Но даже если мы купим этот аргумент, решение все еще слишком широкое и чрезмерное. JLS могла бы затянуть его так, чтобы myFoo.<String,String>check
и obj.<Blah>toString()
были незаконными, поскольку атрибут параметра типа не совпадает. Вероятно, у них не было времени, чтобы сгладить это, поэтому они просто взяли простой маршрут.
Ответ 2
Вам нужен тип свидетеля (тип в алмазе), когда вывод типа не будет работать (см. http://docs.oracle.com/javase/tutorial/java/generics/genTypeInference.html)
Пример, приведенный для этого, заключается в том, что такие последовательные вызовы, как:
processStringList(Collections.emptyList());
где processStringList определяется как:
void processStringList(List<String> stringList)
{
// process stringList
}
Это приведет к ошибке, потому что не может использовать a List<Object>
для List<String>
. Таким образом, свидетель требуется. Хотя, вы можете сделать это несколькими шагами, но это может быть гораздо более удобным.
Ответ 3
Интересно, почему "Type Witness" был брошен в Java?: D
Чтобы понять это, мы должны начать рассказ из Type Inference.
Вывод типа - это способность компилятора Java, чтобы посмотреть на каждый вызов метода и соответствующее объявление, чтобы определить аргумент типа (или аргументы), который делает обращение применимым. Алгоритм вывода определяет типы аргументов и, если доступно, тип, которому назначается результат, или возвращается. Наконец, алгоритм вывода пытается найти наиболее специфический тип, который работает со всеми аргументами.
Если вышеприведенный алгоритм все еще не может определить тип, у нас есть "Свидетель типа", чтобы явно указать, какой тип нам нужен. Например:
public class TypeWitnessTest {
public static void main(String[] args) {
print(Collections.emptyList());
}
static void print(List<String> list) {
System.out.println(list);
}
}
Вышеприведенный код не компилируется:
TypeWitnessTest.java:11: error: method print in class TypeWitnessTest cannot be applied to given types;
print(Collections.emptyList());
^
required: List<String>
found: List<Object>
reason: actual argument List<Object> cannot be converted to List<String> by method invocation conversion
1 error
Итак, у вас есть Type Witness для спасения от этого:
public class TypeWitnessTest {
public static void main(String[] args) {
print(Collections.<String>emptyList());
}
static void print(List<String> list) {
System.out.println(list);
}
}
Это компилируется и работает отлично, однако это было улучшено в Java 8
:
JEP 101: Обобщенный вывод целевого типа
PS: Я начал с основ, чтобы другие читатели StackOverflow также могли принести пользу.
ИЗМЕНИТЬ
Свидетельство о типе на неосновных свидетелях!
public class InternetTest {
public static void main(String[] args) {
String s;
s = Internet.<String>genericReturn(); //Witness used in return type, returns a string
s = Internet.<Integer>stringReturn(); //Witness ignored, returns a string
}
}
class Internet {
public static <T> T genericReturn() { return null; }
public static String stringReturn() { return null; }
}
Я попытался имитировать пример @Rogue, используя javac 1.6.0_65
, но он не смог выполнить компиляцию со следующей ошибкой:
javac InternetTest.java
InternetTest.java:5: stringReturn() in Internet cannot be applied to <java.lang.Integer>()
s = Internet.<Integer>stringReturn(); //Witness ignored, returns a string
^
1 error
@Rogue: Если вы использовали какую-то предыдущую версию, чем использовали, тогда дайте мне знать вашу версию javac. И если бы вы были тогда, это не разрешено сейчас.: P
Ответ 4
Это из-за обратной и/или передовой совместимости (на уровне источника).
Представьте себе что-то вроде введения общих параметров для некоторых классов в Swing в JDK 7. Это может случиться и с методами (даже с ограничениями). Если что-то оказалось плохой идеей, вы можете удалить их, и источник, использующий его, все равно будет компилироваться. По-моему, это причина, по которой это разрешено, даже если оно не используется.
Гибкость, однако, ограничена. Если вы вводили параметры типа с типами n
, вы не можете впоследствии изменять типы m
(в соответствии с исходным кодом), если m != 0
или m != n
.
(Я понимаю, это может не ответить на ваш вопрос, поскольку я не дизайнер Java, это только моя идея/мнение.)