00. Происхождение истории
«Второй брат, почему я не изучаю программирование, когда пойду в колледж?» Однажды третья сестра спросила меня из прихоти.
— Ты уверен, что хочешь быть Чэн Юанем?
«Я думаю, что у девушек как программистов есть огромное преимущество, особенно у тех, у кого милая внешность, как у меня», — Санмей начал становиться серьезным.
«Похоже, да. Кажется, я с большим энтузиазмом встречаю вопрос девушки».
«Второй брат, ты не любишь писать? Ты программист на Java. С таким же успехом ты мог бы вести колонку под названием «Научи свою сестру изучать Java». «Третья сестра, кажется, уже запланировала для меня.
«Я действительно убежден в вас, ребята, у вас есть некоторые идеи. Недавно я вел колонку в серии статей о Java, так что вы тоже можете попробовать!»
PS: Уважаемые читатели, давайте сегодня начнем с малоизвестных "дженериков"! (Подзаголовок предлагает третья сестра, а по содержанию отвечает второй брат)
01. Второй брат, зачем создавать дженерики?
Третья сестра, слушай моего брата и медленно рассказывай.
Java добавила дженерики в 5.0, и говорят, что специалистам понадобилось около 5 лет, чтобы это сделать (звучит непросто). С дженериками, особенно с использованием классов коллекций, все становится более стандартизированным.
Посмотрите на следующий простой код.
ArrayList<String> list = new ArrayList<String>();
list.add("沉默王二");
String str = list.get(0);
Но что делать перед дженериками?
Во-первых, нам нужно использовать массив Object для проектированияArraylist
Добрый.
class Arraylist {
private Object[] objs;
private int i = 0;
public void add(Object obj) {
objs[i++] = obj;
}
public Object get(int i) {
return objs[i];
}
}
Затем мы кArraylist
доступ к данным.
Arraylist list = new Arraylist();
list.add("沉默王二");
list.add(new Date());
String str = (String)list.get(0);
Вы заметили две проблемы:
- Arraylist может содержать данные любого типа (как строки, так и даты), поскольку все классы наследуются от класса Object.
- Приведение требуется при извлечении данных из Arraylist, потому что компилятор не может определить, что вы извлекаете: строку или дату.
Напротив, вы можете ясно почувствовать превосходство дженериков: используйтетип параметраРешена неопределенность элементов - коллекция типа параметра String не может хранить другие типы элементов, и не требуется принудительное преобразование типа при извлечении данных.
02. Второй брат, как создавать дженерики?
Санмей, пока вы новичок, вам нужно только знать, как использовать дженерики. Вы все еще хотите создавать дженерики? ! Однако, раз ты хочешь знать, то брат обязан.
Во-первых, давайте редизайн в соответствии со стандартом дженериковArraylist
Добрый.
class Arraylist<E> {
private Object[] elementData;
private int size = 0;
public Arraylist(int initialCapacity) {
this.elementData = new Object[initialCapacity];
}
public boolean add(E e) {
elementData[size++] = e;
return true;
}
E elementData(int index) {
return (E) elementData[index];
}
}
Общий класс — это класс с одной или несколькими переменными типа. Переменная типа, представленная классом Arraylist, имеет значение E (элемент, первая буква элемента) с использованием угловых скобок.<>
заключите его после имени класса.
Затем мы можем создать экземпляр универсального класса, заменив переменную типа конкретным типом, например строкой.
Arraylist<String> list = new Arraylist<String>();
list.add("沉默王三");
String str = list.get(0);
Типы даты также доступны.
Arraylist<Date> list = new Arraylist<Date>();
list.add(new Date());
Date date = list.get(0);
Во-вторых, мы также можем определить универсальные методы в неуниверсальном классе (или универсальном классе).
class Arraylist<E> {
public <T> T[] toArray(T[] a) {
return (T[]) Arrays.copyOf(elementData, size, a.getClass());
}
}
Однако, если честно, определение универсального метода выглядит немного неясным. Давайте сделаем снимок (примечание: тип возвращаемого значения метода и тип параметра метода должны быть хотя бы одним).
Теперь давайте вызовем универсальный метод.
Arraylist<String> list = new Arraylist<>(4);
list.add("沉");
list.add("默");
list.add("王");
list.add("二");
String [] strs = new String [4];
strs = list.toArray(strs);
for (String str : strs) {
System.out.println(str);
}
Наконец, давайте поговорим о квалификаторах универсальных переменных.extends
. Прежде чем объяснять этот квалификатор, давайте предположим, что есть три класса и определения между ними такие.
class Wanglaoer {
public String toString() {
return "王老二";
}
}
class Wanger extends Wanglaoer{
public String toString() {
return "王二";
}
}
class Wangxiaoer extends Wanger{
public String toString() {
return "王小二";
}
}
мы используем квалификаторextends
перепроектироватьArraylist
Добрый.
class Arraylist<E extends Wanger> {
}
когда мыArraylist
добавлено вWanglaoer
элемент, компилятор выдаст ошибку:Arraylist
добавить толькоWanger
и его подклассыWangxiaoer
объект, добавление его родительского класса не допускаетсяWanglaoer
.
Arraylist<Wanger> list = new Arraylist<>(3);
list.add(new Wanger());
list.add(new Wanglaoer());
// The method add(Wanger) in the type Arraylist<Wanger> is not applicable for the arguments
// (Wanglaoer)
list.add(new Wangxiaoer());
То есть определительextends
Область типов универсального типа может быть сужена.
03. Второй брат, я слышал, что у виртуальных машин нет дженериков?
Санмей, ты хорошо делаешь домашнее задание, ты даже знаешь про виртуальную машину. Брат точно может вам ответить, виртуальные машины не имеют дженериков.
Долговязый. Код Java, который мы пишем (то есть исходный код, файл с суффиксом .java), не может быть напрямую распознан операционной системой, и его необходимо сначала скомпилировать для создания файла .class (то есть файла байт-кода). . Затем виртуальная машина Java (JVM) действует как транслятор, переводя байт-код на язык, понятный операционной системе, и сообщая ей, что делать.
Как убедиться, что на виртуальной машине нет дженериков? Нам нужно декомпилировать байт-код универсального класса — настоятельно рекомендуется инструмент для декомпиляции super god Jad!
Теперь введите следующий код в командной строке (decompileArraylist
файл байт-кодаArraylist.class
).
jad Arraylist.class
После выполнения команды будет сгенерирован файл ArrayList.jad, и результат после открытия его с помощью инструмента редактирования текста выглядит следующим образом.
// Decompiled by Jad v1.5.8g. Copyright 2001 Pavel Kouznetsov.
// Jad home page: http://www.kpdus.com/jad.html
// Decompiler options: packimports(3)
// Source File Name: Arraylist.java
package com.cmower.java_demo.fanxing;
import java.util.Arrays;
class Arraylist
{
public Arraylist(int initialCapacity)
{
size = 0;
elementData = new Object[initialCapacity];
}
public boolean add(Object e)
{
elementData[size++] = e;
return true;
}
Object elementData(int index)
{
return elementData[index];
}
private Object elementData[];
private int size;
}
переменная типа<E>
Исчез Объект!
В этом случае, если общий класс использует квалификаторextends
, какой будет результат? Давайте сначала посмотримArraylist2
исходный код.
class Arraylist2<E extends Wanger> {
private Object[] elementData;
private int size = 0;
public Arraylist2(int initialCapacity) {
this.elementData = new Object[initialCapacity];
}
public boolean add(E e) {
elementData[size++] = e;
return true;
}
E elementData(int index) {
return (E) elementData[index];
}
}
файл байт-кодаArraylist2.class
Результат после декомпиляции с помощью Jad выглядит следующим образом.
// Decompiled by Jad v1.5.8g. Copyright 2001 Pavel Kouznetsov.
// Jad home page: http://www.kpdus.com/jad.html
// Decompiler options: packimports(3)
// Source File Name: Arraylist2.java
package com.cmower.java_demo.fanxing;
// Referenced classes of package com.cmower.java_demo.fanxing:
// Wanger
class Arraylist2
{
public Arraylist2(int initialCapacity)
{
size = 0;
elementData = new Object[initialCapacity];
}
public boolean add(Wanger e)
{
elementData[size++] = e;
return true;
}
Wanger elementData(int index)
{
return (Wanger)elementData[index];
}
private Object elementData[];
private int size;
}
переменная типа<E extends Wanger>
ушел, E был заменен наWanger
.
Приведенные выше два примера показывают, что виртуальная машина Java удалит переменную универсального типа и заменит ее уточненным типом (если нет ограничений, используйтеObject
).
04. Второй брат, есть проблемы со стиранием шрифта?
У третьей сестры, не говори, что стирание шрифта действительно имеет некоторые «проблемы».
Давайте посмотрим на этот код.
public class Cmower {
public static void method(Arraylist<String> list) {
System.out.println("Arraylist<String> list");
}
public static void method(Arraylist<Date> list) {
System.out.println("Arraylist<Date> list");
}
}
На поверхностном уровне сознания мы считаем само собой разумеющимся, чтоArraylist<String> list
а такжеArraylist<Date> list
— это два разных типа, потому что String и Date — это разные классы.
Но из-за стирания типа приведенный выше код не скомпилируется — компилятор выдаст ошибку (а это как раз и есть «проблемы», вызванные стиранием типа):
Erasure of method method(Arraylist) is the same as another method in type Cmower
Erasure of method method(Arraylist) is the same as another method in type Cmower
Грубо говоря, после стирания типы параметров двух методов одинаковы.
То есть,method(Arraylist<String> list)
а такжеmethod(Arraylist<Date> list)
являются методами одного и того же типа параметра и не могут существовать одновременно. переменная типаString
а такжеDate
Он автоматически исчезнет после стирания, фактический параметр метода методаArraylist list
.
Есть старая поговорка: «Лучше сто вещей, чем сто вещей увидеть», но даже если вы ее увидите, это может быть неправдой — проблема стирания дженериков может быть хорошей поддержкой этой точки зрения.
05. Второй брат, я слышал, что у дженериков есть подстановочные знаки?
Третья сестра и брат вдруг подумали, что ты очень подходишь на роль прекрасного Чэн Юаня! Вы отлично справились с подготовительной работой, знаете все связки!
Подстановочные знаки представлены английским вопросительным знаком (?). Когда мы создаем общий объект, мы можем использовать ключевое словоextends
Квалифицирует подклассы, а также может использовать ключевые словаsuper
Квалифицирует родительский класс.
Чтобы лучше объяснить подстановочные знаки, нам нужноArraylist
Сделайте некоторые улучшения.
class Arraylist<E> {
private Object[] elementData;
private int size = 0;
public Arraylist(int initialCapacity) {
this.elementData = new Object[initialCapacity];
}
public boolean add(E e) {
elementData[size++] = e;
return true;
}
public E get(int index) {
return (E) elementData[index];
}
public int indexOf(Object o) {
if (o == null) {
for (int i = 0; i < size; i++)
if (elementData[i]==null)
return i;
} else {
for (int i = 0; i < size; i++)
if (o.equals(elementData[i]))
return i;
}
return -1;
}
public boolean contains(Object o) {
return indexOf(o) >= 0;
}
public String toString() {
StringBuilder sb = new StringBuilder();
for (Object o : elementData) {
if (o != null) {
E e = (E)o;
sb.append(e.toString());
sb.append(',').append(' ');
}
}
return sb.toString();
}
public int size() {
return size;
}
public E set(int index, E element) {
E oldValue = (E) elementData[index];
elementData[index] = element;
return oldValue;
}
}
1) НовыйindexOf(Object o)
способ определить, находится ли элемент вArraylist
в месте. Обратите внимание, что параметрыObject
вместо дженериковE
.
2) Новыйcontains(Object o)
способ определить, находится ли элемент вArraylist
середина. Обратите внимание, что параметрыObject
вместо дженериковE
.
3) НовыйtoString()
метод, удобныйArraylist
печатать.
4) Новыйset(int index, E element)
метод, удобныйArraylist
Изменения элемента.
тебе известно,Arraylist<Wanger> list = new Arraylist<Wangxiaoer>();
Такой оператор не может быть скомпилирован, даже несмотря на то, что Wangxiaoer является подклассом Wanger. Но что, если нам нужны такие «переключающие вверх» отношения? Здесь в игру вступают подстановочные знаки.
использовать<? extends Wanger>
Подстановочный знак формы может реализовать восходящее преобразование универсального типа, см. пример.
Arraylist<? extends Wanger> list2 = new Arraylist<>(4);
list2.add(null);
// list2.add(new Wanger());
// list2.add(new Wangxiaoer());
Wanger w2 = list2.get(0);
// Wangxiaoer w3 = list2.get(1);
Тип списка2Arraylist<? extends Wanger>
, перевод, list2 этоArraylist
, тип которогоWanger
и его подклассы.
Внимание, вот и "ключ"! list2 не может пройтиadd(E e)
способ добавить к немуWanger
илиWangxiaoer
объекта, за исключениемnull
. Почему нельзя спасти? Причина еще предстоит изучить (горький).
Это странно, так как элементы не могут быть сохранены, тоArraylist<? extends Wanger>
Какая польза от такого списка2?
Хоть и не смог пройтиadd(E e)
метод добавляет элементы в list2, но может присваивать ему значения.
Arraylist<Wanger> list = new Arraylist<>(4);
Wanger wanger = new Wanger();
list.add(wanger);
Wangxiaoer wangxiaoer = new Wangxiaoer();
list.add(wangxiaoer);
Arraylist<? extends Wanger> list2 = list;
Wanger w2 = list2.get(1);
System.out.println(w2);
System.out.println(list2.indexOf(wanger));
System.out.println(list2.contains(new Wangxiaoer()));
Arraylist<? extends Wanger> list2 = list;
Оператор присваивает значение списка списку2, затемlist2 == list
. Поскольку list2 не позволяет добавлять в него другие элементы, на данный момент это безопасно — мы можем изящно сделать это с помощью list2.get()
,indexOf()
а такжеcontains()
. Если подумать, если бы вы могли добавлять элементы в list2, эти 3 метода стали бы менее безопасными, и их значения могли бы измениться.
использовать<? super Wanger>
Подстановочные знаки формы, вы можете сохранить родительский класс в ArraylistWanger
элементы, см. пример.
Arraylist<? super Wanger> list3 = new Arraylist<>(4);
list3.add(new Wanger());
list3.add(new Wangxiaoer());
// Wanger w3 = list3.get(0);
Следует отметить, что нельзяArraylist<? super Wanger>
Извлечь данные из списка этого типа3. Почему ты не можешь взять это? Причину еще предстоит выяснить (опять же горькая).
Хотя причины еще предстоит изучить, вывод ясен:<? extends T>
данные могут быть восстановлены,<? super T>
данные могут быть сохранены. Затем, используя это, мы можем получить копию массива -<? extends T>
как источник (гарантируется, что источник не изменится),<? super T>
в качестве цели (значение может быть сохранено).
public class Collections {
public static <T> void copy(Arraylist<? super T> dest, Arraylist<? extends T> src) {
for (int i = 0; i < src.size(); i++)
dest.set(i, src.get(i));
}
}
06. Незаконченная история с продолжением
"Второй брат, ты сегодня ожесточился! Хе-хе. Тебе еще нужно кое-что изучить" Третья сестра начала капризничать.
"..."
"Не смущайся. Когда Санмей узнает причину, Санмей расскажет тебе, хорошо?" - говорил Санмей все более и более энергично.
"..."
"Второй брат, ты все еще думаешь о причине общих подстановочных знаков! Третья сестра собирается сначала просмотреть следующую точку знаний. После того, как ты закончишь думать, расскажи мне об этом!" Слово исчезло.
"..."