#java #generics
#java #generics
Вопрос:
У меня следующий вопрос относительно приведенного ниже кода:
public class GenericBridgeMethods <T> {
public static void main(String[] args) {
List obj = new ArrayList<Integer>();
List <?> l1 = (List<?>) obj; // clause 1
GenericBridgeMethods <?> g1 = (GenericBridgeMethods<?>) obj; // clause 2
}
}
a. Пункт 1, конечно, не выдаст предупреждение о непроверенном приведении
b. Пункт 2 также не выдал предупреждение о непроверенном приведении
Я заметил, что литой из сырьевого типа (объект) в любой reifiable типа (как GenericBridgeMethods или GenericBridgeMethods <?>) не дают непроверенное приведение предупреждение. Если вы запустите этот код, в пункте 2 возникнет ошибка времени выполнения.
Разве компилятор не должен выдавать предупреждение в пункте 2
РЕДАКТИРОВАТЬ 1:
ArrayList a1 = new ArrayList<Integer>(); // clause 3
Number n1 = (Number)a1; // clause 4 ERROR
Comparable c1 = (Comparable)a1; // clause 5
List l1 = new ArrayList<Integer>(); // clause 6
Number n2 = (Number)l1; // clause 7
Comparable c2 = (Comparable)l1; // clause 8
Кто-нибудь может объяснить, почему только в пункте 4 есть ошибка?
Ответ №1:
Ну, во-первых, GenericBridgeMethods
как вы его определили, T
это не повторяемый тип. Повторяемый означает, что тип будет закодирован в классе и будет доступен во время выполнения. Это не относится T
к .
Пункт 2 не выдает предупреждение во время выполнения, потому что оно проверено: будет проверка во время выполнения, которая obj
присваивается GenericBridgeMethods
типу. Поскольку вы выбрали подстановочный знак в качестве параметра типа, проверять ничего не T
нужно.
Если, с другой стороны, вы сделали что-то подобное:
GenericBridgeMethods<String> g1 = (GenericBridgeMethods<String>) obj;
это выдаст вам предупреждение о непроверенном присвоении, потому что факт, который obj
является a GenericBridgeMethods
из String
s, не может быть проверен во время выполнения. Однако то же самое предупреждение появилось бы, если бы вы сделали это:
List<String l1 = (List<String>) obj;
Редактировать
Если вы не понимаете, почему компилятор позволяет вам пытаться преобразовать a List
в a GenericBridgeMethods
, ответ заключается в том, что компилятор не может знать всю иерархию GenericBridgeMethods
и ее подклассы. Может существовать подкласс GenericBridgeMethods
этого implements List
, и в этом случае приведение может быть законным.
Однако вы получите ошибку компиляции, если вы создали GenericBridgeMethods
окончательный класс (и, таким образом, предотвратили его наличие подклассов). В этом случае вы получите ошибку необратимых типов.
Просто чтобы показать вам, что ваш вопрос имеет мало общего с повторяемыми типами и обобщениями, взгляните на это:
public static void main(String[] args) {
List obj = new ArrayList<Integer>();
//this is allowed (no warning), even though it will fail at runtime
CharSequence sequence = (CharSequence) obj;
}
Вы можете явно привести obj
к a CharSequence
, даже если вы знаете, что это приведет к сбою во время выполнения. Причина в том, что все, что знает компилятор, это то, что obj
это тип List
. Поскольку List
это интерфейс, может быть реализация CharSequence
, которая также является a List
, и поэтому приведение должно быть разрешено.
Каждое явное приведение несет в себе определенную степень вероятности сбоя во время выполнения. В противном случае это было бы избыточным приведением, и компилятор должен позволить вам опустить явное приведение.
Редактировать — относительно вашего «редактировать # 1»
ArrayList a1 = new ArrayList<Integer>(); // clause 3
Number n1 = (Number)a1; // clause 4 ERROR
Comparable c1 = (Comparable)a1; // clause 5
List l1 = new ArrayList<Integer>(); // clause 6
Number n2 = (Number)l1; // clause 7
Comparable c2 = (Comparable)l1; // clause 8
Вам интересно, почему не компилируется только «пункт 4». Я думаю, что я уже объяснял это выше и в комментариях, но я пройдусь по этому конкретному примеру для вас шаг за шагом.
ArrayList a1 = new ArrayList<Integer>(); // clause 3
Number n1 = (Number)a1; // clause 4 ERROR
Приведение a1
к Number
не работает, потому Number
что и ArrayList
оба являются классами, а не интерфейсами. Поскольку Java не допускает наследования от нескольких классов, для того, чтобы объект был экземпляром обоих Number
и ArrayList
, Number
должен быть подклассом ArrayList
или наоборот. Известно, что это неверно во время компиляции.
ArrayList a1 = new ArrayList<Integer>(); // clause 3
Comparable c1 = (Comparable)a1; // clause 5
Поскольку Comparable
это интерфейс, подклассом ArrayList
может быть a Comparable
.
List l1 = new ArrayList<Integer>(); // clause 6
Number n2 = (Number)l1; // clause 7
Since List
— это интерфейс Number
, который может реализовать подкласс List
. Компилятор не знает при проверке приведения, которое l1
содержит ArrayList
.
Комментарии:
1. Приведение в пунктах 1 и 2 — это приведение к повторяемому типу. Это то, что я имел в виду. Пункт 2 ДЕЙСТВИТЕЛЬНО выдает ошибку времени выполнения! Это ошибка- java.lang. ClassCastException: java.util. ArrayList не может быть приведен к GenericBridgeMethod .
2. @yapkm01: В библиотеках Java нет такого понятия, как предупреждение во время выполнения. Это, конечно, сбой , потому что ArrayList не является GenericBridgeMethod . Но компилятор не может этого знать, поскольку может существовать подкласс
GenericBridgeMethod
этого implementsList
.3. Извините, я изменил его на ошибку. ДА. Это выдает ошибку времени выполнения. Если это так, компилятор должен был выдать предупреждение о непроверенном приведении
4. @yap: непроверенные приведения имеют отношение к дженерикам. Ваш вопрос действительно не имеет ничего общего с дженериками; они затуманивают проблему. Речь идет вовсе не о повторяемости. Непроверенное предупреждение означает, что приведение фактически не будет проверять тип во время выполнения; вы не получите этого здесь, потому что, очевидно, тип проверяется во время выполнения (иначе вы бы не получили
ClassCastException
!!)5. Я сказал это в своем ответе, но я повторю: непроверенный не означает «непроверенный во время компиляции», это означает «непроверенный во время выполнения «.