1.泛型概述
1.1.為什么使用泛型
沒有泛型,在編寫代碼時只能使用具體類型或Object類型,無法做到使用者想要使用什么類型就是類型。比如:創建一個方法,形參需要指定需要使用的數據類型,在創建方法之初就已經決定了該方法可以處理的數據類型,這大大限制了編程的靈活性。正因如此,才出現了在使用時才決定具體類型是什么的泛型編程。
1.2.泛型是什么
泛:廣泛、普遍,非具體的東西,泛型就是定義之初用符號表示不具體的類型,在使用的時候才動態地指定具體的類型。更應該明白這種泛型編程設計思想,使用泛型帶來的好處是代碼更加簡潔、更加靈活、使程序更加健壯(編譯期沒警告,運行期不會出現類強轉異常--ClassCastException)。
2.泛型接口、類、方法
泛型允許在定義接口、類、方法時使用,將在聲明變量、創建對象、調用方法時動態地指定。
2.1.泛型接口
定義泛型接口:比如集合中的List接口
// 定義接口時指定泛型:E,E類型在接口中就可以作為類型使用
public interface List<E> extends Collection<E>{
……
boolean add(E e);
Iterator<E> iterator();
……
}
// 定義接口時指定泛型:K 和 V,K和V類型在接口中就可以作為類型使用
public interface Map<K,V>{
……
Set<K> keySet();
Collection<V> values();
Set<Map.Entry<K, V>> entrySet();
……
}
使用泛型接口:List接口的泛型類型E,在使用時指定為具體類型String
public static void main(String[] args) {
List<String> list = new ArrayList<>();// 指定泛型類型E=String
list.add("我只認識字符串");//boolean add(E e); 等價於boolean add(String e);
Iterator<String> iterator = list.iterator();//Iterator<E> iterator();
while (iterator.hasNext()){
String next = iterator.next();//不需要強轉為String
System.out.println(next);
}
}
關於泛型接口Map<K,V> 集合怎么用,就自行編寫感受下。
2.2.泛型類
普通泛型類
定義泛型類
public class DemoFx<D> {
private D dd;
public D getDd(){
return this.dd;
}
public void setDd(D dd){
this.dd = dd;
}
}
使用泛型類
public static void main(String[] args) {
DemoFx<String> stringDemoFx = new DemoFx<>();
stringDemoFx.setDd("我是字符串類型");
System.out.println(stringDemoFx.getDd());
}
泛型類的繼承與實現
定義泛型類:以ArrayList 類為例,繼承泛型抽象類和實現泛型接口:
public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable{
……
public E get(int index) {
rangeCheck(index);
return elementData(index);
}
……
}
使用泛型類
public static void main(String[] args) {
List<String> list = new ArrayList<String>();// 指定泛型類型E=String
list.add("我只認識字符串");
String s = list.get(0);// 返回值為String
}
2.3.泛型方法
定義泛型方法:還是ArrayList案例
public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable{
……
public <T> T[] toArray(T[] a) {
if (a.length < size)
// Make a new array of as runtime type, but my contents:
return (T[]) Arrays.copyOf(elementData, size, a.getClass());
System.arraycopy(elementData, 0, a, 0, size);
if (a.length > size)
a[size] = null;
return a;
}
……
}
使用泛型方法:public <T> T[] toArray(T[] a)
public static void main(String[] args) {
List<String> list = new ArrayList<String>();
list.add("s1");
list.add("s2");
list.add("sn");
// public <T> T[] toArray(T[] a)
String[] strings = list.toArray(new String[list.size()]);
System.out.println(Arrays.asList(strings));
}
3.類型通配符
3.1.使用類型通配符
通配符表示符號是問號<?>,它是未知類型,可以匹配任何類型,也稱為無界通配符。
對比”通配符“和”泛型“創建的方法
// 通配符定義
public void foreach(List<?> list){
for (int i =0 ;i<list.size();i++) {
Object o = list.get(i);
System.out.println(o.toString());
}
}
// 泛型定義
public <T> void foreach2(List<T> list){
for(T t : list){
System.out.println(t.toString());
}
}
// 使用
public static void main(String[] args) {
List<String> list = new ArrayList<String>();
list.add("s1");
list.add("s2");
list.add("sn");
Demo demo = new Demo();
demo.foreach(list); // 通配符
demo.foreach2(list); // 泛型
}
通配符和泛型都可以實現相同的效果,並且泛型方法還可以使用本身定義的泛型類型,而通配符的”?“不可以當作數據類型來使用,所以通配符方法案例中只能用Object來接收list的元素,這也是通配符的缺點:無法確定未知類型是什么類型。
所以通配符的出現到底有什么用呢?
通配符為泛型的一種特例,無需定義既可在形參中使用的未知類型。
泛型和通配符的區別
-
Java編譯器把泛型【T】推斷成T類型,在代碼塊中允許出現 T類型變量;而把通配符【?】推斷成未知類型,不存在 ?類型變量; -
Class<T>需要依賴於T,需要在方法聲明時指定<T>,而Class<?>則不需要;
這樣可能更好理解泛型和通配符:泛型 強調的是類型,通配符 強調的是符號。
Class<?> 表示任意類型,但又不等同於Class<Object>,前者在類型不匹配的情況下只能夠插入null,但是后者可以插入Object或任何Object對象的子類。
例如:不能往List<?> list里添加任意類型的對象,除了null
3.2.類型上限
通配符上限:<? extends Demo> ,通配符【?】的上限是Demo類型,既是<? extends Demo> 的范圍是Demo或其子類類型。
泛型上限:<T extends Demo> ,和通配符理解一樣。類型上限如圖
案例:
創建三個類DemoFather、Demo、DemoChildren,關系如上圖
public class DemoTest {
public static void main(String[] args) {
List<DemoChildren> demoChildrens = new ArrayList<>();
demoChildrens.add(new DemoChildren());
demoChildrens.add(new DemoChildren());
DemoTest test = new DemoTest();
test.testDemo(demoChildrens); // 通配符
test.testDemo2(demoChildrens);// 泛型
}
// 通配符上限:控制list 集合允許的類型范圍為Demo或其子類
public void testDemo(List<? extends Demo> list){
// 若無上限,這里只能用Object類型代替Demo類型
for (Demo demo : list){
System.out.println(demo.toString());
}
}
// 泛型上限:控制list 集合允許的類型范圍為Demo或其子類
public <T extends Demo> void testDemo2(List<T> list){
for (T t : list){
System.out.println(t.toString());
}
// or
for(Demo demo:list){
System.out.println(demo.toString());
}
}
}
泛型的上限是在定義時確定上限<T extends Demo>;通配符直接在形參上確定上限<? extends Demo>。其實都很好理解,類型上限就是在一般寫法的基礎上加入范圍“上限”,既是 extends xxx。
源碼的一些例子
// 接口泛型上限
public interface ObservableArray<T extends ObservableArray<T>> extends Observable {……}
// 抽象類泛型上限
public abstract class ArrayListenerHelper<T extends ObservableArray<T>> extends ExpressionHelperBase {……}
public abstract class CellBehaviorBase<T extends Cell> extends BehaviorBase<T> {……}
// 方法泛型上限
public static <T extends Number> ReadOnlyLongProperty readOnlyLongProperty(final ReadOnlyProperty<T> property) {……}
// 通配符上限
void putAll(Map<? extends K, ? extends V> m);
3.3.類型下限
通配符下限:<? super Demo> ,通配符【?】的下限是Demo類型,既是<? super Demo> 的范圍是Demo的父類類型。
泛型下限:無。主要是因為類型下限會令人困惑並且不是特別有用。為什么類型參數沒有下限的一些解釋:http://www.angelikalanger.com/GenericsFAQ/FAQSections/TypeParameters.html#FAQ107
public static void main(String[] args) {
Demo demo = new Demo();
List<Demo> demos = new ArrayList<>();
demos.add(demo);
DemoTest test = new DemoTest();
test.testSuper(demos);
DemoFather demoFather = new DemoFather();
List<DemoFather> demoFathers = new ArrayList<>();
demoFathers.add(demoFather);
DemoTest test2 = new DemoTest();
test2.testSuper(demoFathers);
}
public void testSuper(List<? super Demo> list){
// 雖然有下限,但無法直接使用Demo類型接收參數
for (Object obj : list){
System.out.println(obj.toString());
}
}
雖然有下限,但無法直接使用Demo類型接收參數。這就像“向上轉型”和“向下轉型”,向上轉型是自動的,向下轉型需要強轉;類型上限可以使用最大類型(父類)接收比它小的類型(子類),類型下限不可以使用最小類型(子類)接受可能比它大的類型(父類)。
源碼的一些例子
public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{
……
public void sort(Comparator<? super E> c) {
final int expectedModCount = modCount;
Arrays.sort((E[]) elementData, 0, size, c);
if (modCount != expectedModCount) {
throw new ConcurrentModificationException();
}
modCount++;
}
}
public class Arrays {
public static <T> void sort(T[] a, int fromIndex, int toIndex,
Comparator<? super T> c) {
if (c == null) {
sort(a, fromIndex, toIndex);
} else {
rangeCheck(a.length, fromIndex, toIndex);
if (LegacyMergeSort.userRequested)
legacyMergeSort(a, fromIndex, toIndex, c);
else
TimSort.sort(a, fromIndex, toIndex, c, null, 0, 0);
}
}
}
關於泛型字母E、K、V、T是什么?
-
E表示Element, -
K表示Key, -
V表示Value, -
T表示Type, -
N表示Number, -
? 表示 未知類型
除了【?】,其他泛型符號你寫成字符串都可以,但要注意可讀性,一般都是使用單個大寫字母表示,不然代碼反而不簡潔、不易閱讀。
4.泛型實現原理--類型擦除
把一個具有泛型信息的對象 賦給 另一個沒有泛型信息的變量引用,類型信息都將被擦除。
案例一:不指定泛型上限,類型擦除后為Object
public static void main(String[] args) {
// 定義集合泛型E = String
List<String> stringArrayList = new ArrayList<>();
stringArrayList.add("test1");
// 獲取到的類型為:String
String s = stringArrayList.get(0);
// 把帶有泛型信息的stringArrayList 對象賦給不確定泛型的List
List listObject = stringArrayList;
// listObject 只知道get的類型為Object,而不是String
Object obj = listObject.get(0);
}
案例二:指定泛型上限,類型擦除后為上限的類型
public class DemoFather {}
public class Demo extends DemoFather{}
public class DemoChildren<T extends DemoFather> {
private T t;
public T getT(){
return this.t;
}
public void setT(T t){
this.t= t;
}
}
// 測試public class DemoTest {
public static void main(String[] args) {
//class DemoChildren<T extends DemoFather>,指定泛型T=Demo類型
DemoChildren<Demo> demoChildren = new DemoChildren<Demo>();
// 拿到的方法類型確實是T=Demo類型
Demo demo = demoChildren.getT();
// 把帶有泛型信息的 demoChildren 對象賦給不確定泛型的demoChildren2
DemoChildren demoChildren2 =demoChildren;
// 再來獲取方法的類型時,變為了上限的DemoFather類型
DemoFather demoFather = demoChildren2.getT();
}
}
結論:
“指定泛型上限時,類型擦除后為上限的類型;反之是Object類型,因為Java中所有類都默認繼承了Object類。
”
所以案例二的泛型類在編譯階段是長這樣的
public class DemoChildren {
private DemoFather t;
public DemoFather getT(){
return this.t;
}
public void setT(DemoFather t){
this.t= t;
}
}
// 原來的泛型類,對比一下
public class DemoChildren<T extends DemoFather> {
private T t;
public T getT(){
return this.t;
}
public void setT(T t){
this.t= t;
}
}
Java往期文章
Java全棧學習路線、學習資源和面試題一條龍
我心里優秀架構師是怎樣的?
免費下載經典編程書籍