国产xxxx99真实实拍_久久不雅视频_高清韩国a级特黄毛片_嗯老师别我我受不了了小说

資訊專欄INFORMATION COLUMN

Java 泛型

Zachary / 2453人閱讀

摘要:唯一的例外是,它是所有引用類型的實(shí)例另一方面,程序可以調(diào)用方法來返回集合指定索引處的元素,其返回值是一個(gè)未知類型,但可以肯定的是,它總是一個(gè)。

泛型入門 編譯時(shí)不檢查類型的異常
public class ListErr
{
    public static void main(String[] args)
    {
        // 創(chuàng)建一個(gè)只想保存字符串的List集合
        List strList = new ArrayList();
        strList.add("布達(dá)佩斯");
        strList.add("布拉格");
        // "不小心"把一個(gè)Integer對(duì)象"丟進(jìn)"了集合
        strList.add(34);     // ①
        strList.forEach(str -> System.out.println(((String)str).length())); // ②
    }
}

上述程序創(chuàng)建了一個(gè)List集合,且該List集合保存字符串對(duì)象——但程序不能進(jìn)行任何限制,如果程序在①處“不小心”把一個(gè)Integer對(duì)象"丟進(jìn)"了List集合,這將導(dǎo)致程序在②處引發(fā)ClassCastException異常,因?yàn)槌绦蛟噲D把一個(gè)Integer對(duì)象轉(zhuǎn)換為String類型

使用泛型

參數(shù)化類型,允許程序在創(chuàng)建集合時(shí)指定集合元素的類型。Java的參數(shù)化類型被稱為泛型(Generic)

class GenericList
{
    public static void main(String[] args)
    {
        // 創(chuàng)建一個(gè)只想保存字符串的List集合
        List strList = new ArrayList<>();     // ①
        strList.add("布達(dá)佩斯");
        strList.add("布拉格");
        // 下面代碼將引起編譯錯(cuò)誤
        strList.add(34);     // ②
        strList.forEach(str -> System.out.println(((String)str).length())); // ③
    }
}

strList集合只能保存字符串對(duì)象,不能保存其他類型的對(duì)象。創(chuàng)建特殊集合的方法是:在集合接口、類后增加尖括號(hào),尖括號(hào)里放一個(gè)數(shù)據(jù)類型,即表明這個(gè)集合接口、集合類只能保存特定類型的對(duì)象

①類型聲明,在創(chuàng)建這個(gè)ArrayList對(duì)象時(shí)也指定了一個(gè)類型參數(shù);②引發(fā)編譯異常;③不需要進(jìn)行強(qiáng)制類型轉(zhuǎn)換

泛型使程序更加簡(jiǎn)潔,集合自動(dòng)記住所有集合元素的數(shù)據(jù)類型,從而無須對(duì)集合元素進(jìn)行強(qiáng)制類型轉(zhuǎn)換

Java7泛型的“菱形”語法

Java允許在構(gòu)造器后不需要帶完整的泛型信息,只要給出一對(duì)尖括號(hào)(<>)即可,Java可以推斷尖括號(hào)里應(yīng)該是什么泛型信息

public class DiamondTest
{
    public static void main(String[] args)
    {
        // Java自動(dòng)推斷出ArrayList的<>里應(yīng)該是String
        List countries = new ArrayList<>();
        countries.add("法蘭西第五共和國(guó)");
        countries.add("西班牙王國(guó)");
        // 遍歷countries集合,集合元素就是String類型
        countries.forEach(ele -> System.out.println(ele.length()));
        // Java自動(dòng)推斷出HashMap的<>里應(yīng)該是String , List
        Map> citiesInfo = new HashMap<>();
        // Java自動(dòng)推斷出ArrayList的<>里應(yīng)該是String
        List cities = new ArrayList<>();
        cities.add("巴黎");
        cities.add("巴塞羅那");
        citiesInfo.put("Bienvenue" , cities);
        // 遍歷Map時(shí),Map的key是String類型,value是List類型
        citiesInfo.forEach((key , value) -> System.out.println(key + "-->" + value));
    }
}
深入泛型

所謂泛型:就是允許在定義類、接口、方法時(shí)指定類型形參,這個(gè)類型形參將在聲明變量、創(chuàng)建對(duì)象、調(diào)用方法時(shí)動(dòng)態(tài)地指定(即傳入實(shí)際的類型參數(shù),也可稱為類型實(shí)參)

定義泛型接口、類

List接口、Iterator接口、Map的代碼片段

// 定義接口時(shí)指定了一個(gè)類型形參,該形參名為E
public interface List       
{
    // 在該接口里,E可作為類型使用
    // 下面方法可以使用E作為參數(shù)類型
    void add(E x);
    Iterator iterator();    // ①
}

// 定義接口時(shí)指定了一個(gè)類型形參,該形參為E
public interface Iterator
{
    // 在該接口里E完全可以作為類型使用
    E next();
    boolean hasNext();
}

