一、泛型簡介
1.泛型的概念
- 所謂泛型,就是允許在定義類、介面時透過一個標識表示類中某個屬性的型別或者是某個方法的返 回值及引數型別。這個型別引數將在使用時(例如,繼承或實現這個介面,用這個型別宣告變數、 建立物件時確定(即傳入實際的型別引數,也稱為型別實參)。
- 從JDK 5.0以後,Java引入了“引數化型別(Parameterized type)”的概念,允許我們在建立集合時再指定集合元素的型別,正如:List,這表明該List只能儲存字串型別的物件。
- JDK 5.0改寫了集合框架中的全部介面和類,為這些介面、類增加了泛型支援,從而可以在宣告集合變數、建立集合物件時傳入型別實參。
2.泛型的引入背景
集合容器類在設計階段/宣告階段不能確定這個容器到底實際存的是什麼型別的物件,所以在JDK1.5之前只能把元素型別設計為Object,JDK1.5之後使用泛型來解決。因為這個時候除了元素的型別不確定,其他的部分是確定的,例如關於這個元素如何儲存,如何管理等是確定的,因此此時把元素的型別設計成一個引數,這個型別引數叫做泛型。Collection,List,ArrayList 這個就是型別引數,即泛型。
3.引入泛型的目的
- 解決元素儲存的安全性問題,好比商品、藥品標籤,不會弄錯。
- 解決獲取資料元素時,需要型別強制轉換的問題,好比不用每回拿商品、藥品都要辨別。
Java泛型可以保證如果程式在編譯時沒有發岀警告,執行時就不會產生
ClassCastException
異常。同時,程式碼更加簡潔、健壯。
二、泛型在集合中的應用
1. 在集合中沒有使用泛型的例子
@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. 在集合中使用泛型的例子1
//在集合中使用泛型,以ArrayList為例
@Test
public void test1(){
ArrayList<String> list = new ArrayList<>();
list.add("AAA");
list.add("BBB");
list.add("FFF");
list.add("EEE");
list.add("CCC");
//遍歷方式一:
Iterator<String> iterator = list.iterator();
while (iterator.hasNext()){
System.out.println(iterator.next());
}
System.out.println("-------------");
//便利方式二:
for (String str:
list) {
System.out.println(str);
}
}
圖示:
3. 在集合中使用泛型例子2
@Test
//在集合中使用泛型的情況:以HashMap為例
public void test2(){
Map<String,Integer> map = new HashMap<>();//jdk7新特性:型別推斷
map.put("Tom",26);
map.put("Jarry",30);
map.put("Bruce",28);
map.put("Davie",60);
//巢狀迴圈
Set<Map.Entry<String, Integer>> entries = map.entrySet();
Iterator<Map.Entry<String, Integer>> iterator = entries.iterator();
while (iterator.hasNext()){
Map.Entry<String, Integer> entry = iterator.next();
String key = entry.getKey();
Integer value = entry.getValue();
System.out.println(key+"="+value);
}
}
4. 集合中使用泛型總結:
① 集合介面或集合類在JDK 5.0時都修改為帶泛型的結構。
② 在例項化集合類時,可以指明具體的泛型型別
③ 指明完以後,在集合類或介面中凡是定義類或介面時,內部結構(比如:方法、構造器、屬性等)使用到類的泛型的位置,都指定為例項化的泛型型別。
比如:add(E e) --->例項化以後:add(Integer e)
④ 注意點:泛型的型別必須是類,不能是基本資料型別。需要用到基本資料型別的位置,拿包裝類替換
⑤ 如果例項化時,沒有指明泛型的型別。預設型別為 java.lang.Object
型別。
三、自定義泛型結構
泛型類、泛型介面、泛型方法
1. 泛型的宣告
- interface
List<T>
和class GenTest<K,V>
其中,T,K,V,不代表值,而是表示型別。這裡使用任意字母都可以。 - 常用T表示,是Type的縮寫。
2. 泛型的例項化:
一定要在類名後面指定型別引數的值(型別)。如:
List<String> strList =new ArrayList<String>();
Iterator<Customer> iterator = customers.iterator();
- T只能是類,不能用基本資料型別填充。但可以使用包裝類填充
- 把一個集合中的內容限制為一個特定的資料型別,這就是 generics背後的核心思想
//JDK 5.0以前
Comparable c = new Date();
System.out.println(c.comparaTo("red");
//JDK 5.0以後
Comparable <Date> c = new Date();
System.out.println(c.comparaTo("red");
總結:使用泛型的主要優點在於能夠在編譯時而不是在執行時檢測錯誤
3. 注意點
-
泛型類可能有多個引數,此時應將多個引數一起放在尖括號內。比如<E1,E2,E3>
-
泛型類的構造器如下:
public GenericClass(){}
而下面是錯誤的:
public GenericClass<E>{}
-
例項化後,操作原來泛型位置的結構必須與指定的泛型型別一致。
-
泛型不同的引用不能相互賦值。
儘管在編譯時 ArrayList和ArrayList是兩種型別,但是,在執行時只有一個ArrayList被載入到JVM中。
-
泛型如果不指定,將被擦除,泛型對應的型別均按照Object處理,但不等價於Object。
建議:泛型要使用一路都用。要不用,一路都不要用。
-
如果泛型結構是一個介面或抽象類,則不可建立泛型類的物件。
-
JDK 7.0,泛型的簡化操作:
ArrayList<Fruit>first= new ArrayList<>();
(型別推斷) -
泛型的指定中不能使用基本資料型別,可以使用包裝類替換。
-
在類/介面上宣告的泛型,在本類或本介面中即代表某種型別,可以作為非靜態屬性的型別、非靜態方法的引數型別、非靜態方法的返回值型別。但在靜態方法中不能使用類的泛型。
-
異常類不能是泛型的。
-
不能使用
new E[]
。但是可以:E[] elements= (E[])new Object[capacity];
css
參考:ArrayList原始碼中宣告:`Object[] elementData`,而非泛型引數型別陣列。
- 父類有泛型,子類可以選擇保留泛型也可以選擇指定泛型型別:
diff
子類不保留父類的泛型:按需實現
- 沒有型別---擦除
- 具體型別
- 子類保留父類的泛型:泛型子類
- 全部保留
- 部分保留
- 結論:子類必須是“富二代”,子類除了指定或保留父類的泛型,還可以增加自己的泛型
程式碼示例:
class Father<T1, T2> {
}
/**
* 定義泛型子類Son
* 情況一:繼承泛型父類後不保留父類的泛型
*/
//1.沒有指明型別 擦除
class Son1<A, B> extends Father {//等價於class Son1 extends Father<Object,Odject>{}
}
//2.指定具體型別
class Son2<A, B> extends Father<Integer, String> {
}
/**
* 定義泛型子類Son
* 情況二:繼承泛型父類後保留泛型型別
*/
//1.全部保留
class Son3<T1, T2, A, B> extends Father<T1, T2> {
}
//2.部分保留
class Son4<T2, A, B> extends Father<Integer,T2>{
}
4. 自定義泛型結構
4.1自定義泛型類
程式碼示例:
/**
* 自定義泛型類Order
*/
class Order<T> {
private String orderName;
private int orderId;
//使用T型別定義變數
private T orderT;
public Order() {
}
//使用T型別定義構造器
public Order(String orderName, int orderId, T orderT) {
this.orderName = orderName;
this.orderId = orderId;
this.orderT = orderT;
}
//這個不是泛型方法
public T getOrderT() {
return orderT;
}
//這個不是泛型方法
public void setOrderT(T orderT) {
this.orderT = orderT;
}
//這個不是泛型方法
@Override
public String toString() {
return "Order{" +
"orderName='" + orderName + '\'' +
", orderId=" + orderId +
", orderT=" + orderT +
'}';
}
// //靜態方法中不能使用類的泛型。
// public static void show(T orderT){
// System.out.println(orderT);
// }
// //try-catch中不能是泛型的。
// public void show(){
// try {
//
// }catch (T t){
//
// }
// }
//泛型方法:在方法中出現了泛型的結構,泛型引數與類的泛型引數沒有任何關係。
//換句話說,泛型方法所屬的類是不是泛型類都沒有關係。
//泛型方法,可以宣告為靜態的。
// 原因:泛型引數是在呼叫方法時確定的。並非在例項化類時確定。
public static <E> List<E> copyFromArryToList(E[] arr) {
ArrayList<E> list = new ArrayList<>();
for (E e :
list) {
list.add(e);
}
return list;
}
}
自定義泛型類Order的使用
@Test
public void test1() {
//如果定義了泛型類,例項化沒有指明類的泛型,則認為此泛型型別為Object型別
//要求:如果大家定義了類是帶泛型的,建議在例項化時要指明類的泛型。
Order order = new Order();
order.setOrderT(123);
System.out.println(order.getOrderT());
order.setOrderT("abc");
System.out.println(order.getOrderT());
//建議:例項化時指明類的泛型
Order<String> order1 = new Order<>("Tom", 16, "male");
order1.setOrderT("AA:BBB");
System.out.println(order1.getOrderT());
}
@Test
//呼叫泛型方法
public void test2(){
Order<String> order = new Order<>();
Integer [] arr = new Integer[]{1,2,3,4,5,6};
List<Integer> list = order.copyFromArryToList(arr);
System.out.println(list);
}
4.2自定義泛型介面
程式碼示例:
/**
* 自定義泛型介面
*/
public interface DemoInterface <T> {
void show();
int size();
}
//實現泛型介面
public class Demo implements DemoInterface {
@Override
public void show() {
System.out.println("hello");
}
@Override
public int size() {
return 0;
}
}
@Test
//測試泛型介面
public void test3(){
Demo demo = new Demo();
demo.show();
}
4.3自定義泛型方法
- 方法,也可以被泛型化,不管此時定義在其中的類是不是泛型類。在泛型方法中可以定義泛型引數,此時,引數的型別就是傳入資料的型別。
- 泛型方法的格式:
[訪問許可權]<泛型>返回型別 方法名(泛型標識 引數名稱])丟擲的異常
- 泛型方法宣告泛型時也可以指定上限
程式碼示例:
//泛型方法:在方法中出現了泛型的結構,泛型引數與類的泛型引數沒有任何關係。
//換句話說,泛型方法所屬的類是不是泛型類都沒有關係。
//泛型方法,可以宣告為靜態的。
// 原因:泛型引數是在呼叫方法時確定的。並非在例項化類時確定。
public static <E> List<E> copyFromArryToList(E[] arr) {
ArrayList<E> list = new ArrayList<>();
for (E e :list) {
list.add(e);
}
return list;
}
4.4總結:
- 泛型實際上就是標籤,宣告時不知道型別,再使用時指明
- 定義泛型結構,即:泛型類、介面、方法、構造器時貼上泛型的標籤
- 用泛型定義類或藉口是放到類名或介面名後面,定義泛型方法時在方法名前加上
5.泛型的應用場景
DAO.java
:定義了運算元據庫中的表的通用操作。 ORM思想(資料庫中的表和Java中的類對應)
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;
}
}
CustomerDAO.java
:
public class CustomerDAO extends DAO<Customer>{//只能操作某一個表的DAO
}
StudentDAO.java
:
public class StudentDAO extends DAO<Student> {//只能操作某一個表的DAO
}
四、泛型在繼承上的體現
泛型在繼承方面的體現:
雖然類A是類B的父類,但是 G<A>
和 G<B>
二者不具備子父類關係,二者是並列關係。
補充:類A是類B的父類,A<G>
是 B<G>
的父類
程式碼示例:
@Test
public void test1(){
Object obj = null;
String str = null;
obj = str;
Object[] arr1 = null;
String[] arr2 = null;
arr1 = arr2;
//編譯不透過
// Date date = new Date();
// str = date;
List<Object> list1 = null;
List<String> list2 = new ArrayList<String>();
//此時的list1和list2的型別不具子父類關係
//編譯不透過
// list1 = list2;
/*
反證法:
假設list1 = list2;
list1.add(123);導致混入非String的資料。出錯。
*/
show(list1);
show1(list2);
}
public void show1(List<String> list){
}
public void show(List<Object> list){
}
@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<>();
}
五、萬用字元
1.萬用字元的使用
-
使用型別萬用字元:
?
比如:
List<?>
,Map<?,?>
List<?>
是List<String>
、List<Object>
等各種泛型 List 的父類。 -
讀取
List<?>
的物件list中的元素時,永遠是安全的,因為不管list的真實型別是什麼,它包含的都是Object -
寫入list中的元素時,不可以。因為我們不知道c的元素型別,我們不能向其中新增物件。 除了新增null之外。
說明:
-
將任意元素加入到其中不是型別安全的
Collection<?> c = new ArrayList<String>()
c.add(new Object());//編譯時錯誤
因為我們不知道c的元素型別,我們不能向其中新增物件。add 方法有型別引數 E 作為集合的元素型別。我們傳給add的任何引數都必須是一個已知型別的子類。因為我們不知道那是什麼型別,所以我們無法傳任何東西進去。
-
唯一的例外的是 null,它是所有型別的成員。
-
我們可以呼叫
get()
方法並使用其返回值。返回值是一個未知的型別,但是我們知道,它總是一個Object。
程式碼示例:
@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;
//新增(寫入):對於List<?>就不能向其內部新增資料。
//除了新增null之外。
// list.add("DD");
// list.add('?');
list.add(null);
//獲取(讀取):允許讀取資料,讀取的資料型別為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);
}
}
2.注意點
//注意點1:編譯錯誤:不能用在泛型方法宣告上,返回值型別前面<>不能使用?
public static <?> void test(ArrayList<?> list){
}
//注意點2:編譯錯誤:不能用在泛型類的宣告上
class GenericTypeClass<?>{
}
//注意點3:編譯錯誤:不能用在建立物件上,右邊屬於建立集合物件
ArrayList<> list2 new ArrayList<?>();
3.有限制的萬用字元
-
<?>
:允許所有泛型的引用呼叫 -
萬用字元指定上限
上限
extends
:使用時指定的型別必須是繼承某個類,或者實現某個介面,即<=
-
萬用字元指定下限
下限
super
:使用時指定的型別不能小於操作的類,即>=
-
舉例:
-
<?extends Number>(無窮小, Number\]
只允許泛型為Number及Number子類的引用呼叫
-
<?super Number>\[Number,無窮大)
只允許泛型為Number及Number父類的引用呼叫
-
<? extends Comparable>
只允許泛型為實現 Comparable介面的實現類的引用呼叫
-
程式碼示例:
@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;
// list1 = list5;
// list2 = list3;
list2 = list4;
list2 = list5;
//讀取資料:
list1 = list3;
Person p = list1.get(0);
//編譯不透過
//Student s = list1.get(0);
list2 = list4;
Object obj = list2.get(0);
////編譯不透過
// Person obj = list2.get(0);
//寫入資料:
//編譯不透過
// list1.add(new Student());
//編譯透過
list2.add(new Person());
list2.add(new Student());
}