🖕Добро пожаловать в мой официальный аккаунт «Брат Тонг читает исходный код», ознакомьтесь с другими статьями из серии исходного кода и поплавайте в океане исходного кода вместе с братом Тонгом.
задний план
Вчера столкнулся с такой проблемой при просмотре форума, и код:
public class GenericTest {
//方法一
public static <T extends Comparable<T>> List<T> sort(List<T> list) {
return Arrays.asList(list.toArray((T[]) new Comparable[list.size()]));
}
//方法二
public static <T extends Comparable<T>> T[] sort2(List<T> list) {
// 这里没报错
return list.toArray((T[]) new Comparable[list.size()]);
}
public static void main(String[] args) {
List<Integer> list = new ArrayList<>();
list.add(1);
list.add(2);
// 方法一调用正常
System.out.println(sort(list).getClass());
// 方法二调用报错了,这里报错了
System.out.println(sort2(list).getClass());
}
}
Эта проблема имеет следующие четыре явления:
(1) Способ 1 называется полностью нормальным;
(2) метод 2 вызывает ошибку;
(3) Место, где метод 2 сообщает об ошибке, находится вSystem.out.println(sort2(list).getClass());
эта строка вместоreturn list.toArray((T[]) new Comparable[list.size()]);
эта линия;
(4) Сообщение об ошибке[Ljava.lang.Comparable; cannot be cast to [Ljava.lang.Integer;
;
Как насчет этого? Вы имеете в виду ответ? Введите стирание? Как стереть? Трение трения?
решать
Когда я только что получил этот вопрос, я тоже был в замешательстве.return list.toArray((T[]) new Comparable[list.size()]);
Это нормально, и если вы хотите сообщить об ошибке, оба метода должны сообщать об ошибке.
С менталитетом не сдаваться и не бросать, брат Тонг провел много экспериментов и, наконец, пришел к сути дженериков, и позвольте мне объяснить.
виньетка
В первую очередь надо понимать, что массив в java не поддерживает понижающее приведение, но если он такого типа, то его можно преобразовать, см. следующий пример:
public static void main(String[] args) {
Object[] objs = new Object[]{1};
// 类型转换错误
// Integer[] ins = (Integer[]) objs;
Object[] objs2 = new Integer[]{1};
// 不报错
Integer[] ins2 = (Integer[]) objs2;
}
стирание типа
Дженерики в java — это ложные дженерики, которые действительны только во время компиляции и не имеют понятия дженериков во время выполнения.Вот простой пример:
public static void main(String[] args) {
List<String> strList = Arrays.asList("1");
List<Integer> intList = Arrays.asList(1);
// 打印:true
System.out.println(strList.getClass() == intList.getClass());
}
Вы можете видеть, что типы двух списков одинаковы.Если вы считаете этот пример недостаточно убедительным, то я приведу вам избыточный пример:
public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
List<String> strList = new ArrayList<>();
Method addMethod = strList.getClass().getMethod("add", Object.class);
addMethod.invoke(strList, 1);
addMethod.invoke(strList, true);
addMethod.invoke(strList, new Long(1));
addMethod.invoke(strList, new Byte[]{1});
// 打印:[1, true, 1, 1]
System.out.println(strList);
}
Видишь ли, я могу добавить в список строковых типов все, что захочу, а ты? !
Следовательно, общий тип в java является ложным, и во время выполнения нет отбрасывания.
Вернуться к сути
Я понимаю, что массивы нельзя принудительно уменьшить, и я также понимаю стирание типов.. Кажется, у меня все еще есть тяжелые времена в моей жизни, э-э, нет, я все еще не могу решить эту проблему?
эээ, кажется~~
Давайте рассмотрим еще один простой пример:
// GenericTest2.java(源码)
public class GenericTest2 {
public static void main(String[] args) {
System.out.println(raw("1"));
}
public static <T> T raw(T t) {
return t;
}
}
// GenericTest2.class(反编译)
public class GenericTest2 {
public GenericTest2() {
}
public static void main(String[] args) {
System.out.println((String)raw("1"));
}
public static <T> T raw(T t) {
return t;
}
}
Хм~ Вроде бы есть зацепка.После декомпиляции есть дополнительный метод построения.
А, это верно. что-нибудь еще?
Взгляните повнимательнее,System.out.println((String)raw("1"));
В этом предложении добавлено сильное преобразование String.
Это ключевой момент.В сочетании с стиранием типов во время выполнения не существует так называемого универсального типа, поэтому то, что возвращает функция raw(), на самом деле является Object, но вызывающая сторона знает, что мне нужен тип String, поэтому я знаю, как это сделать. .
Давайте рассмотрим крайний пример:
// GenericTest2.java(源码)
public class GenericTest2 {
public static void main(String[] args) {
System.out.println(raw("1"));
}
public static <T> T raw(T t) {
return (T)new Integer(1);
}
}
// GenericTest2.class(反编译)
public class GenericTest2 {
public GenericTest2() {
}
public static void main(String[] args) {
System.out.println((String)raw("1"));
}
public static <T> T raw(T t) {
return new Integer(1);
}
}
Если вы внимательно понаблюдаете, то обнаружите, что принудительная передача в методе raw()(T)new Integer(1)
сталnew Integer(1)
, принудительная передача была стерта Фактически, T здесь становится Object во время выполнения, а все типы являются подклассами Object, поэтому в принудительной передаче нет необходимости.
а также(String)raw("1")
Принудительное преобразование по-прежнему добавлено: вызывающая сторона знает, что тип — String, поэтому после возврата из raw() он принудительно преобразуется в String.
Конечно, этот код сообщит об ошибке при запуске,java.lang.Integer cannot be cast to java.lang.String
, поскольку raw() возвращает тип Integer, приведение к типу String завершается ошибкой.
Ну, основная идея такова.
А общие классы?
Все примеры, которые мы привели выше, являются универсальными методами, так что насчет универсальных классов?
Опять же, давайте посмотрим на пример:
// GenericTest3.java(源码)
public class GenericTest3 {
public static void main(String[] args) {
System.out.println(new Raw<String>().raw("1"));
}
}
class Raw<T> {
public T raw(T t) {
return (T)new Integer(1);
}
}
// GenericTest3.class(反编译)
public class GenericTest3 {
public GenericTest3() {
}
public static void main(String[] args) {
System.out.println((String)(new Raw()).raw("1"));
}
}
class Raw<T> {
Raw() {
}
public T raw(T t) {
return new Integer(1);
}
}
Как видите, производительность универсального метода точно такая же. Конечно, среда выполнения также сообщитjava.lang.Integer cannot be cast to java.lang.String
эта ошибка.
Суммировать
Обобщения в java действительны только во время компиляции.Во время выполнения только вызывающая сторона знает, какой тип необходим, и вызывающая сторона сама выполняет приведение после вызова универсального метода, а вызываемая сторона совершенно нечувствительна.
Итак, не спрашивайте вызываемого абонента, когда возникла проблема, а спросите у вызывающего абонента, как вы это назвали? !
Ответь на открытие
Для удобства возьмем вступительный вопрос здесь.
// GenericTest.java(源码)
public class GenericTest {
//方法一
public static <T extends Comparable<T>> List<T> sort(List<T> list) {
return Arrays.asList(list.toArray((T[]) new Comparable[list.size()]));
}
//方法二
public static <T extends Comparable<T>> T[] sort2(List<T> list) {
// 这里没报错
return list.toArray((T[]) new Comparable[list.size()]);
}
public static void main(String[] args) {
List<Integer> list = new ArrayList<>();
list.add(1);
list.add(2);
// 方法一调用正常
System.out.println(sort(list).getClass());
// 方法二调用报错了,这里报错了
System.out.println(sort2(list).getClass());
}
}
Здесь вроде по-другому, стало<T extends Comparable<T>>
, по сути то же самое, если отдельно написать<T>
эквивалентно<T extends Object>
из.
Тогда, давайте расширим его, вызываемый объект совершенно нечувствителен, он может только изо всех сил пытаться получить тип, который он знает, например, он может только пытаться получить Comparable, если он<T>
То, что вы получаете, это Объект.
Таким образом, второй метод возвращает фактический тип Comparable[].Как вызываемый объект, у него вообще нет проблем.
Однако вызывающая сторона знает, что мне нужен тип Integer[], потому что список имеет тип Integer[], поэтому возвращаемый тип должен быть типа Integer[], поэтому я вынудил его и сообщил об ошибке.
Это так? Давайте посмотрим на декомпилированный код:
// GenericTest.class(反编译)
public class GenericTest {
public GenericTest() {
}
public static <T extends Comparable<T>> List<T> sort(List<T> list) {
return Arrays.asList(list.toArray((Comparable[])(new Comparable[list.size()])));
}
public static <T extends Comparable<T>> T[] sort2(List<T> list) {
// 这里使用的是Comparable[]强转,所以返回的也是实打实的Comparable[]类型
return (Comparable[])list.toArray((Comparable[])(new Comparable[list.size()]));
}
public static void main(String[] args) {
List<Integer> list = new ArrayList();
list.add(1);
list.add(2);
System.out.println(sort(list).getClass());
// 数组向下转型失败
System.out.println(((Integer[])sort2(list)).getClass());
}
}
Видно, что он полностью согласуется с нашим анализом.
Одним словом навсегда
Обобщения в java действительны только во время компиляции.Во время выполнения только вызывающая сторона знает, какой тип ей нужен, и после того, как вызывающая сторона вызывает универсальный метод, он/она выполняет приведение самостоятельно.Вызываемая сторона совершенно нечувствительна, и только вызывающая сторона Он делает все возможное, чтобы получить типы, которые он знает.
В это время у меня в голове зазвучала знакомая мелодия "Одна фраза, целая жизнь...", помните сегодняшнюю фразу?
Добро пожаловать, чтобы обратить внимание на мою общедоступную учетную запись «Брат Тонг читает исходный код», проверить больше статей из серии исходного кода и поплавать в океане исходного кода с братом Тонгом.