// 定義該接口時(shí)指定了兩個(gè)類型形參,其形參名為K、V
public interface Map
{
    // 在該接口里K、V完全可以作為類型使用
    Set keySet();           // ②
    V put(K key,V value);
}

上面代碼說明泛型實(shí)質(zhì):允許在定義接口、類時(shí)聲明類型形參,類型形參在整個(gè)接口、類體內(nèi)可當(dāng)成類型使用

包含泛型聲明的類型可以在定義變量、創(chuàng)建對(duì)象時(shí)傳入一個(gè)類型實(shí)參,從而可以動(dòng)態(tài)地生成無數(shù)個(gè)邏輯上的子類,但這種子類在物理上并不存在

可以為任何類、接口增加泛型聲明(并不是只有集合類才可以使用泛型聲明,雖然集合類是泛型的重要使用場(chǎng)所)

// 定義Onmyoji類時(shí)使用了泛型聲明
public class Onmyoji
{
    // 使用T類型形參定義實(shí)例變量
    private T info;
    public Onmyoji(){}
    // 下面方法使用T類型形參來定義構(gòu)造器
    public Onmyoji(T info)
    {
        this.info = info;
    }
    public T getInfo() 
    {
        return info;
    }
    public void setInfo(T info) 
    {
        this.info = info;
    }
    public static void main(String[] args) 
    {
        //由于傳給T形參的是String,所以構(gòu)造器參數(shù)只能是String
        Onmyoji a1 = new Onmyoji<>("安倍晴明");
        System.out.println(a1.getInfo());
        // 由于傳給T形參的是Double,所以構(gòu)造器參數(shù)只能是Double或double
        Onmyoji a2 = new Onmyoji<>(520.1314);
        System.out.println(a2.getInfo());
    }
}

當(dāng)創(chuàng)建帶泛型聲明的自定義類,為該類定義構(gòu)造器時(shí),構(gòu)造器名還是原來的類名,不要增加泛型聲明

從泛型類派生子類

當(dāng)創(chuàng)建了帶泛型聲明的接口、父類之后,可以為該接口創(chuàng)建實(shí)現(xiàn)類,或從父類派生子類。當(dāng)使用這些接口、父類時(shí)不能再包含類型形參

// 定義類Shikigami繼承Onmyoji類,Onmyoji類不能跟類型形參
public class Shikigami extends Onmyoji{ }        // 錯(cuò)誤

方法中的形參(或數(shù)據(jù)形參)代表變量、常量、表達(dá)式等數(shù)據(jù)。定義方法時(shí),可以聲明數(shù)據(jù)形參;調(diào)用方法(使用方法)時(shí),必須為這些數(shù)據(jù)形參傳入實(shí)際的數(shù)據(jù);與此類似的是,定義類、接口、方法時(shí)可以聲明類型形參,使用類、接口、方法時(shí)應(yīng)為類型形參傳入實(shí)際的類型

// 使用Onmyoji類時(shí)為T形參傳入String類型
public class Shikigami extends Onmyoji{ }        // 正確

調(diào)用方法時(shí)必須為所有的數(shù)據(jù)參數(shù)傳入?yún)?shù)值,而使用類、接口時(shí)也可以不為類型形參傳入實(shí)際的類型參數(shù)

// 使用Onmyoji類時(shí),沒有為T形參傳入實(shí)際的類型參數(shù)
public class Shikigami extends Onmyoji{ }        // 正確

子類需要重寫父類的Getters和Setters方法

private String info;
public String getInfo() 
{
    return "子類"+ super.getInfo();
}
public void setInfo(String info)
{
    this.info = info;
}
并不存在泛型類

ArrayList類,是一種特殊的ArrayList類。該ArrayList對(duì)象只能添加String對(duì)象作為集合元素。但實(shí)際上,系統(tǒng)并沒有為ArrayList生成新的class文件,而且也不會(huì)把ArrayList當(dāng)成新類來處理。因?yàn)椴还芊盒偷臅r(shí)間類型參數(shù)是什么,它們?cè)谶\(yùn)行時(shí)總有同樣的類(class)

// 分別創(chuàng)建List對(duì)象和List對(duì)象
List l1 = new ArrayList<>();
List l2 = new ArrayList<>();
// 調(diào)用getClass()方法來比較l1和l2的類是否相等
System.out.println(l1.getClass() == l2.getClass());        // 輸出true

不管為泛型的類型形參傳入哪一種類型實(shí)參,對(duì)于Java來說,它們依然被當(dāng)成同一個(gè)類處理,在內(nèi)存中也只占用一塊內(nèi)存空間,因此在靜態(tài)方法、靜態(tài)初始化塊或者靜態(tài)變量的聲明和初始化中不允許使用類型形參

