Почему этот общий метод с привязкой возвращает какой-либо тип?
Почему компилируется следующий код? Метод IElement.getX(String)
возвращает экземпляр типа IElement
или его подclassов. Код в основном classе вызывает метод getX(String)
. Компилятор позволяет сохранить возвращаемое значение переменной типа Integer
(которая явно не находится в иерархии IElement
).
public interface IElement extends CharSequence { T getX(String value); } public class Main { public void example(IElement element) { Integer x = element.getX("x"); } }
Не должен ли возвращаемый тип быть экземпляром IElement
– даже после стирания типа?
Байт-код метода getX(String)
:
- Можно ли эмулировать шаблон ?
- Использование ключевого слова var в C #
- Вызов статических общих методов
- Как определить применение Lisp в Haskell?
- Сколько стоит слишком много с ключевым словом C ++ 11 auto?
public abstract T getX(java.lang.String); flags: ACC_PUBLIC, ACC_ABSTRACT Signature: #7 // (Ljava/lang/String;)TT;
Изменить: заменить String
последовательно на Integer
.
- Почему я должен предоставлять явно общие типы параметров. В то время как компилятор должен выводить тип?
- Вывод типа параметра шаблона c ++
- Почему конструктор C # не может вывести тип?
- Collections.emptyList () возвращает List ?
- Возможна ли неполная запись типа в C #?
- Функции с общими типами параметров
- decltype и круглые скобки
- Как написать функцию для общих чисел?
Это действительно законный тип вывода *.
Мы можем свести это к следующему примеру ( Идеал ):
interface Foo { F bar(); public static void main(String[] args) { Foo foo = null; String baz = foo.bar(); } }
Компилятору разрешено вывести (бессмысленный, действительно) тип пересечения String & Foo
потому что Foo
– это интерфейс. Для примера в вопросе выведен Integer & IElement
.
Это бессмысленно, потому что преобразование невозможно. Мы не можем делать такие роли:
// won't compile because Integer is final Integer x = (Integer & IElement) element;
Тип вывода в основном работает с:
- набор переменных вывода для каждого из параметров типа метода.
- набор границ, которые должны соответствовать.
- иногда ограничения , которые сводятся к границам.
В конце алгоритма каждая переменная разрешена к типу пересечения на основе связанного набора, и если они действительны, компиляция вызывает.
Процесс начинается в 8.1.3 :
Когда начинается вывод, связанный набор обычно генерируется из списка объявлений параметров типа
P 1 , ..., P p
и связанных переменных выводаα 1 , ..., α p
. Такое связанное множество строится следующим образом. Для каждого l (1 ≤ l ≤ p) :
[…]
В противном случае для каждого типа
T
ограниченного символом&
в TypeBound , в наборе […] появляется оценкаα l <: T[P 1 :=α 1 , ..., P p :=α p ]
].
Итак, это означает, что сначала компилятор начинается с привязки F <: Foo
(что означает, что F
является подтипом Foo
).
Переходя к 18.5.2 , рассматривается тип возвращаемого объекта:
Если вызов является поли-выражением, [...] пусть
R
- тип возвратаm
,T
- целевой тип вызова, а затем:
[...]
В противном случае формула ограничения
‹R θ → T›
уменьшается и включается в [связанный набор].
Формула ограничения ‹R θ → T›
сводится к другой оценке R θ <: T
, поэтому мы имеем F <: String
.
Позже они решаются согласно 18.4 :
[...] экземпляр-кандидат
T i
определен для каждогоα i
:
- В противном случае, где
α i
имеет собственные верхние границыU 1 , ..., U k
,T i = glb(U 1 , ..., U k )
.Оценки
α 1 = T 1 , ..., α n = T n
включены в текущий связанный набор.
Напомним, что наш набор оценок F <: Foo, F <: String
. glb(String, Foo)
определяется как String & Foo
. Это, по-видимому, законный тип для glb , который требует только:
Это ошибка времени компиляции, если для любых двух classов ( не интерфейсов )
V i
иV j
V i
не является подclassомV j
или наоборот.
В заключение:
Если разрешение преуспевает с экземплярами
T 1 , ..., T p
для переменных выводаα 1 , ..., α p
, пустьθ'
- подстановка[P 1 :=T 1 , ..., P p :=T p ]
. Затем:
- Если необработанное преобразование не было необходимым для применения метода, то тип вызова
m
получается путем примененияθ'
к типуm
.
Поэтому метод ссылается на String & Foo
как тип F
Разумеется, мы можем назначить это String
, поэтому невозможно преобразовать Foo
в String
.
Тот факт, что String
/ Integer
являются конечными classами, по-видимому, не рассматривается.
* Примечание: стирание типа / полностью не связано с проблемой.
Кроме того, хотя это компилируется и на Java 7, я думаю, что разумно сказать, что нам не нужно беспокоиться о спецификации там. Вывод типа Java 7 был по существу менее сложной версией Java 8. Он компилируется по тем же причинам.
Как добавление, хотя и странно, это, вероятно, никогда не вызовет проблемы, которая еще не была. Редко полезно писать общий метод, возвращаемый тип которого выведен исключительно из возвращаемой цели, так как только null
может быть возвращен из такого метода без кастинга.
Предположим, например, что у нас есть некоторый аналог карты, в котором хранятся подтипы определенного интерфейса:
interface FooImplMap { void put(String key, Foo value); F get(String key); } class Bar implements Foo {} class Biz implements Foo {}
Уже совершенно допустимо сделать ошибку, например:
FooImplMap m = ...; m.put("b", new Bar()); Biz b = m.get("b"); // casting Bar to Biz
Итак, тот факт, что мы также можем делать Integer i = m.get("b");
не является новой возможностью для ошибки. Если бы мы программировали такой код, это было уже потенциально необоснованным.
Как правило, параметр типа должен быть только выведен из целевого типа, если нет причин для его привязки, например Collections.emptyList()
и Optional.empty()
:
private static final Optional EMPTY = new Optional<>(); public static Optional empty() { @SuppressWarnings("unchecked") Optional t = (Optional ) EMPTY; return t; }
Это A-OK, потому что Optional.empty()
может ни производить, ни потреблять T