1. 程式人生 > 實用技巧 >Java進階--泛型

Java進階--泛型


1.泛型概述

泛型:是一種未知的資料型別,當我們不知道使用什麼資料型別的時候,可以使用泛型。

在學習集合時,我們都知道集合中是可以存放任意物件的,只要把物件儲存集合後,那麼這時他們都會被提升成Object型別。當我們在取出每一個物件,並且進行相應的操作,這時必須採用型別轉換。

1.1 不使用泛型的弊端

集合可以儲存任何型別的資料,這會引發異常。
大家觀察下面程式碼:

public class GenericDemo {
	public static void main(String[] args) {
		Collection coll = new ArrayList();
		coll.add("abc");
		coll.add("itcast");
		coll.add(5); //由於集合沒有做任何限定,任何型別都可以給其中存放
		Iterator it = coll.iterator();
		while(it.hasNext()){
			//需要列印每個字串的長度,就要把迭代出來的物件轉成String型別
			String str = (String) it.next();
			System.out.println(str.length());
		}
	}
}


程式在執行時發生了問題java.lang.ClassCastException,會發生型別轉換異常呢?由於集合中什麼型別的元素都可以儲存。導致取出時強轉引發執行時 ClassCastException。

1.2使用泛型的好處

  • 將執行時期的ClassCastException,轉移到了編譯時期變成了編譯失敗。
  • 避免了型別強轉的麻煩。
  • 泛型:可以在類或方法中預支地使用未知的型別。


通過我們如下程式碼體驗一下:

public class GenericDemo2 {
	public static void main(String[] args) {
        Collection<String> list = new ArrayList<String>();
        list.add("abc");
        list.add("itcast");
        // list.add(5);//當集合明確型別後,存放型別不一致就會編譯報錯
        // 集合已經明確具體存放的元素型別,那麼在使用迭代器的時候,迭代器也同樣會知道具體遍歷元素型別
        Iterator<String> it = list.iterator();
        while(it.hasNext()){
            String str = it.next();
            //當使用Iterator<String>控制元素型別後,就不需要強轉了。獲取到的元素直接就是String型別
            System.out.println(str.length());
        }
	}
}

tips:泛型是資料型別的一部分,我們將類名與泛型合併一起看做資料型別。

2.泛型的定義與使用


泛型,用來靈活地將資料型別應用到不同的類、方法、介面當中。將資料型別作為引數進行傳遞。

2.1 定義和使用含有泛型的類

定義格式:

修飾符 class 類名<代表泛型的變數> {  }


例如,API中的ArrayList集合:

class ArrayList<E>{ 
    public boolean add(E e){ }

    public E get(int index){ }
   	....
}


使用泛型: 即什麼時候確定泛型。


在建立物件的時候確定泛型




例如,ArrayList<String> list = new ArrayList<String>();


此時,變數E的值就是String型別,那麼我們的型別就可以理解為:

class ArrayList<String>{ 
     public boolean add(String e){ }

     public String get(int index){  }
     ...
}


再例如,ArrayList<Integer> list = new ArrayList<Integer>();
此時,變數E的值就是Integer型別,那麼我們的型別就可以理解為:

class ArrayList<Integer> { 
     public boolean add(Integer e) { }

     public Integer get(int index) {  }
     ...
}


舉例自定義泛型類

public class MyGenericClass<MVP> {
	//沒有MVP型別,在這裡代表 未知的一種資料型別 未來傳遞什麼就是什麼型別
	private MVP mvp;
     
    public void setMVP(MVP mvp) {
        this.mvp = mvp;
    }
     
    public MVP getMVP() {
        return mvp;
    }
}


使用:

public class GenericClassDemo {
  	public static void main(String[] args) {		 
         // 建立一個泛型為String的類
         MyGenericClass<String> my = new MyGenericClass<String>();    	
         // 呼叫setMVP
         my.setMVP("大鬍子登登");
         // 呼叫getMVP
         String mvp = my.getMVP();
         System.out.println(mvp);
         //建立一個泛型為Integer的類
         MyGenericClass<Integer> my2 = new MyGenericClass<Integer>(); 
         my2.setMVP(123);   	  
         Integer mvp2 = my2.getMVP();
    }
}

2.2 含有泛型的方法

定義格式:

修飾符 <代表泛型的變數> 返回值型別 方法名(引數){  }