public class R
{
    static T info;    // 代碼錯(cuò)誤,不能在靜態(tài)變量聲明中使用類型形參
    T age;
    public void foo(T msg){}
    public static void bar(T msg){}    // 代碼錯(cuò)誤,不能在靜態(tài)方法聲明中使用類型形參
}

由于系統(tǒng)中并不會(huì)真正生成泛型類,所以instanceof運(yùn)算符后不能使用泛型類。

if(cs instanceof List)
{
    ...
} 

類型通配符

如果Foo是Bar的一個(gè)子類型(子類或者子接口),而G是具有泛型聲明的類或接口,G并不是G的子類型

數(shù)組和泛型有所不同,假設(shè)Foo是Bar的一個(gè)子類型(子類或者子接口),那么Foo[]依然是Bar[]的子類型;但G不是G的子類型

Java泛型的設(shè)計(jì)原則是,只要代碼在編譯時(shí)沒有出現(xiàn)警告,就不會(huì)遇到運(yùn)行時(shí)ClassCastException異常

使用類型通配符

為了表示各種泛型List的父類,可以使用類型通配符,類型通配符是一個(gè)問號(hào)(?),將一個(gè)問號(hào)作為類型實(shí)參傳給List集合,寫作:List(意思是未知類型元素的List)。這個(gè)問號(hào)(?)被稱為通配符,它的元素類型可以匹配任何類型

public void test(List c)
{
    for(int i = 0; i < c.size(); i++)
    {
        System.out.println(c.get(i));
    }
}

現(xiàn)在使用任何類型的List來調(diào)用它,程序依然可以訪問集合c中的元素,其類型是Object,這永遠(yuǎn)是安全的,因?yàn)椴还躄ist的真實(shí)類型是什么,它包含的都是 Object

這種帶通配符的List僅表示它是各種泛型List的父類,并不能把元素加入到其中

List c = new ArrayList();    
// 下面程序引起編譯錯(cuò)誤
c.add(new Object());

因?yàn)槌绦驘o法確認(rèn)c集合里元素的類型,所以不能向其中添加對(duì)象。根據(jù)前面的List接口定義的代碼可以發(fā)現(xiàn):add ()方法由類型參數(shù)E作為集合的元素類型,所以傳給add的參數(shù)必須是E類的對(duì)象或者其子類的對(duì)象。但因?yàn)樵谠摾胁恢繣是什么類型,所以程序無法將任何對(duì)象“丟進(jìn)”該集合。唯一的例外是 null,它是所有引用類型的實(shí)例

另一方面,程序可以調(diào)用get()方法來返回List集合指定索引處的元素,其返回值是一個(gè)未知類型,但可以肯定的是,它總是一個(gè)Object。因此,把get()的返回值賦值給一個(gè)Object類型的變量,或者放在任何希望是Object類型的地方都可以

設(shè)定類型通配符的上限

List并不是List的子類型,所以不能把List對(duì)象當(dāng)成List使用。為了表示List的父類,使用List

List是受限通配符的例子,此處的問號(hào)(?)代表一個(gè)未知的類型,此處的未知類型一定是Shape的子類也可以是Shape,因此可以把shape稱為這個(gè)通配符的上限(upper bound)

設(shè)定類型形參的上限

Java泛型不僅允許在使用通配符形參時(shí)設(shè)定上限,而且可以在定義類型形參時(shí)設(shè)定上限,用于表示傳給該類型形參的實(shí)際類型要么是該上限類型,要門是該上限類型的子類

在一種更極端的情況下,程序需要為類型形參設(shè)定多個(gè)上限(至少有一個(gè)父類上限,可以有多個(gè)接口上限),表明該類型形參必須是其父類的子類(其父類本事也行),并且實(shí)現(xiàn)多個(gè)上限接口

//表明T類型必須是Number類或其子類,并必須實(shí)現(xiàn)java.io.Serializablepublic 
class Apple
{
    ...
}

與類同時(shí)繼承父類、實(shí)現(xiàn)接口類似的是:為類型形參指定多個(gè)上限,所有的接口上限必須位于類上限之后。也就是說,如果需要為類型形參指定類上限,類上限必須位于第一位

泛型方法 定義泛型方法

Java不允許把對(duì)象放進(jìn)一個(gè)未知類型的集合中

所謂泛型方法,就是在聲明方法時(shí)定義一個(gè)或多個(gè)類型形參

修飾符  返回值類型 方法名(形參列表)
{
    // 方法體...
}

把上面方法的格式和普通方法的格式進(jìn)行對(duì)比,不難發(fā)現(xiàn)泛型方法的方法簽名比普通方法的方法簽名多了類型形參聲明,類型形參聲明以尖括號(hào)括起來,多個(gè)類型形參之間以逗號(hào)隔開,所有的類型形參聲明放在方法修飾符和方法返回值類型之間

