Java 新特性之泛型
阿新 • • 發佈:2021-02-19
1)文筆有限,如果發現部落格有書寫有誤的地方懇請讀者直言不諱,我一定會第一時間改正。
2)程式碼的具體實現可以參考程式碼中的註釋,如果由於註釋不清楚而不明白相應原理,可以與作者私聊。碼字不易,有興趣的小夥伴點個讚唄,大家相互學習。
3)本篇部落格為Java新特性之 泛型,如需瞭解 Java 的其它部分,歡迎點選連結。
傳送門:
1 概述
1.1 泛型的定義
所謂泛型(Generic),就是允許在定義類、介面時通過一個標識來 表示 類中某個屬性的型別或者是某個方法的返回值及引數型別。
這個型別引數將在使用時確定。例如當繼承或者實現某個介面時,通過在宣告或者建立物件時,傳入實際的型別引數來確定。
1.2 為什麼需要泛型
我們用在集合中新增元素舉例。
當集合中沒有泛型時:
/**
* 需求:存放學生的成績
* 1 當集合中不使用泛型時。集合中的任何元素都會預設用 Object 來對待
*/
@Test
public void test1(){
ArrayList list = new ArrayList();
list.add(78);
list.add(76);
list.add(89);
list.add(88);
//問題一:型別不安全
list.add("Tom");
for(Object score : list){
//問題二:強轉時,可能出現ClassCastException
int stuScore = (Integer) score;
System.out.println(stuScore);
}
}
當集合中使用泛型時:
/**
* 需求:存放學生的成績
* 2 在集合中使用泛型的情況:以ArrayList為例
*/
@Test
public void test2(){
ArrayList<Integer> list = new ArrayList<Integer>();
list.add(78);
list.add(87);
list.add(99);
list.add(65);
//編譯時,就會進行型別檢查,保證資料的安全。因為在例項化時規定了泛型是Integer
//型,所以list中無法加入String型別的"Tom"
//list.add("Tom");
//方式一:
// for(Integer score : list){
// //避免了強轉操作
// int stuScore = score;
//
// System.out.println(stuScore);
//
// }
//方式二:
Iterator<Integer> iterator = list.iterator();
while(iterator.hasNext()){
int stuScore = iterator.next();
System.out.println(stuScore);
}
}
重點:
- 泛型只能使用包裝資料型別,而不能使用基本資料型別;
- 如果例項化時,沒指明泛型的型別。預設型別為 java.lang.Object 型別;
- 把一 個集合中的內容限制為一個特定的資料型別,這就是泛型背後的核心思想;
- 泛型在例項化之後,後面的集合就一定要放例項化時固定的型別了。
2 泛型在集合中的使用
/**
* 需求:存放學生的成績
* 1 在集合中使用泛型的情況:以ArrayList為例
*/
@Test
public void test2(){
// 如果沒有<Integer>,那麼就表示ArrayList中變數型別是 Object 型別的。
ArrayList<Integer> list = new ArrayList<>();
list.add(78);
list.add(87);
list.add(99);
list.add(65);
Iterator<Integer> iterator = list.iterator();
while(iterator.hasNext()){
int stuScore = iterator.next();
System.out.println(stuScore);
}
}
/**
* 需求:存放學生的成績
* 2 在集合中使用泛型的情況:以HashMap為例
*/
@Test
public void test3(){
Map<String,Integer> map = new HashMap<>();
map.put("Tom",87);
map.put("Jerry",87);
map.put("Jack",67);
//泛型的巢狀
Set<Map.Entry<String,Integer>> entry = map.entrySet();
for (Map.Entry<String, Integer> e : entry) {
String key = e.getKey();
Integer value = e.getValue();
System.out.println(key + "----" + value);
}
}
3 自定義泛型結構
3.1 自定義泛型類、介面
- 泛型類可能有多個引數,此時應將多個引數一起放在尖括號內 。比如:<E1,E2,E3>;
- 泛 型類的構造器為
public GenericClass(){}
,而非public GenericClass<E>(){}
; - T 不能用基本資料型別填充,但可以使用包裝類填充;
- 靜態方法中不能使用類的泛型;
- 異常類是不能泛型的;
- 不能使用
new E[]
;但是可以E [] elements = (E[])new Object[capacity]
; - 父類有泛型,子類可以選擇保留泛型也可以選擇指定泛型型別。
/**
* 自定義泛型類
* 1. <T>中的 T 表示的是什麼型別;
* 2. 泛型類並不是表示這個類是什麼型別,而是表示類中的屬性是什麼型別,且這些型別在構造物件的時候確定;
* 3. 類名中的 <T> 是為了宣告這個類是泛型類。
*/
public class Order<T> {
String orderName;
int orderId;
//類的內部結構就可以使用類的泛型
T order;
public Order(){
// 在建構函式中必須使用下面一種方法
// T[] arr = new T[10];
T[] arr = (T[]) new Object[10];
}
public Order(String orderName,int orderId,T order){
this.orderName = orderName;
this.orderId = orderId;
this.order = order;
}
//如下的三個方法都不是泛型方法
public T getOrder(){
return order;
}
public void setOrder(T order){
this.order = order;
}
@Override
public String toString() {
return "Order{" +
"orderName='" + orderName + '\'' +
", orderId=" + orderId +
", order=" + order +
'}';
}
//靜態方法中不能使用類的泛型,因為泛型的建立是在類的例項化時,而靜態方法是屬於類本身的。
// public static void show(T order){
// System.out.println(order);
// }
}
3.2 自定義泛型方法
public class Order<T> {
String orderName;
int orderId;
T order;
public Order(){
T[] arr = (T[]) new Object[10];
}
//泛型方法:在方法中出現了泛型的結構,泛型引數與類的泛型引數沒有任何關係。
// 換句話說,泛型方法所屬的類是不是泛型類都沒有關係。
//泛型方法是可以宣告為靜態的,原因是泛型方法的泛型引數是在呼叫方法時確定的,並非在例項化類時確定。
public static <E> List<E> copyFromArrayToList(E[] arr){
ArrayList<E> list = new ArrayList<>();
for(E e : arr){
list.add(e);
}
return list;
}
}
3.3 自定義泛型在資料庫上的使用
在 Java 對資料庫的操作中,我們常採用 ORM 思想,即資料庫中的一張表對應一個 JavaBean 物件。
基本思想:對於不同表的很多操作都是相同的,這些操作,唯一不同的是被操作的物件。所以我們可以定義一個 DAO.java
來定義操作資料庫中的表的通用操作,然後利用泛型來約定不同的物件。
1)定義基本操作的Dao類
/**
* DAO:data(base) access object
*/
public class DAO<T> {//表的共性操作的DAO
//新增一條記錄
public void add(T t){
}
//刪除一條記錄
public boolean remove(int index){
return false;
}
//修改一條記錄
public void update(int index,T t){
}
//查詢一條記錄
public T getIndex(int index){
return null;
}
//查詢多條記錄
public List<T> getForList(int index){
return null;
}
//泛型方法
//舉例:獲取表中一共有多少條記錄?獲取最大的員工入職時間?
public <E> E getValue(){
return null;
}
}
2)資料庫中的某一張表的 JavaBean物件
/**
* 此類對應資料庫中的 customers 表
*/
public class Customer {
}
3)資料庫中的另一張表的 JavaBean物件
/**
* 此類對應資料庫中的 students 表
*/
public class Student {
}
4)操作 customs 表的 DAO
/**
* 只能操作 customs 表的 DAO
*/
public class CustomerDAO extends DAO<Customer>{
}
5)操作 students 表的 DAO
/**
* 只能操作 students 表的 DAO
*/
public class StudentDAO extends DAO<Student> {
}
6)測試類
public class DAOTest {
@Test
public void test1(){
//對 customs 表進行操作
CustomerDAO dao1 = new CustomerDAO();
dao1.add(new Customer());
List<Customer> list = dao1.getForList(10);
//對 students 表進行操作
StudentDAO dao2 = new StudentDAO();
Student student = dao2.getIndex(1);
}
}
如果沒有多型的話,我們需要寫很多過載的方法。這是一個重點。
4 泛型在繼承上的體現
現在有三個類,分別為類A、類B和類G,其中類A為類B的父類,那麼:
G<A>
和G <B>
二者不是子父類關係,二者是並列關係;- 但是
A<G>
是B<G>
的父類。
程式碼說明:
/**
* `G<A>` 和`G <B>`二者不是子父類關係,二者是並列關係;所以不能賦值。
*/
@Test
public void test1(){
//多型的體現
Object obj = null;
String str = null;
obj = str;
Object[] arr1 = null;
String[] arr2 = null;
arr1 = arr2;
// 編譯不通過,因為String 並不是 Data 的父類。
//Date date = new Date();
//str = date;
List<Object> list1 = null;
List<String> list2 = new ArrayList<String>();
//編譯不通過,此時的list1和list2的型別不具有子父類關係
//list1 = list2;
}
/**
* `A<G>` 是 `B<G>` 的父類。所以可以賦值。
*/
@Test
public void test2(){
AbstractList<String> list1 = null;
List<String> list2 = null;
ArrayList<String> list3 = null;
list1 = list3;
list2 = list3;
List<String> list4 = new ArrayList<>()
}
5 萬用字元的使用
由第四章可知,儘管類A是類B的父類,但是G<A>
和G<B>
是沒關係的,不過二者共同的父類是:G<?>
。其中的?
在泛型中便是萬用字元。
?
在資料庫中叫做佔位符。
5.1 普通萬用字元
@Test
public void test3(){
List<Object> list1 = null;
List<String> list2 = null;
List<?> list = null;
list = list1;
list = list2;
//編譯通過
print(list1);
print(list2);
List<String> list3 = new ArrayList<>();
list3.add("AA");
list3.add("BB");
list3.add("CC");
list = list3;
//1 新增(寫入):對於List<?>就不能向其內部新增資料。
//除了新增null之外。
// list.add("DD");
// list.add('?');
list.add(null);
//2 獲取(讀取):允許讀取資料,讀取的資料型別為Object。
Object o = list.get(0);
System.out.println(o);
}
public void print(List<?> list){
Iterator<?> iterator = list.iterator();
while(iterator.hasNext()){
Object obj = iterator.next();
System.out.println(obj);
}
}
5.2 有限制條件的萬用字元的使用
帶限制條件的萬用字元 | 作用 |
---|---|
<? extends A> | 只允許泛型為 A 及 A 子類的引用呼叫 |
<? super A> | 只允許泛型為 A 及 A 父類的引用呼叫 |
<? extends Comparable> | 只允許泛型為實現 Comparable 介面的實現類的引用呼叫 |
程式碼例項:
public class Person {
}
public class Student extends Person {
}
@Test
public void test4(){
List<? extends Person> list1 = null;
List<? super Person> list2 = null;
List<Student> list3 = new ArrayList<Student>();
List<Person> list4 = new ArrayList<Person>();
List<Object> list5 = new ArrayList<Object>();
list1 = list3;
list1 = list4;
//list5無法賦值給list1
//list1 = list5;
//list3無法賦值給list2
//list2 = list3;
list2 = list4;
list2 = list5;
}