предисловие
С сегодняшнего дня я начну повторять основы Java. Через неделю может быть статья . То, что я пишу в своем блоге, может быть неверным. Если есть какие-либо ошибки, пожалуйста, потерпите меня и поправьте меня~
Сегодня я хочу рассмотреть дженерики. Универсальные шаблоны также являются очень важным элементом знаний в Java. В этой статье в основном объясняются основные понятия, а не продвинутые знания. Если у вас есть хорошая основа, вы можете просмотреть ее ~
1. Что такое дженерики?
Принцип проектирования дженериков Java: если во время компиляции нет предупреждений, во время выполнения не будет исключений ClassCastException..
Дженерики:отложить работу, связанную с типом, до создания объекта или вызова метода
Параметризованный тип:
- передать тип в качестве параметра
<数据类型>
может быть только ссылочным типом
Связанные термины:
-
ArrayList<E>
серединаE называется переменной параметра типа -
ArrayList<Integer>
серединаInteger называется фактическим параметром типа - все называется
ArrayList<E>
универсальный тип - весь
ArrayList<Integer>
Тип с именем ParameterizedType
2. Зачем нужны дженерики?
На заре Java Object использовался для представления любого типа, но приведение вниз имело проблему принудительного преобразования, что делало программу менее безопасной.
Во-первых, давайте представим: что было бы с коллекциями без дженериков
- Коллекции Collection и Map не имеют ограничений по типу элементов.Первоначально моя коллекция Collection загружается со всеми объектами Dog, но нет синтаксической ошибки при хранении объектов Cat в коллекции снаружи.
- Бросив объект в коллекцию, коллекция не знает, какого типа элемент, только то, что это Object. Следовательно, когда get() возвращается Object.Чтобы получить объект снаружи, вам также нужно его разыграть
С дженериками:
- Код стал более лаконичным [без принудительного преобразования]
- Программа более надежна [пока нет предупреждения во время компиляции, во время выполнения не будет ClassCastException]
- Читабельность и стабильность [При написании коллекции тип ограничен]
2.1 После создания дженериков используйте расширенный for для обхода коллекции
При создании коллекцииМы уточнили тип коллекции, поэтому мы можем использовать расширенный for для перебора коллекции!
//创建集合对象
ArrayList<String> list = new ArrayList<>();
list.add("hello");
list.add("world");
list.add("java");
//遍历,由于明确了类型.我们可以增强for
for (String s : list) {
System.out.println(s);
}
3. Общие основы
3.1 Общие классы
Общий класс предназначен для определения универсального типа в классе, и когда пользователь использует класс, тип уточняется.....В этом случае пользователь может уточнить, какой тип и какой тип представляет этот класс...Пользователям не нужно беспокоиться о проблеме принудительного преобразования и проблеме аномального преобразования во время выполнения.
- Обобщения, определенные в классах, также можно использовать в методах класса!
/*
1:把泛型定义在类上
2:类型变量定义在类上,方法中也可以使用
*/
public class ObjectTool<T> {
private T obj;
public T getObj() {
return obj;
}
public void setObj(T obj) {
this.obj = obj;
}
}
- Тестовый код:
Какой тип пользователь хочет использовать, укажите тип во время создания. При использовании класс будет автоматически преобразован в тип, который хочет использовать пользователь.
public static void main(String[] args) {
//创建对象并指定元素类型
ObjectTool<String> tool = new ObjectTool<>();
tool.setObj(new String("钟福成"));
String s = tool.getObj();
System.out.println(s);
//创建对象并指定元素类型
ObjectTool<Integer> objectTool = new ObjectTool<>();
/**
* 如果我在这个对象里传入的是String类型的,它在编译时期就通过不了了.
*/
objectTool.setObj(10);
int i = objectTool.getObj();
System.out.println(i);
}
3.2 Общие методы
Универсальные классы были введены ранее, дженерики, определенные в классах, также могут использоваться в методах...
Прямо сейчас мы могли бы быть простоМетод должен использовать дженерики....Внешний мир заботится только о методе, а не о других свойствах класса.... В этом случае мы определяем дженерики для всего класса, что немного суетливо.
- Определение универсального метода....Сначала определяются дженерики, а затем используются
//定义泛型方法..
public <T> void show(T t) {
System.out.println(t);
}
- Тестовый код:
Какой тип передается пользователем и какой тип является возвращаемым значением?
public static void main(String[] args) {
//创建对象
ObjectTool tool = new ObjectTool();
//调用方法,传入的参数是什么类型,返回值就是什么类型
tool.show("hello");
tool.show(12);
tool.show(12.5);
}
3.3 Подклассы, производные от общих классов
Ранее мы определили общие классы,Универсальный класс — это класс, обладающий свойствами дженериков. По сути, это класс Java, поэтому его можно наследовать.
Так как же это наследуется? ? тут два случая
- Явная переменная параметра универсального типа подкласса
- Переменная параметра неоднозначного типа подкласса универсального класса
3.3.1 Подклассы поясняют переменные параметра типа универсального класса
- универсальный интерфейс
/*
把泛型定义在接口上
*/
public interface Inter<T> {
public abstract void show(T t);
}
- Класс, который реализует общий интерфейс...
/**
* 子类明确泛型类的类型参数变量:
*/
public class InterImpl implements Inter<String> {
@Override
public void show(String s) {
System.out.println(s);
}
}
3.3.2 Переменная параметра неоднозначного типа подкласса универсального класса
- Когда подкласс неоднозначно относится к переменной параметра типа универсального класса,При использовании подклассов во внешнем мире вам также необходимо передать переменные параметра типа, и вам необходимо определить переменные параметра типа в классе реализации.
/**
* 子类不明确泛型类的类型参数变量:
* 实现类也要定义出<T>类型的
*
*/
public class InterImpl<T> implements Inter<T> {
@Override
public void show(T t) {
System.out.println(t);
}
}
Тестовый код:
public static void main(String[] args) {
//测试第一种情况
//Inter<String> i = new InterImpl();
//i.show("hello");
//第二种情况测试
Inter<String> ii = new InterImpl<>();
ii.show("100");
}
Стоит отметить, что:
- Если класс реализации переопределяет метод родительского класса, тип возвращаемого значения должен быть таким же, как у родительского класса!
- Обобщения, объявленные в классах, действительны только для нестатических членов.
3.4 Подстановочные знаки
Зачем нужны подстановочные знаки типов? ? ? ? Давайте посмотрим на требование.....
Теперь есть требование:Метод получает параметр коллекции, проходит по коллекции и печатает элементы коллекции, что делать?
- Как мы делали до того, как узнали о дженериках, мы могли бы сделать это:
public void test(List list){
for(int i=0;i<list.size();i++){
System.out.println(list.get(i));
}
}
Приведенный выше код правильный,Просто во время компиляции появится предупреждение о том, что тип элемента коллекции не определен....это неэлегантно...
- Итак, мы узнали о дженериках, что нам теперь делать? ? Некоторые люди могут сделать это:
public void test(List<Object> list){
for(int i=0;i<list.size();i++){
System.out.println(list.get(i));
}
}
В этом синтаксисе нет ничего плохого, но здесь стоит отметить:Метод test() может проходить только по коллекции, загруженной с помощью Object! ! !
подчеркивать:в общем<Object>
Это не так наследуется, как раньше, то естьList<Object>
иList<String>
не имеет значения! ! ! !
Что делать сейчас? ? ? Мы не знаем, какой тип элементов загружает коллекция List.List<Objcet>
Это не сработает.....Итак, дженерики Java предоставляют подстановочные знаки типов?
Таким образом, код должен быть изменен на это:
public void test(List<?> list){
for(int i=0;i<list.size();i++){
System.out.println(list.get(i));
}
}
Подстановочный знак ? указывает, что может быть сопоставлен любой тип, и может быть сопоставлен любой класс Java......
Теперь стоит отметить, что когда мы используем подстановочный знак ?:Вы можете вызывать только методы, не относящиеся к типу объекта, но не можете вызывать методы, относящиеся к типу объекта.
Помните,Можно вызывать только методы, не относящиеся к объекту, а методы, относящиеся к типу объекта, вызывать нельзя.. Потому что конкретный тип не известен, пока его не использует внешний мир. То есть в приведенной выше коллекции List я не могу использовать метод add().Потому что метод add() бросает объект в коллекцию, и теперь я не знаю, что это за тип объекта.
3.4.1 Установка верхнего предела подстановочного знака
Во-первых, давайте посмотрим, где используется установка подстановочного знака....
Теперь я хочу получить коллекцию List, которая может работать только с элементами числового типа [Float, Integer, Double, Byte и другие числовые типы в порядке], как это сделать? ? ?
Мы узнали о подстановочных знаках, но если вы используете подстановочные знаки напрямую, набор будет работать не только с числами. Поэтому нам нужноИспользуйте для установки верхнего предела подстановочного знака
List<? extends Number>
Приведенный выше код означает:Элементы, загруженные коллекцией List, могут быть только подклассом Number или самим собой.
public static void main(String[] args) {
//List集合装载的是Integer,可以调用该方法
List<Integer> integer = new ArrayList<>();
test(integer);
//List集合装载的是String,在编译时期就报错了
List<String> strings = new ArrayList<>();
test(strings);
}
public static void test(List<? extends Number> list) {
}
3.4.2 Установка нижнего предела подстановочного знака
Теперь, когда мы поговорили о том, как установить верхний предел подстановочных знаков, нетрудно установить нижний предел подстановочных знаков. Просто посмотрите на грамматику
//传递进来的只能是Type或Type的父类
<? super Type>
Нередко устанавливается нижняя граница для подстановочных знаков в коллекции TreeSet.... Давайте посмотрим
public TreeSet(Comparator<? super E> comparator) {
this(new TreeMap<>(comparator));
}
Итак, для чего это нужно? ? Давайте подумаем об этом, когда мы хотим создатьTreeSet<String>
Когда переменная типа и передается компаратору, который может сравнивать размер строки.
Тогда есть много вариантов для этого компаратора,это может бытьComparator<String>
, или параметр типа является родительским классом String, напримерComparator<Objcet>
....
Делать это оченьгибкий. Это,Пока он может сравнивать размеры строк, все в порядке.
Стоит отметить, что:Независимо от того, устанавливаете ли вы верхний или нижний предел подстановочных знаков, вы не можете работать с методами, связанными с объектами.Пока задействованы подстановочные знаки, их тип неизвестен!
3.5 Подстановочные знаки и универсальные методы##
В большинстве случаев мы можем использовать общие методы вместо подстановочных знаков......
//使用通配符
public static void test(List<?> list) {
}
//使用泛型方法
public <T> void test2(List<T> t) {
}
Оба вышеупомянутых метода в порядке... Итак, теперь возникает вопрос, мы используем подстановочные знаки или общие методы? ?
в общем:
- еслиМежду параметрами существует зависимость типа, или вернутьсяВозвращаемое значение зависит от параметраиз. затем используйтеобщий метод
- еслинет зависимостейДа, просто используйтеподстановочный знак, подстановочный знак будетБыть гибким.
3.6 Общее стирание
ДженерикиПредоставляется для использования компилятором javac, который используется для определения типа ввода коллекции, чтобы позволить компилятору на уровне исходного кода заблокировать вставку недопустимых данных в коллекцию. Но после того, как компилятор скомпилирует java-программу с дженериками,Сгенерированный файл класса больше не будет содержать общую информацию., чтобы эффективность работы программы не пострадала, этот процесс называется "удалением".
3.6.1 Совместимость
В JDK5 была предложена концепция дженериков, но раньше в JDK5 не было дженериков. То есть дженерики должны быть совместимы с коллекциями ниже JDK5.
При назначении коллекции с универсальной функцией более старой версии коллекции универсальный тип стирается.
Стоит отметить, что:Он сохраняет верхнюю границу параметра типа.
List<String> list = new ArrayList<>();
//类型被擦除了,保留的是类型的上限,String的上限就是Object
List list1 = list;
Если яназначить набор без параметров типа набору с параметрами типа, как это будет происходить? ?
List list = new ArrayList();
List<String> list2 = list;
Ошибки тоже не выдает, просто пишет "непроверенное преобразование"
В-четвертых, применение общего
Когда мы пишем веб-страницы, часто используется несколько DAO, и нам приходится каждый раз писать несколько DAO, что немного хлопотно.
Итак, какого эффекта мы хотим? ?Напишите только один абстрактный DAO, пока другие DAO наследуют абстрактный DAO, будут соответствующие методы.
Для достижения этого эффекта необходимо использовать дженерики.Потому что в абстрактном DAO невозможно знать, какой DAO унаследует сам себя, поэтому невозможно узнать его конкретный тип. Обобщения указывают свой конкретный тип при создании.
- Абстрактный ДАО
public abstract class BaseDao<T> {
//模拟hibernate....
private Session session;
private Class clazz;
//哪个子类调的这个方法,得到的class就是子类处理的类型(非常重要)
public BaseDao(){
Class clazz = this.getClass(); //拿到的是子类
ParameterizedType pt = (ParameterizedType) clazz.getGenericSuperclass(); //BaseDao<Category>
clazz = (Class) pt.getActualTypeArguments()[0];
System.out.println(clazz);
}
public void add(T t){
session.save(t);
}
public T find(String id){
return (T) session.get(clazz, id);
}
public void update(T t){
session.update(t);
}
public void delete(String id){
T t = (T) session.get(clazz, id);
session.delete(t);
}
}
- Унаследовав абстрактный DAO, класс реализации имеет соответствующий метод добавления, удаления, изменения и проверки.
CategoryDao
public class CategoryDao extends BaseDao<Category> {
}
BookDao
public class BookDao extends BaseDao<Book> {
}
5. Наконец
Здесь представлены основы дженериков.Если возникнет необходимость в будущем, я проведу углубленное исследование.Если вы считаете, что эта статья вам поможет, вы можете поставить лайк и подписаться на публичный аккаунт на волну~
Использованная литература:
- Core Java
Если в статье есть какие-либо ошибки, пожалуйста, поправьте меня, и мы сможем общаться друг с другом. Учащиеся, привыкшие читать технические статьи в WeChat и желающие получить больше ресурсов по Java, могутОбратите внимание на публичный аккаунт WeChat: Java3y