public class GenericMethodTest
{
    // 聲明一個(gè)泛型方法,該泛型方法中帶一個(gè)T類型形參,
    static  void fromArrayToCollection(T[] a, Collection c)
    {
        for (T o : a)
        {
            c.add(o);
        }
    }
    public static void main(String[] args)
    {
        Object[] oa = new Object[100];
        Collection co = new ArrayList<>();
        // 下面代碼中T代表Object類型
        fromArrayToCollection(oa, co);
        String[] sa = new String[100];
        Collection cs = new ArrayList<>();
        // 下面代碼中T代表String類型
        fromArrayToCollection(sa, cs);
        // 下面代碼中T代表Object類型
        fromArrayToCollection(sa, co);
        Integer[] ia = new Integer[100];
        Float[] fa = new Float[100];
        Number[] na = new Number[100];
        Collection cn = new ArrayList<>();
        // 下面代碼中T代表Number類型
        fromArrayToCollection(ia, cn);
        // 下面代碼中T代表Number類型
        fromArrayToCollection(fa, cn);
        // 下面代碼中T代表Number類型
        fromArrayToCollection(na, cn);
        // 下面代碼中T代表Object類型
        fromArrayToCollection(na, co);
        // 下面代碼中T代表String類型,但na是一個(gè)Number數(shù)組,
        // 因?yàn)镹umber既不是String類型,
        // 也不是它的子類,所以出現(xiàn)編譯錯(cuò)誤
        // fromArrayToCollection(na, cs);
    }
}

為了讓編譯器能準(zhǔn)確地推斷出泛型方法中類型形參的類型,不要制造迷惑!系統(tǒng)一旦迷惑了,就是你錯(cuò)了!看如下程序

public class ErrorTest
{
    // 聲明一個(gè)泛型方法,該泛型方法中帶一個(gè)T類型形參
    static  void test(Collection from, Collection to)
    {
        for (T ele : from)
        {
            to.add(ele);
        }
    }
    public static void main(String[] args)
    {
        List as = new ArrayList<>();
        List ao = new ArrayList<>();
        // 下面代碼將產(chǎn)生編譯錯(cuò)誤
        test(as , ao);
    }
}

上面程序中定義了test方法,該方法用于將前一個(gè)集合里的元素復(fù)制到下一個(gè)集合中,該方法中的兩個(gè)形參 from、to 的類型都是 Collection,這要求調(diào)用該方法時(shí)的兩個(gè)集合實(shí)參中的泛型類型相同,否則編譯器無法準(zhǔn)確地推斷出泛型方法中類型形參的類型

上面程序中調(diào)用test方法傳入了兩個(gè)實(shí)際參數(shù),其中as的數(shù)據(jù)類型是List,而ao的數(shù)據(jù)類型是List,與泛型方法簽名進(jìn)行對(duì)比:test(Collectiona, Collection c)編譯器無法正確識(shí)別T所代表的實(shí)際類型。為了避免這種錯(cuò)誤,可以將該方法改為如下形式:

public class RightTest
{
    // 聲明一個(gè)泛型方法,該泛型方法中帶一個(gè)T形參
    static  void test(Collection from , Collection to)
    {
        for (T ele : from)
        {
            to.add(ele);
        }
    }
    public static void main(String[] args)
    {
        List ao = new ArrayList<>();
        List as = new ArrayList<>();
        // 下面代碼完全正常
        test(as , ao);
    }
}

上面代碼改變了test方法簽名,將該方法的前一個(gè)形參類型改為 Collection,這種采用類型通配符的表示方式,只要test方法的前一個(gè)Collection集合里的元素類型是后一個(gè)Collection集合里元素類型的子類即可

泛型方法和類型通配符的區(qū)別

大多數(shù)時(shí)候都可以使用泛型方法來代替類型通配符。例如,對(duì)于 Java 的 Collection 接口中兩個(gè)方法定義:

public interface Collection
{
    boolean containAll(Collection c);
    boolean addAll(Collection c);
     ... 
}

上面集合中兩個(gè)方法的形參都采用了類型通配符的形式,也可以采用泛型方法的形式,如下所示

public interface Collection
{
    boolean  containAll(Collection c); 
    boolean  addAll(Collection c); 
    ...
}

上面方法使用了泛型形式,這時(shí)定義類型形參時(shí)設(shè)定上限(其中E是 Collection 接口里定義的類型形參,在該接口里E可當(dāng)成普通類型使用)。上面兩個(gè)方法中類型形參T只使用了一次,類型形參T產(chǎn)生的唯一效果是可以在不同的調(diào)用點(diǎn)傳入不同的實(shí)際類型。對(duì)于這種情況,應(yīng)該使用通配符:通配符就是被設(shè)計(jì)用來支持靈活的子類化的

泛型方法允許類型形參被用來表示方法的一個(gè)或多個(gè)參數(shù)之間的類型依賴關(guān)系,或者方法返回值與參數(shù)之間的類型依賴關(guān)系。如果沒有這樣的類型依賴關(guān)系,就不應(yīng)該使用泛型方法

