Вопрос 1: Зачем нужны дженерики?
отвечать:
Код, написанный с использованием механизма дженериков, лучше, чем код, использующий беспорядок.Object
переменные, а затем привести код с большей безопасностью и читабельностью, то есть код, написанный с использованием универсального механизма, может быть повторно использован многими различными типами объектов.
Вопрос 2: С точки зрения ArrayList, зачем использовать дженерики?
отвечать:
До того, как Java добавила дженерики, был одинArrayList
класс, этоArrayList
Общая концепция класса заключается в использованиинаследоватьбыть реализованным.
public class ArrayList {
private Object[] elementData;
public Object get(int i) {....}
public void add(Object o) {....}
}
У этого класса есть две проблемы:
- Кастинг требуется при получении значения
- Нет проверки ошибок, в массив можно добавлять объекты любого класса
ArrayList files = new ArrayList();
files.add(new File(""));
String filename = (String)files.get(0);
Для этого вызова скомпилируйте и запустите без ошибок, но когда мы используем метод get в другом месте, чтобы получить только что сохраненныйFile
объект, приведенный кString
тип вызовет ошибку.
Дженерики решают эту проблему, предоставляятип параметра.
ArrayList<String> files = new ArrayList<>();
Это делает код более читаемым, мы можем сразу увидеть, что этот список данных содержитString
объект.
Компилятор также может эффективно использовать эту информацию, когда мы вызываемget
Когда нет необходимости использовать приведение, компилятор знает, что тип возвращаемого значенияString
, вместоObject
:
String filename = files.get(0);
Компилятор тоже знаетArrayList<String>
серединаadd
метод имеет видString
параметр. Это будет лучше, чем использоватьObject
Параметры типа стали безопаснее, теперь компилятор может проверять, чтобы не вставлять объекты неправильного типа:
files.add(new File(""));
Такой код не может быть скомпилирован,Гораздо лучше иметь ошибку компиляции, чем исключение приведения класса во время выполнения..
Вопрос 3: Давайте поговорим об общих классах
Общий классимеет одну или несколько переменных типаКласс, для которого мы фокусируемся только на дженериках и не беспокоимся о деталях хранения данных.
public class Couple<T> {
private T one;
private T two;
}
Singer
Класс вводит переменную типа T, заключенную в угловые скобки и помещаемую после имени класса. Общий класс может иметь несколько переменных типа:
public class Couple<T, U> {...}
Переменная типа в определении классаУказывает возвращаемый тип метода и типы полей и локальных переменных.
//域
private T one;
//返回类型
public T getOne() { return one; }
//局部变量
public void setOne(T newValue) { one = newValue; }
Общие типы могут быть созданы с использованием конкретных типов вместо переменных типа:
Couple<Rapper>
Общие классы можно рассматривать как фабрики для обычных классов., например: Я использовал дженерики для создания модели, а какой материал заливать, решать пользователю.
Вопрос 4. Расскажите об определении и использовании универсальных методов.
отвечать:
Универсальные методы могут быть определены в обычных классах или в универсальных классах, а переменные типа помещаются впосле модификатора,перед возвращаемым типом.
Давайте рассмотрим пример универсального метода:
class ArrayUtil {
public static <T> T getMiddle(T...a){
return a[a.length / 2];
}
}
При вызове универсального метода укажите конкретный тип в угловых скобках перед именем метода:
String middle = ArrayUtil.<String>getMiddle("a","b","c");
В этом случае вызов метода можно опустить.<String>
параметр типа, компилятор будет использоватьвывод типаЧтобы вывести вызванный метод, то есть вы можете написать:
String middle = ArrayAlg.getMiddle("a","b","c");
Вопрос пятый:E V T K ?
что это
отвечать:
-
E
- Элемент представляет собой элемент. Атрибут является перечислением. -
T
—— Класс типа, который относится к типу Java -
K
- ключ ключ -
V
——Значение значения -
?
- Указывает на неопределенный тип в использовании
Вопрос 6: Вы поняли ограничения переменных типа?
отвечать:
Переменная типа или подстановочный знак могут иметь несколько определений, например:
<T extends Serializable & Cloneable>
Использование нескольких уточненных типов одной переменной типа&
разделены, в то время как,
Используется для разделения переменных нескольких типов.
<T extends Serializable,Cloneable>
В наследовании переменных типа вы можете иметь, если хотитенесколько интерфейсовсупертип, но ограниченныймаксимум один класс. Если класс используется как квалификация, он должен быть в списке квалификаций.Первый.
Ограничение переменных типа заключается в том, чтобы ограничить поведение универсальных шаблонов, указав, что только классы, реализующие определенный интерфейс, могут использоваться в качестве переменных типа для создания экземпляра класса.
Вопрос 7: Что вы знаете о дженериках и наследовании?
отвечать:
Во-первых, давайте посмотрим на класс и его подклассы, такие какSinger
иRapper
. ноCouple<Rapper>
но нетCouple<Singer>
подкласс .
Какую бы связь ни имели S и T,Couple<S>
иCouple<T>
Нет подключения.
Здесь нам нужно обратить внимание на разницу между дженериками и массивами Java, вы можете использоватьRapper[]
Массиву присваивается типSinger[]
Переменные:
Rapper[] rappers = ...;
Singer[] singer = rappers;
Однако массивы имеют специальную защиту: если вы попытаетесь сохранить суперкласс в массиве подклассов, виртуальная машина выброситArrayStoreException
аномальный.
Вопрос 8: Давайте поговорим о подстановочных знаках
отвечать:
В подстановочных типах параметры типа могут изменяться. Например, подстановочные знаки:
Couple<? extends Singer>
представляет любой универсальный тип, параметр типа которогоSinger
подклассы, такие какCouple<Rapper>
, но не будетCouple<Dancer>
.
Предположим, теперь нам нужно написать метод для вывода чего-либо:
public static void printCps(Couple<Rapper> cps) {
Rapper one = cp.getOne();
Rapper two = cp.getTwo();
System.out.println(one.getName() + " & " + two.getName() + " are cps.");
}
Как упоминалось ранее, невозможноCouple<Rapper>
перешел к этому методу, который очень ограничительный. Решение простое, используйте подстановочные знаки:
public static void printCps(Couple< ? extends Singer> cps)
Couple<Rapper>
даCouple< ? extends Singer>
подтип .
Далее рассмотрим другую проблему, использование подстановочных знаков пройдет.Couple< ? extends Singer>
Неработающие ссылкиCouple<Rapper>
?
Couple<Rapper> rapper = new Couple<>(rapper1, rapper2);
Couple<? extends Singer> singer = rapper;
player.setOne(reader);
Это может привести к повреждению, но когда мы звонимsetOne
, если звонок неSinger
подклассRapper
объект класса, но другойSinger
объект подкласса, возникает ошибка.
Давайте взглянемCouple<? extends Singer>
Методы:
? extends Singer getOne();
void setOne(? extends Singer);
Это будет очевидно, потому что если мы позвонимsetOne()
метод, компилятор может знать, что определенныйSinger
подтип , не имея возможности точно определить, какой это тип, он отказывается передавать какой-либо конкретный тип, потому что ? не может использоваться для сопоставления.
но использоватьgetOne
Такой проблемы нет, потому что нам не нужно заботиться о том, какой тип он получит, но он должен бытьSinger
подкласс .
Квалификация с подстановочными знаками очень похожа на квалификацию переменной типа, но типы с подстановочными знаками имеют дополнительную возможность указывать квалификацию супертипа:
? super Rapper
Этот подстановочный знак ограниченRapper
Все родительские классы, почему вы хотите это сделать? Подстановочные знаки с квалификацией супертипа имеют прямо противоположное поведение подстановочных знаков с квалификацией подтипа, вы можете указать параметры для метода, но вы не можете получить конкретное значение, т.е. методы доступа небезопасны, а методы-мутаторы безопасны.:
Компилятор не может знатьsetOne
Конкретный тип метода, поэтому вызов этого метода не может получить типSinger
илиObject
параметр. только пройтиRapper
объект типа или некоторого подтипа (Reader
) объект. Также, если звонитьgetOne
, тип возвращаемого объекта не гарантируется.
в заключении:
Подстановочные знаки, уточненные супертипами, могут быть записаны в универсальные объекты, а подстановочные знаки, уточненные подтипами, могут быть прочитаны из универсальных объектов.
Вопрос 9: Как выглядят дженерики в виртуальной машине?
отвечать:
-
Виртуальные машины не имеют объектов универсального типа, все объекты относятся к обычным классам.Всякий раз, когда определяется универсальный тип, автоматически предоставляется соответствующий примитивный тип. Имя исходного типа — это имя универсального типа с удаленными параметрами типа.Удалить переменную типа и заменить ее квалифицированным типом(Неквалифицированные переменные используются для
Object
). Цель этого состоит в том, чтобы разрешить неуниверсальныеJava
Позже программа поддерживает общиеjvm
все еще работает (обратно совместим) -
Когда программа вызывает универсальный метод, компилятор вставляет приведение, если возвращаемый тип стирается.
Couple<Singer> cps = ...;
Singer one = cp.getOne();
стеретьcp.getOne
вернет возвращаемый типObject
тип. Компилятор автоматически вставляетSinger
преобразование типа принуждения. То есть компилятор компилирует этот вызов метода в две инструкции виртуальной машины:
к оригинальному методу
cp.getOne
вызов Приведите возвращаемый тип объекта кSinger
тип.
- Приведения также вставляются при доступе к общедоступному универсальному полю.
//我们写的代码
Singer one = cps.one;
//编译器做的事情
Singer one = (Singer)cps.one;
Вопрос 10: Что вы знаете об универсальном стирании?
отвечать:
Стирание типа происходит в универсальных методах, программисты обычно думают о следующих универсальных методах.
public static <T extends Comparable> T min(T[] a)
представляет собой полное семейство методов, и после стирания типа остается только один метод:
public static Comparable min(Comparable[] a)
В это время параметр типа T был стерт, оставив только ограниченный типComparable
.
Но стирание метода приносит некоторые проблемы:
class Coupling extends Couple<People> {
public void setTwo(People people) {
super.setTwo(people);
}
}
После стирания:
class Coupling extends Couple {
public void setTwo(People People) {...}
}
В это время возникает проблема, что есть еще одинCouple
класс унаследовалsetTwo
метод, а именно:
public void setTwo(Object two)
Очевидно, это другой метод, потому что он принимает другой тип параметра (Object
) вместоPeople
.
Coupling coupling = new Coupling(...);
Couple<People> cp = interval;
cp.setTwo(people);
Вот, надеюсьsetTwo
Вызов является полиморфным и вызывает наиболее подходящий метод. так какcp
ЦитироватьCoupling
объект, поэтому следует называтьCoupling.setTwo
. Проблема в том, что стирание типов конфликтует с полиморфизмом. Чтобы решить эту проблему, компилятор долженCoupling
Создайте метод моста в классе:
public void setTwo(Object second) {
setTwo((People)second);
}
Переменнаяcp
был объявлен как типCouple<LocalDate>
, и этот тип имеет только один простой метод с именемsetTwo
,СейчасsetTwo(Object)
. Для виртуальной машиныcp
Ссылочный объект вызывает этот метод. Этот объектCoupling
тип, поэтому он будет вызыватьCoupling.setTwo(Object)
метод. Этот метод является методом синтетического моста. это позвонитCoupling.setTwo(Date)
, чего мы и ожидали.
Итак, мы должны помнить несколько моментов об общих преобразованиях Java:
- В виртуальной машине нет дженериков, только обычные классы и методы
- Все параметры типа заменяются их уточненными типами.
- Мостовые методы синтезированы для сохранения полиморфизма
- Для обеспечения безопасности типов вставляйте приведения типов, если это необходимо.