概念
定義類、接口、方法時,同時聲明了一個或多個類型變量(如<E>),稱為泛型類、泛型接口、泛型方法、它們統稱為泛型。
語法
public class ArrayList<E>{}
E可以接收不同類型的數據,可以是字符串,也可以是學生類等東西。
作用:泛型提供了在編譯階段約束所能操作的數據類型,并自動進行檢查的能力,這樣可以避免強制類型轉換,及其可能出現的異常。
接下來用常見API,ArrayList集合舉例
package GenericityDemo;import java.util.ArrayList;public class GenericDemo1 {public static void main(String[] args) {
// //認識泛型
// ArrayList list = new ArrayList(); //沒有使用泛型,存儲任意類型數據
// list.add("hello");
// list.add(100);
// list.add(true);
// //獲取數據
// for (int i = 0; i < list.size(); i++)
// {
// Object obj = list.get(i); //必須用Object接收,因為存儲的任意類型數據
// //數據轉型處理
// String s = (String) list.get(i);//運行到整型時程序會崩,因此最好要統一數據類型
// System.out.println(s);
// }ArrayList<String> list = new ArrayList<String>(); //泛型約束,只能存儲String類型數據list.add("hello");list.add("world");//list.add(90); //類型不是泛型約束的類型,報錯for (int i = 0; i < list.size(); i++){String s = list.get(i); //類型確定,不用轉型System.out.println(s); //不會出現異常}}
}
一、泛型類
泛型類(Generic Class)?是通過類型參數化定義的類,其核心作用是通過類型抽象實現代碼的通用性、類型安全性和靈活性。
語法
修飾符 class 類名<類型變量,類型變量....>{}
public class ArrayList<E>{
......
}
示例
創建一個ArrayList集合類(其實就是在自己的類里用之前的API,但接收的數據類型由我們限制)
package GenericityDemo;import java.util.ArrayList;public class MyArrayList <E>{private ArrayList list = new ArrayList();public boolean add(E e){list.add(e);return true;}public boolean remove(E e){return list.remove(e);}@Overridepublic String toString() {return list.toString();}
}
package GenericityDemo;public class GenericDemo2 {public static void main(String[] args) {//目標:學會自定義泛型類//需求:模擬ArrayList集合,自定義一個集合MyArrayListMyArrayList<String> list = new MyArrayList<>();//jdk7支持后面不寫泛型類型list.add("java");list.add("后端");System.out.println(list);}
}
類型變量建議使用大寫的英文字母,常用的有:E、T、K、V等
E:常用于集合類(如?List
、Set
),強調泛型參數是容器內的元素。
T:當泛型類或方法不限制具體類型時,常用?T
?作為默認類型占位符。
K/V:用于鍵值對數據結構(如?Map
)中,明確區分鍵和值的類型。
二、泛型接口
泛型接口(Generic Interface)?是通過類型參數化定義的接口,其核心作用是通過類型抽象提高代碼的復用性、類型安全性和靈活性。
修飾符 interface 接口名<類型變量,類型變量...>{
}
public interface A<E>{
...
}
示例
對學生數據/老師數據都要進行增刪查改操作。
定義一個泛型接口可以接學生或老師對象進行操作,可以實現一個方法操作兩個角色。
package GenericityDemo3;public interface Data<T> {
// void add(Student s);
// void add(Teacher t);//類型約定死了,,只能存Student或Teacher//利用泛型接口void add(T t); //此時泛型可以為Teacher也能為Studentvoid delete(T t);void update(T t);T query(int id);
}
(此示例并沒有真正實現增刪查改操作,只為示范)
package GenericityDemo3;public class Student {
}
package GenericityDemo3;public class Teacher {
}
創造兩個類用于實現Data,一個實現老師操作,一個實現學生操作
package GenericityDemo3;public class TeacherData implements Data<Teacher>{//操作老師數據@Overridepublic void add(Teacher teacher) {}@Overridepublic void delete(Teacher teacher) {}@Overridepublic void update(Teacher teacher) {}@Overridepublic Teacher query(int id) {return null;}
}
package GenericityDemo3;public class StudentData implements Data<Student>{//專門操作學生對象@Overridepublic void add(Student student) {}@Overridepublic void delete(Student student) {}@Overridepublic void update(Student student) {}@Overridepublic Student query(int id) {return null;}
}
package GenericityDemo3;public class GenericDemo3 {public static void main(String[] args) {//目標:搞清楚泛型接口的基本應用//需求:項目需要對學生數據/老師數據都要進行增刪查改操作StudentData studentData = new StudentData();studentData.add(new Student()); //添加學生數據.對學生操作Student s = studentData.query(1001);}
}
通過泛型接口,可以為不同類型的數據定義統一的處理邏輯,同時避免強制類型轉換和運行時類型錯誤。
三、泛型方法
泛型方法(Generic Method)?是通過在方法簽名中聲明類型參數來實現的方法級別泛型化。它與泛型類的主要區別在于作用范圍:泛型方法的類型參數僅作用于該方法內部,而非整個類。
語法
修飾符<類型變量,類型變量,...>返回值類型 方法名(形參列表){}
示例
現在我需要一個方法將數組的內容打印出來
package GenericDemo4;public class GenericDemo4 {//目標:理解泛型方法public static void main(String[] args) {//需求:打印任意數組的內容String[] arr1 = {"hello","world","java"};printArray(arr1);Student[] arr2 = new Student[3];//printArray(arr2);//只能接收String類型數組Student s1 = getMax(arr2); printArray2(arr1);getMax(arr2); //泛型方法,可以接收任意類型的數組,避免強轉}public static void printArray(String[] arr){for (int i = 0; i < arr.length; i++) {System.out.println(arr[i] + " ");}}public static <T> void printArray2(T[] arr){for (int i = 0; i < arr.length; i++) {System.out.println(arr[i] + " ");}}public static <T> T getMax(T[] name){return null;}
}
上述代碼有兩個方法,一個是printArray一個是printArray2,但printArray有一個缺點就是只能打印字符串數組的內容,若是其他數組則無法接收。因此,我們可以做一個泛型方法即printArray2,它可以接收String數組也可以接收Student數組,實現了一個方法解決多個問題。
四、通配符與上下限
當我們要傳的參數為多個集合,每個集合都是繼承于某個父類的子類的集合時,我們就可以通過使用通配符來接收不同的子類集合。(注:例如兩個集合為ArrayList<Cat>和ArrayList<Dog>,不能用ArrayList<Animal>接收,因為他們本質上是不同的集合,雖然他們的子類繼承了同一個父類)
示例
package GenericDemo4;public class Car {
}
package GenericDemo4;public class BYD extends Car{}
package GenericDemo4;public class BMW extends Car{
}
package GenericDemo4;import java.util.ArrayList;public class GenericDemo5 {public static void main(String[] args) {//目標:理解通配符和上下限ArrayList<BYD> byds = new ArrayList<>();byds.add(new BYD());byds.add(new BYD());byds.add(new BYD());//go(byds); //報錯不具備通用性ArrayList<BMW> bmws = new ArrayList<>();bmws.add(new BMW());bmws.add(new BMW());bmws.add(new BMW());go(bmws);}//模擬極品飛車游戲//雖然比亞迪和寶馬是Car的子類,但ArrayList<BYD>和ArrayList<BMW>和ArrayList<Car>是不同的,因此不能用多態
// public static void go(ArrayList<BMW> cars)
// {
//
// }//通配符,在使用泛型時代表一切類型public static void go(ArrayList<?> cars){}
}
然而,若是這么寫這個方法能接收的集合范圍又太大了,連貓和狗都可以進這個開汽車的方法,因此需要使用上下限來加以限制。
? extends Car ?能接收Car或者Car的子類(泛型上限)
? super Car ?能接收Car或者Car的父類(泛型下限)
package GenericDemo4;import java.util.ArrayList;public class GenericDemo5 {public static void main(String[] args) {//目標:理解通配符和上下限ArrayList<BYD> byds = new ArrayList<>();byds.add(new BYD());byds.add(new BYD());byds.add(new BYD());//go(byds); //報錯不具備通用性ArrayList<BMW> bmws = new ArrayList<>();bmws.add(new BMW());bmws.add(new BMW());bmws.add(new BMW());go(bmws);}//模擬極品飛車游戲//雖然比亞迪和寶馬是Car的子類,但ArrayList<BYD>和ArrayList<BMW>和ArrayList<Car>是不同的,因此不能用多態
// public static void go(ArrayList<BMW> cars)
// {
//
// }//通配符,在使用泛型時代表一切類型//為了防止將狗,貓等不屬于車的類型傳入方法,使用泛型的上下限限制//? extends Car ?能接收Car或者Car的子類(泛型上限)//? super Car ?能接收Car或者Car的父類(泛型下限)public static void go(ArrayList<? extends Car> cars){}
}
這樣,就能將這個方法限制在接收汽車類的子類了。
五、泛型支持的數據類型
泛型不支持基本的數據類型,即int,double等數據類型,原因是泛型工作在編譯階段,編譯結束后系統會進行泛型擦除,所有類型都會轉變為Object類型。而Object是對象類型,接收的是對象,數字等類型不是對象,因此不接收基本的數據類型。為了能夠兼容基本的數據類型,java的庫里添加了包裝類,用于將基本數據類型變為對象。
包裝類有8種。
int -> Integer char -> Character ,其它的都是首字母大小寫。
package GenericDemo5;import java.util.ArrayList;
import java.util.Objects;public class GenericDemo5 {public static void main(String[] args) {//目標:搞清楚泛型和集合不支持基本數據類型,只能支持對象類型(引用類型)//ArrayList<double> list = new ArrayList<>(); //報錯,不支持//泛型擦除:泛型工作在編譯階段,等編譯后泛型就沒用了,所以泛型在編譯后都會被擦除。所有類型會恢復為Object類型。//Object是對象類型,不能直接指向某個數,只能指向某個對象。//因此要使用包裝類,比如Integer、Double、Character、Boolean等。//包裝類的作用:把基本數據類型包裝成對象類型。//ArrayList<int> list = new ArrayList<>();//list.add(12); //12不是對象,報錯ArrayList<Integer> list = new ArrayList<>();list.add(12); //自動裝箱,把基本數據類型12包裝成Integer對象。int rs= list.get(0); //自動拆箱,把Integer對象12拆箱成基本數據類型。//把基本數據類型變成包裝類對象//手工包裝//Integer i = new Integer(12);//從jdk9開始,這個方法被淘汰了(過時)//推薦用valueOf的原因:valueOf方法中緩存了-128~127的數值,重復創建這個范圍內的數字時不會創建新對象,而是直接返回緩存中的對象。//緩存對象范圍是-128~127,如果超過這個范圍,就會創建新的對象。//更優雅,節約內存。Integer i = Integer.valueOf(12); //此時i為對象,里面存儲的是12Integer i2 = Integer.valueOf(12);System.out.println(i == i2); //ture, 因為i和i2是同一個對象//自動裝箱Integer i3 = 12; //與Integer i = Integer.valueOf(12)一個意思Integer i4 = 12;System.out.println(i3 == i4);//自動拆箱:把包裝類型的對象直接給基本類型的數據int i5 = i3;System.out.println(i5);System.out.println("========================================================");//包裝類新增的功能//1、把基本類型的數據變成字符串int j=23;String rs1 = Integer.toString(j);System.out.println(rs1+1); //不是24,說明已經是字符串Integer i6=j;String rs2 = i6.toString();System.out.println(rs2+1);String rs3 = i6+""; //直接加""轉換為字符串System.out.println(rs3+1);System.out.println("========================================================");//把字符串數值轉換成對應的基本數據類型String str = "123";int i1 = Integer.parseInt(str);int i9 = Integer.valueOf(str);System.out.println(i1+1);System.out.println(i9+1);}
}