如果某個(gè)方法中一個(gè)形參(a)的類型或返回值的類型依賴于另一個(gè)形參(b)的類型,則形參(b)的類型聲明不應(yīng)該使用通配符----因?yàn)樾螀ⅲ╝)或返回值的類型依賴于該形參(b)的類型,如果形參(b)的類型無法確定,程序就無法定義形參(a)的類型。在這種情況下,只能考慮使用在方法簽名中聲明類型形參 ——也就是泛型方法

也可以同時(shí)使用泛型方法和通配符,如Java的Collections.copy方法

public class Collections
{
    public static  void copy(List dest, List src)
    {
        ...
    }
    ...
}

上面copy方法中的dest和src存在明顯的依賴關(guān)系,從源List中復(fù)制出來的元素,必須可以“丟進(jìn)”目標(biāo)List中,所以源List集合元素的類型只能是目標(biāo)集合元素的類型的子類型或者它本身。但JDK定義src形參類型時(shí)使用的是類型通配符,而不是泛型方法。這是因?yàn)椋涸摲椒o須向src集合中添加元素,也無須修改src集合里的元素,所以可以使用類型通配符,不使用泛型方法

類型通配符與泛型方法(在方法簽名中顯式聲明類型形參)還有一個(gè)顯著的區(qū)別:類型通配符既可以在方法簽名中定義形參的類型,也可以用于定義變量的類型;但泛型方法中的類型形參必須在對(duì)應(yīng)方法中顯式聲明

Java7的“菱形”語法與泛型構(gòu)造器

Java允許在構(gòu)造器簽名中聲明類型形參,這樣就產(chǎn)生了所渭的泛型構(gòu)造器。一旦定義了泛型構(gòu)造器,接下來在調(diào)用構(gòu)造器時(shí),就不僅可以讓Java根據(jù)數(shù)據(jù)參數(shù)的類型來“推斷”類型形參的類型,而且程序員也可以顯式地為構(gòu)造器中的類型形參指定實(shí)際的類型

class Foo
{
    public  Foo(T t)
    {
        System.out.println(t);
    }
}
public class GenericConstructor
{
    public static void main(String[] args)
    {
        // 泛型構(gòu)造器中的T參數(shù)為String。
        new Foo("瘋狂Java講義");
        // 泛型構(gòu)造器中的T參數(shù)為Integer。
        new Foo(200);
        // 顯式指定泛型構(gòu)造器中的T參數(shù)為String,
        // 傳給Foo構(gòu)造器的實(shí)參也是String對(duì)象,完全正確。
        new  Foo("瘋狂Android講義");
        // 顯式指定泛型構(gòu)造器中的T參數(shù)為String,
        // 但傳給Foo構(gòu)造器的實(shí)參是Double對(duì)象,下面代碼出錯(cuò)
        new  Foo(12.3);
    }
}

前面介紹過 Java 7 新增的“菱形”語法,它允許調(diào)用構(gòu)造器時(shí)在構(gòu)造器后使用一對(duì)尖括號(hào)來代表泛型信息。但如果程序顯式指定了泛型構(gòu)造器中聲明的類型形參的實(shí)際類型,則不可以使用“菱形”語法

class MyClass
{
    public  MyClass(T t)
    {
        System.out.println("t參數(shù)的值為:" + t);
    }
}
public class GenericDiamondTest
{
    public static void main(String[] args)
    {
        // MyClass類聲明中的E形參是String類型。
        // 泛型構(gòu)造器中聲明的T形參是Integer類型
        MyClass mc1 = new MyClass<>(5);
        // 顯式指定泛型構(gòu)造器中聲明的T形參是Integer類型,
        MyClass mc2 = new  MyClass(5);
        // MyClass類聲明中的E形參是String類型。
        // 如果顯式指定泛型構(gòu)造器中聲明的T形參是Integer類型
        // 此時(shí)就不能使用"菱形"語法,下面代碼是錯(cuò)的。
        // MyClass mc3 = new  MyClass<>(5);
    }
}

上面程序中最后一行代碼既指定了泛型構(gòu)造器中的類型形參是 Integer 類型,又想使用“菱形”語法,所以這行代碼無法通過編譯

設(shè)定通配符下限

實(shí)現(xiàn)將src集合里的元素復(fù)制到dest集合里的功能,因?yàn)閐est集合可以保存src集合里的所有元素,所以dest集合元素的類型應(yīng)該是src集合元素類型的父類,假設(shè)該方法需要一個(gè)返回值,返回最后一個(gè)被復(fù)制的元素。為了表示兩個(gè)參數(shù)之間的類型依賴,考慮同時(shí)使用通配符、泛型參數(shù)來實(shí)現(xiàn)該方法

public static  T copy(Collection dest, Collection src)
{
    T last = null;
    for (T ele : src)
    {
        last = ele;
        dest.add(ele); 
    } 
    return last; 
}