例如,

public class MyGenericMethod {	  
    public <MVP> void show(MVP mvp) {
    	System.out.println(mvp.getClass());
    }
    
    public <MVP> MVP show2(MVP mvp) {	
    	return mvp;
    }
}


使用格式:呼叫方法時,確定泛型的型別

public class GenericMethodDemo {
    public static void main(String[] args) {
        // 建立物件
        MyGenericMethod mm = new MyGenericMethod();
        // 演示看方法提示
        mm.show("aaa");
        mm.show(123);
        mm.show(12.45);
    }
}

2.3 含有泛型的介面


定義格式:

修飾符 interface介面名<代表泛型的變數> {  }


例如,

public interface MyGenericInterface<E>{
	public abstract void add(E e);
	
	public abstract E getE();  
}


使用格式:


1、定義類時確定泛型的型別
例如

public class MyImp1 implements MyGenericInterface<String> {
	@Override
    public void add(String e) {
        // 省略...
    }

	@Override
	public String getE() {
		return null;
	}
}

此時,泛型E的值就是String型別。


2、始終不確定泛型的型別,直到建立物件時,確定泛型的型別


例如

public class MyImp2<E> implements MyGenericInterface<E> {
	@Override
	public void add(E e) {
       	 // 省略...
	}

	@Override
	public E getE() {
		return null;
	}
}


確定泛型:

/*
 * 使用
 */
public class GenericInterface {
    public static void main(String[] args) {
        MyImp2<String>  my = new MyImp2<String>();  
        my.add("aa");
    }
}

3.泛型萬用字元


當使用泛型類或者介面時,傳遞的資料中,泛型型別不確定,可以通過萬用字元<?>表示。但是一旦使用泛型的萬用字元後,只能使用Object類中的共性方法,集合中元素自身方法無法使用。

萬用字元基本使用

泛型的萬用字元:不知道使用什麼型別來接收的時候,此時可以使用?,?表示未知萬用字元。
此時只能接受資料,不能往該集合中儲存資料。


舉個例子大家理解使用即可:

package 集合和泛型.Generic;

import java.util.ArrayList;
import java.util.Iterator;

public class Demo05Generic {
    public static void main(String[] args) {
        ArrayList<Integer> lsit01 = new ArrayList<>();
        lsit01.add(1);
        lsit01.add(2);
        lsit01.add(3);
        
        ArrayList<String> lsit02 = new ArrayList<>();
        lsit02.add("a");
        lsit02.add("b");
        lsit02.add("c");

        PrintArray(lsit01);
        PrintArray(lsit02);
        
        // ArrayList<?> lsit03 = new ArrayList<?>(); 定義不能使用萬用字元
    }
    
    // 定義一個方法,能遍歷所有的ArrayList集合
    public static void PrintArray(ArrayList<?> list){
        Iterator<?> it = list.iterator();
        while (it.hasNext()){
            Object obj = it.next();
            System.out.println(obj);
        }
    }
}

tips:泛型不存在繼承關係

萬用字元高階使用----受限泛型

之前設定泛型的時候,實際上是可以任意設定的,只要是類就可以設定。但是在JAVA的泛型中可以指定一個泛型的上限下限


泛型的上限

  • 格式型別名稱 <? extends 類 > 物件名稱
  • 意義只能接收該型別及其子類


泛型的下限

  • 格式型別名稱 <? super 類 > 物件名稱
  • 意義只能接收該型別及其父型別


比如:現已知Object類,String 類,Number類,Integer類,其中Number是Integer的父類

public static void main(String[] args) {
    Collection<Integer> list1 = new ArrayList<Integer>();
    Collection<String> list2 = new ArrayList<String>();
    Collection<Number> list3 = new ArrayList<Number>();
    Collection<Object> list4 = new ArrayList<Object>();
    
    getElement(list1);
    getElement(list2);//報錯
    getElement(list3);
    getElement(list4);//報錯
  
    getElement2(list1);//報錯
    getElement2(list2);//報錯
    getElement2(list3);
    getElement2(list4);
  
}
// 泛型的上限:此時的泛型?,必須是Number型別或者Number型別的子類
public static void getElement1(Collection<? extends Number> coll){}
// 泛型的下限:此時的泛型?,必須是Number型別或者Number型別的父類
public static void getElement2(Collection<? super Number> coll){}