實(shí)際上有一個(gè)問題:當(dāng)遍歷src集合的元素時(shí),src元素的類型是不確定的(但可以肯定它是T的子類),程序只能用T來籠統(tǒng)地表示各種src集合的元素類型

List ln = new ArrayList<>; 
List li = new ArrayList<>; 
// 下面代碼引起編譯錯(cuò)誤 
Integer last = copy(ln, li);

ln的類型是List,與copy方法簽名的形參類型進(jìn)行對(duì)比即得到T的實(shí)際類型是 Number,而不是Integer類型——即copy方法的返回值也是Number類型,而不是Integer類型,但實(shí)際上是后一個(gè)復(fù)制元素的元素類型一定是 Integer。也就是說,程序在復(fù)制集合元素的過程中,丟失src集合元素的類型

對(duì)于上面的copy方法,可以這樣理解兩個(gè)集合參數(shù)之間的依賴關(guān)系:不管src集合元素的類型是什么,只要dest集合元素的類型與前者相同或是前者的父類即可。Java允許設(shè)定通配符的下限:,這個(gè)通配符表示它必須是Type本身,或是Type的父類

public class MyUtils
{
    // 下面dest集合元素類型必須與src集合元素類型相同,或是其父類
    public static  T copy(Collection dest
        , Collection src)
    {
        T last = null;
        for (T ele  : src)
        {
            last = ele;
            dest.add(ele);
        }
        return last;
    }
    public static void main(String[] args)
    {
        List ln = new ArrayList<>();
        List li = new ArrayList<>();
        li.add(5);
        // 此處可準(zhǔn)確的知道最后一個(gè)被復(fù)制的元素是Integer類型
        // 與src集合元素的類型相同
        Integer last = copy(ln , li);    // ①
        System.out.println(ln);
    }
}

使用這種語句,就可以保證程序的①處調(diào)用后推斷出晟后一個(gè)被復(fù)制的元素類型是 Integer,而不是籠統(tǒng)的 Number 類型

泛型方法與方法重載

因?yàn)榉盒图仍试S設(shè)定通配符的上限,也允許設(shè)定通配符的下限,從而允許在一個(gè)類里包含如下兩個(gè)方法定義

public class MyUtils
{ 
    public static  void copy(Collection dest , Collection src) 
    {...}    // ①        
    public static  copy(Collection dest, Collection src) 
    {...}    // ②
}

MyUtils類中包含兩個(gè)copy方法,這兩個(gè)方法的參數(shù)列表存在一定的區(qū)別,但這種區(qū)別不是很明確:這兩個(gè)方法的兩個(gè)參數(shù)都是Collection對(duì)象,前一個(gè)集合里的集合元素類型是后一個(gè)集合里集合元素類型的父類。如果這個(gè)類僅包含這兩個(gè)方法不會(huì)有任何錯(cuò)誤,但只要調(diào)用這個(gè)方法就會(huì)引起編譯錯(cuò)誤

List ln = new ArrayList<>; 
List li = new ArrayList<>; 
copy(ln , li);

上面程序調(diào)用copy方法,但這個(gè)copy()方法既可以匹配①號(hào)copy方法,此時(shí)T類型參數(shù)的類型是 Number;也可以匹配②號(hào)copy()方法,此時(shí)T參數(shù)的類型是Integer。編譯器無法確定這行代碼想調(diào)用哪個(gè)copy()方法,所以這行代碼將引起編譯錯(cuò)誤

Java8改進(jìn)的類型推斷

Java8改進(jìn)了泛型方法的類型推斷能力,類型推斷主要有如下兩方面

可通過調(diào)用方法的上下文來推斷類型參數(shù)的目標(biāo)類型

可在方法調(diào)用鏈中,將推斷得到的類型參數(shù)傳遞到最后一個(gè)方法

class MyUtil
{
    public static  MyUtil nil()
    {
        return null;
    }
    public static  MyUtil cons(Z head, MyUtil tail)
    {
        return null;
    }
    E head()
    {
        return null;
    }
}
public class InferenceTest
{
    public static void main(String[] args)
    {
        // 可以通過方法賦值的目標(biāo)參數(shù)來推斷類型參數(shù)為String
        MyUtil ls = MyUtil.nil();
        // 無需使用下面語句在調(diào)用nil()方法時(shí)指定類型參數(shù)的類型
        MyUtil mu = MyUtil.nil();
        // 可調(diào)用cons方法所需的參數(shù)類型來推斷類型參數(shù)為Integer
        MyUtil.cons(42, MyUtil.nil());
        // 無需使用下面語句在調(diào)用nil()方法時(shí)指定類型參數(shù)的類型
        MyUtil.cons(42, MyUtil.nil());

        // 希望系統(tǒng)能推斷出調(diào)用nil()方法類型參數(shù)為String類型,
        // 但實(shí)際上Java 8依然推斷不出來,所以下面代碼報(bào)錯(cuò)
        // String s = MyUtil.nil().head();
        String s = MyUtil.nil().head();
    }
}

擦除和轉(zhuǎn)換

在嚴(yán)格的泛型代碼里,帶泛型聲明的類總應(yīng)該帶著泛型參數(shù)。但是為了和古老的java代碼保持一致,也就是說為了向下兼容,也允許在使用帶泛型聲明的類時(shí)不指定實(shí)際的類型參數(shù)。如果沒有為這個(gè)泛型指定實(shí)際的類型參數(shù),則該類型參數(shù)被稱作raw type(原始類型),默認(rèn)是聲明該類型參數(shù)時(shí)指定的第一個(gè)上限類型

當(dāng)把一個(gè)具體泛型信息的對(duì)象賦值給另外一個(gè)沒有泛型信息的變量時(shí),所有尖括號(hào)之間的類型信息都將被扔掉。比如說將一個(gè)List類型賦值給一個(gè)list,這個(gè)時(shí)候原來的這個(gè)List集合的類型就變成了類型參數(shù)的上限了,也就是Object

class Apple
{
    T size;
    public Apple()
    {
    }
    public Apple(T size)
    {
        this.size = size;
    }
    public void setSize(T size)
    {
        this.size = size;
    }
    public T getSize()
    {
        return this.size;
    }
}
public class ErasureTest
{
    public static void main(String[] args)
    {
        Apple a = new Apple<>(6);    // ①
        // a的getSize()方法返回Integer對(duì)象
        Integer as = a.getSize();
        // 把a(bǔ)對(duì)象賦給Apple變量,丟失尖括號(hào)里的類型信息
        Apple b = a;      // ②
        // b只知道size的類型是Number
        Number size1 = b.getSize();
        // 下面代碼引起編譯錯(cuò)誤
        Integer size2 = b.getSize();  // ③
    }
}

上面程序定義了一個(gè)帶泛型聲明的Apple類,其類型形參的上限是Number,這個(gè)類型形參用來定義Apple類的size變量。程序在①處創(chuàng)建了一個(gè)Apple對(duì)象,該Apple對(duì)象傳入了Integer作為類型形參的值,所以調(diào)用a的getSize()方法時(shí)返回Integer類型的值。當(dāng)把a(bǔ)賦給一個(gè)不帶泛型信息的b變量時(shí),編譯器就會(huì)丟失a對(duì)象的泛型信息,即所有尖括號(hào)里的信息都會(huì)丟失——因?yàn)锳pple的類型形參的上限是Number類,所以編譯器依然知道b的getSize()方法返回Number類型,但具體是Number的哪個(gè)子類就不清楚了

從邏輯上來看,List是List的子類,如果直接把一個(gè)List對(duì)象賦給一個(gè)List對(duì)象應(yīng)該引起編譯錯(cuò)誤,但實(shí)際上不會(huì)。對(duì)于泛型而言,可以直接把一個(gè)List對(duì)象賦予一個(gè)List對(duì)象,編譯器僅僅提示“未經(jīng)檢查的轉(zhuǎn)換”

public class ErasureTest2
{
    public static void main(String[] args)
    {
        List li = new ArrayList<>();
        li.add(34);
        li.add(59);
        List list = li;
        // 下面代碼引起“未經(jīng)檢查的轉(zhuǎn)換”的警告,編譯、運(yùn)行時(shí)完全正常
        List ls = list;     // ①
        // 但只要訪問ls里的元素,如下面代碼將引起運(yùn)行時(shí)異常
        // ClassCastException
        System.out.println(ls.get(0));
    }
}

上面程序中定義了一個(gè)List對(duì)象,這個(gè)List對(duì)象保留了集合元素的類型信息。當(dāng)把這個(gè)List對(duì)象賦給一個(gè)List類型的List后,編譯器就會(huì)丟失前者的泛型信息,即丟失List集合里元素的類型信息,這是典型的擦除。Java又允許直接把List對(duì)象賦給一個(gè)List(Type可以是任何類型)類型的變量,所以程序在①處可以編譯通過,只是發(fā)出“未經(jīng)檢查的轉(zhuǎn)換”警告,所以當(dāng)試圖把該集合里的元素當(dāng)成String類型的對(duì)象取出時(shí),將引發(fā)運(yùn)行時(shí)異常

下面代碼與上面代碼的行為完全相似

public class ErasureTest2
{
    public static void main(String[] args)
    {
        List li = new ArrayList<>();
        li.add(34);
        li.add(59); 
        System.out.println((String)li.get(0));   
    }
}

程序從li中獲取一個(gè)元素,并試圖通過強(qiáng)制類型轉(zhuǎn)換把它轉(zhuǎn)換成一個(gè)String,將引發(fā)運(yùn)行時(shí)異常。前面使用泛型代碼時(shí),系統(tǒng)與之存在完全相似的行為,所以引發(fā)相同的ClassCastException異常

泛型與數(shù)組

Java泛型與一個(gè)很重要的設(shè)計(jì)原則——如果一段代碼在編譯時(shí)沒有提出“[unchecked] 未經(jīng)檢查的轉(zhuǎn)換”警告,則程序在運(yùn)行時(shí)不會(huì)引發(fā)ClassCastException異常。正是基于這個(gè)原因,所以數(shù)組元素的類型不能包含類型變量或類型形參,除非是無上限的類型通配符。但可以聲明元素類型包含類型變量或類型形參的數(shù)組。也就是說,只能聲明List[]形式的數(shù)組,但不能創(chuàng)建ArrayList[10]這樣的數(shù)組對(duì)象。

Java不支持創(chuàng)建泛型數(shù)組

文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請(qǐng)注明本文地址:http://m.specialneedsforspecialkids.com/yun/66419.html

相關(guān)文章

  • java編程思想》—— 泛型

    摘要:引用泛型除了方法因不能使用外部實(shí)例參數(shù)外,其他繼承實(shí)現(xiàn)成員變量,成員方法,方法返回值等都可使用。因此,生成的字節(jié)碼僅包含普通的類,接口和方法。 為什么要使用泛型程序設(shè)計(jì)? 一般的類和方法,只能使用具體的類型:要么是基本類型,要么是自定義類的對(duì)應(yīng)類型;如果要編寫可以應(yīng)用于多種類型的代碼,這種刻板的限制對(duì)代碼的束縛就會(huì)很大。----摘自原書Ordinary classes and meth...

    CODING 評(píng)論0 收藏0
  • Java知識(shí)點(diǎn)總結(jié)(Java泛型

    摘要:知識(shí)點(diǎn)總結(jié)泛型知識(shí)點(diǎn)總結(jié)泛型泛型泛型就是參數(shù)化類型適用于多種數(shù)據(jù)類型執(zhí)行相同的代碼泛型中的類型在使用時(shí)指定泛型歸根到底就是模版優(yōu)點(diǎn)使用泛型時(shí),在實(shí)際使用之前類型就已經(jīng)確定了,不需要強(qiáng)制類型轉(zhuǎn)換。 Java知識(shí)點(diǎn)總結(jié)(Java泛型) @(Java知識(shí)點(diǎn)總結(jié))[Java, Java泛型] [toc] 泛型 泛型就是參數(shù)化類型 適用于多種數(shù)據(jù)類型執(zhí)行相同的代碼 泛型中的類型在使用時(shí)指定 泛...

    linkin 評(píng)論0 收藏0
  • 聊聊Java泛型及實(shí)現(xiàn)

    摘要:靜態(tài)變量是被泛型類的所有實(shí)例所共享的。所以引用能完成泛型類型的檢查。對(duì)于這個(gè)類型系統(tǒng),有如下的一些規(guī)則相同類型參數(shù)的泛型類的關(guān)系取決于泛型類自身的繼承體系結(jié)構(gòu)。事實(shí)上,泛型類擴(kuò)展都不合法。 前言 和C++以模板來實(shí)現(xiàn)靜多態(tài)不同,Java基于運(yùn)行時(shí)支持選擇了泛型,兩者的實(shí)現(xiàn)原理大相庭徑。C++可以支持基本類型作為模板參數(shù),Java卻只能接受類作為泛型參數(shù);Java可以在泛型類的方法中取得...

    lewif 評(píng)論0 收藏0
  • 淺析Java泛型

    摘要:泛型類在類的申明時(shí)指定參數(shù),即構(gòu)成了泛型類。換句話說,泛型類可以看成普通類的工廠。的作用就是指明泛型的具體類型,而類型的變量,可以用來創(chuàng)建泛型類的對(duì)象。只有聲明了的方法才是泛型方法,泛型類中的使用了泛型的成員方法并不是泛型方法。 什么是泛型? 泛型是JDK 1.5的一項(xiàng)新特性,它的本質(zhì)是參數(shù)化類型(Parameterized Type)的應(yīng)用,也就是說所操作的數(shù)據(jù)類型被指定為一個(gè)參數(shù),...

    godiscoder 評(píng)論0 收藏0
  • 初探Java類型擦除

    摘要:可以看到,如果我們給泛型類制定了上限,泛型擦除之后就會(huì)被替換成類型的上限。相應(yīng)的,泛型類中定義的方法的類型也是如此。參考語言類型擦除下界通配符和的區(qū)別 本篇博客主要介紹了Java類型擦除的定義,詳細(xì)的介紹了類型擦除在Java中所出現(xiàn)的場(chǎng)景。 1. 什么是類型擦除 為了讓你們快速的對(duì)類型擦除有一個(gè)印象,首先舉一個(gè)很簡(jiǎn)單也很經(jīng)典的例子。 // 指定泛型為String List list1 ...

    DevTalking 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

最新活動(dòng)
閱讀需要支付1元查看
<