最美情侣中文字幕电影,在线麻豆精品传媒,在线网站高清黄,久久黄色视频

歡迎光臨散文網(wǎng) 會(huì)員登陸 & 注冊(cè)

Java 泛型詳解

2020-12-15 11:28 作者:每天一個(gè)禿頂小技巧  | 我要投稿

概述:


在 Java5 以前,普通的類和方法只能使用特定的類型:基本數(shù)據(jù)類型或類類型,如果編寫的代碼需要應(yīng)用于多種類型,這種嚴(yán)苛的限制對(duì)代碼的束縛就會(huì)很大
Java5 的一個(gè)重大變化就是引入泛型,泛型實(shí)現(xiàn)了參數(shù)化類型,使得你編寫的組件(通常是集合)可以適用于多種類型。泛型的初衷是通過解耦類或方法與所使用的類型之間的約束,使得類或方法具備最寬泛的表達(dá)力。然而很快你就會(huì)發(fā)現(xiàn),Java 中的泛型并沒有你想的那么完美,甚至存在一些令人迷惑的實(shí)現(xiàn)


泛型類
促成泛型出現(xiàn)的最主要?jiǎng)訖C(jī)之一就是為了創(chuàng)建集合類,集合用于存放要使用到的對(duì)象?,F(xiàn)有一個(gè)只能持有單個(gè)對(duì)象的類:(代碼)

class Automobile {}


public class Holder1 {

private Automobile a;

public Holder1(Automobile a) { this.a = a; }

Automobile get() { return a; }

}


如果沒有泛型,那么就必須明確指定其持有的對(duì)象的類型,會(huì)導(dǎo)致該復(fù)用性不高,它無法持有其他類型的對(duì)象,我們當(dāng)然不希望為每個(gè)類型都編寫一個(gè)新類


在 Java5 以前,為了解決這個(gè)問題,我們可以讓這個(gè)類直接持有 Object 類型的對(duì)象,這樣就可以持有多種不同類型的對(duì)象了。但通常而言,我們只會(huì)用集合存儲(chǔ)同一類型的對(duì)象。泛型的主要目的之一就是用來約定集合要存儲(chǔ)什么類型的對(duì)象,并且通過編譯器確保規(guī)約得以滿足
所以,與其使用 Object,我們更希望先指定一個(gè)類型占位符,稍后再?zèng)Q定具體使用什么類型。由此我們需要使用類型參數(shù),用尖括號(hào)括住,放在類名后面。然后在使用這個(gè)類時(shí),再用實(shí)際的類型替換此類型參數(shù)(代碼)

public class GenericHolder<T> {

private T a;

public GenericHolder() {}

public void set(T a) { this.a = a; }

public T get() { return a; }


public static void main(String[] args) {

// 在 Java7 中右邊的尖括號(hào)可以為空

GenericHolder<Automobile> h2 = new GenericHolder<Automobile>();

GenericHolder<Automobile> h3 = new GenericHolder<>();

h3.set(new Automobile()); // 此處有類型校驗(yàn)

Automobile a = h3.get(); // 無需類型轉(zhuǎn)換

//- h3.set("Not an Automobile"); // 報(bào)錯(cuò)

}

}

元組類庫

有時(shí)一個(gè)方法需要能返回多個(gè)對(duì)象,而 return語句只能返回單個(gè)對(duì)象,解決的方法就是創(chuàng)建一個(gè)對(duì)象,用它來打包想要返回的多個(gè)對(duì)象。元組的概念正是基于此,元組將一組對(duì)象直接打包存儲(chǔ)于單一對(duì)象中,可以從該對(duì)象讀取其中元素,卻不允許向其中存儲(chǔ)新對(duì)象(這個(gè)概念也稱數(shù)據(jù)傳輸對(duì)象或信使)
元組可以具有任意長(zhǎng)度,元組中的對(duì)象可以是不同類型的,我們希望能為每個(gè)對(duì)象指明類型,這時(shí)泛型就派上用場(chǎng)了。例如下面是一個(gè)可以存儲(chǔ)兩個(gè)對(duì)象的元組:(代碼)

public class Tuple<A, B> {

public final A a1;

public final B a2;

public Tuple(A a, B b) { a1 = a; a2 = b; }

public String rep() { return a1 + ", " + a2; }


@Override

public String toString() {

return "(" + rep() + ")";

}

}

使用 final 修飾成員變量可以保證其不被修改,如果用戶想存儲(chǔ)不同的元素,那么就必須創(chuàng)建新的 Tuple 對(duì)象。當(dāng)然也可以允許用戶重新對(duì) a1、a2 賦值,但無疑前一種形式會(huì)更加安全

利用繼承機(jī)制可以實(shí)現(xiàn)長(zhǎng)度更長(zhǎng)的元組:(代碼)

public class Tuple3<A, B, C> extends Tuple2<A, B> {

public final C a3;

public Tuple3(A a, B b, C c) {

super(a, b);

a3 = c;

}


@Override

public String rep() {

return super.rep() + ", " + a3;

}

}

泛型方法

到目前為止,我們已經(jīng)研究了參數(shù)化整個(gè)類,其實(shí)還可以參數(shù)化類中的方法。類本身是否是泛型,與它的方法是否是泛型并沒有什么直接關(guān)系。我們應(yīng)該盡可能使用泛型方法,通常將單個(gè)方法泛型化要比將整個(gè)類泛型化要更加清晰易懂
要定義泛型方法,請(qǐng)將泛型參數(shù)列表放置在返回值之前:(代碼)

public class GenericMethods {

public <T> void f(T x) {

System.out.println(x.getClass().getName());

}


public static void main(String[] args) {

GenericMethods gm = new GenericMethods();

gm.f("");

gm.f(1);

gm.f(1.0);

gm.f(1.0F);

gm.f('c');

gm.f(gm);

}

}

使用泛型方法時(shí),通常不需要指定參數(shù)類型,因?yàn)榫幾g器會(huì)找出這些類型,這稱為類型參數(shù)推斷,因此,對(duì) f() 的調(diào)用看起來像普通的方法調(diào)用,而且像是被重載了無數(shù)次一樣。

泛型擦除

當(dāng)你開始深入研究泛型時(shí),你會(huì)發(fā)現(xiàn)一個(gè)殘酷的現(xiàn)實(shí):在泛型代碼內(nèi)部,無法獲取任何有關(guān)泛型參數(shù)類型的信息

class Frob {}

class Fnorkle {}

class Quark<Q> {}

class Particle<POSITION, MOMENTUM> {}


public class LostInformation {


public static void main(String[] args) {

List<Frob> list = new ArrayList<>();

Map<Frob, Fnorkle> map = new HashMap<>();

Quark<Fnorkle> quark = new Quark<>();

Particle<Long, Double> p = new Particle<>();

System.out.println(Arrays.toString(list.getClass().getTypeParameters()));

System.out.println(Arrays.toString(map.getClass().getTypeParameters()));

System.out.println(Arrays.toString(quark.getClass().getTypeParameters()));

System.out.println(Arrays.toString(p.getClass().getTypeParameters()));

}

}


/* Output:

[E]

[K,V]

[Q]

[POSITION,MOMENTUM]

*/

正如上例中輸出所示,你只能看到用作參數(shù)占位符的標(biāo)識(shí)符,這并非有用的信息。Java 泛型是使用擦除實(shí)現(xiàn)的,這意味著當(dāng)你在使用泛型時(shí),任何具體的類型信息都被擦除了,你唯一知道的就是你在使用一個(gè)對(duì)象。因此 List<String> 和 List 在運(yùn)行時(shí)實(shí)際上是相同的類型,它們都被擦除成原生類型 List
再來看一個(gè)例子:(代碼)

class Manipulator<T> {

private T obj;


Manipulator(T x) {

obj = x;

}


// Error: cannot find symbol: method f():

public void manipulate() {

obj.f();

}

}


public class Manipulation {

public static void main(String[] args) {

HasF hf = new HasF();

Manipulator<HasF> manipulator = new Manipulator<>(hf);

manipulator.manipulate();

}

}

因?yàn)椴脸?,Java 編譯器無法將 manipulate() 方法能調(diào)用 obj 的 f() 方法這一需求映射到 HasF 具有 f() 方法這個(gè)事實(shí)上。為了調(diào)用 f(),我們必須協(xié)助泛型類,為泛型類給定一個(gè)邊界,以此告訴編譯器只能接受遵循這個(gè)邊界的類型。這里重用了 extends 關(guān)鍵字。由于有了邊界,下面的代碼就能通過編譯:(代碼)
public class Manipulator2<T extends HasF> {

private T obj;


Manipulator2(T x) {

obj = x;

}


public void manipulate() {

obj.f();

}

}

邊界 <T extends HasF> 聲明 T 必須是 HasF 類型或其子類。如果情況確實(shí)如此,就可以安全地在 obj 上調(diào)用 f() 方法。泛型類型參數(shù)會(huì)擦除到它的第一個(gè)邊界(可能有多個(gè)邊界,稍后你將看到)。我們還提到了類型參數(shù)的擦除。編譯器實(shí)際上會(huì)把類型參數(shù)替換為它的擦除,就像上面的示例,T 擦除到了 HasF,就像在類的聲明中用 HasF 替換了 T 一樣。如果我們?cè)敢?,完全可以把上例?T 替換成 HashF,效果也是一樣的,那么泛型的意義又何在呢?
這提出了很重要的一點(diǎn):泛型只有在類型參數(shù)比某個(gè)具體類型(以及其子類)更加“泛化”,代碼能跨多個(gè)類工作時(shí)才有用。因此,使用類型參數(shù)通常比簡(jiǎn)單的聲明類更加復(fù)雜。但是,不能因此認(rèn)為使用 <T extends HasF> 形式就是有缺陷的。你必須查看所有的代碼,從而確定代碼是否復(fù)雜到必須使用泛型的程度
有關(guān)泛型擦除的困惑,其實(shí)是 Java 為實(shí)現(xiàn)泛型的一種妥協(xié),因?yàn)榉盒筒⒉皇?Java 語言出現(xiàn)時(shí)就有的。擦除減少了泛型的泛化性,泛型類型只有在靜態(tài)類型檢測(cè)期間才出現(xiàn),在此之后,程序中的所有泛型類型都將被擦除,替換為它們的非泛型上界。例如, List<T> 這樣的類型注解會(huì)被擦除為 List,普通的類型變量在未指定邊界的情況下會(huì)被擦除為 Object
在 Java5 以前編寫的類庫是沒有使用泛型的,而作者可能打算重新用泛型編寫,或者根本不打算這樣做。Java 設(shè)計(jì)者們既要保證舊代碼和類文件依然合法,還得考慮當(dāng)某個(gè)類庫變?yōu)榉盒蜁r(shí),不會(huì)破壞依賴于它的代碼和應(yīng)用。Java 設(shè)計(jì)者們最終認(rèn)為泛型是唯一可行的解決方案,擦除使得向泛型的遷移成為可能,為了實(shí)現(xiàn)非泛型的代碼和泛型代碼共存,必須將某個(gè)類庫使用了泛型這樣的“證據(jù)”擦除
基于上述觀點(diǎn),當(dāng)你在編寫泛型代碼時(shí),必須時(shí)刻提醒自己,你只是看起來擁有有關(guān)參數(shù)的類型信息而言。因?yàn)椴脸?,我們無法在運(yùn)行時(shí)知道確切的類型,為了補(bǔ)償擦除帶來的弊端,我們可以為所需的類型顯示傳遞一個(gè) Class 對(duì)象,以在類型表達(dá)式中使用它

class Building {

}


class House extends Building {

}


public class ClassTypeCapture<T> {

Class<T> kind;


public ClassTypeCapture(Class<T> kind) {

this.kind = kind;

}


public boolean f(Object arg) {

return kind.isInstance(arg);

}


public static void main(String[] args) {

ClassTypeCapture<Building> ctt1 =

new ClassTypeCapture<>(Building.class);

System.out.println(ctt1.f(new Building()));

System.out.println(ctt1.f(new House()));

ClassTypeCapture<House> ctt2 =

new ClassTypeCapture<>(House.class);

System.out.println(ctt2.f(new Building()));

System.out.println(ctt2.f(new House()));

}

}

邊界和通配符
由于擦除會(huì)刪除類型信息,因此唯一可用于無限制泛型參數(shù)的方法是那些 Object 可用的方法。邊界允許我們對(duì)泛型使用的參數(shù)類型施以類型,將參數(shù)限制為某類型的子集,那么就可以調(diào)用該子集中的方法。為了應(yīng)用約束,Java 泛型使用了 extends 關(guān)鍵字

class Coord {

public int x, y, z;

}


interface Weight {

int weight();

}


class Solid<T extends Coord & Weight> {

T item;


Solid(T item) {

this.item = item;

}


T getItem() {

return item;

}


int getX() {

return item.x;

}


int getY() {

return item.y;

}


int getZ() {

return item.z;

}


int weight() {

return item.weight();

}

}


class Bounded

extends Coord implements Weight {


@Override

public int weight() {

return 0;

}

}


public class BasicBounds {

public static void main(String[] args) {

Solid<Bounded> solid =

new Solid<>(new Bounded());

solid.getY();

solid.weight();

}

}

引入通配符可以在泛型實(shí)例化時(shí)更加靈活地控制,也可以在方法中控制方法的參數(shù),具體語法如下:

  • ? extends T:表示 T 或 T 的子類

  • ? super T:表示 T 或 T 的父類

?:表示可以是任意類型

值得注意的問題
在這里主要闡述在使用 Java 泛型時(shí)會(huì)出現(xiàn)的各類問題
1. 任何基本數(shù)據(jù)類型不能作為類型參數(shù)
Java 泛型的限制之一是不能將基本類型用作類型參數(shù)。因此,不能創(chuàng)建 ArrayList<int> 之類的東西。 解決方法是使用基本類型的包裝器類以及自動(dòng)裝箱機(jī)制。如果創(chuàng)建一個(gè) ArrayList<Integer>,并將基本類型 int 應(yīng)用于這個(gè)集合,那么你將發(fā)現(xiàn)自動(dòng)裝箱機(jī)制將自動(dòng)地實(shí)現(xiàn) int 到 Integer 的雙向轉(zhuǎn)換,這幾乎就像是有一個(gè) ArrayList<int> 一樣

2. 實(shí)現(xiàn)參數(shù)化接口
一個(gè)類不能實(shí)現(xiàn)同一個(gè)泛型接口的兩種變體,由于擦除的原因,這兩個(gè)變體會(huì)成為相同的接口。下面是產(chǎn)生這種沖突的情況:
interface Payable<T> {}


class Employee implements Payable<Employee> {}


class Hourly extends Employee implements Payable<Hourly> {}

Hourly 不能編譯,因?yàn)椴脸龝?huì)將 Payable<Employe> 和 Payable<Hourly> 簡(jiǎn)化為相同的類 Payable,這樣,上面的代碼就意味著在重復(fù)兩次地實(shí)現(xiàn)相同的接口。十分有趣的是,如果從 Payable 的兩種用法中都移除掉泛型參數(shù)(就像編譯器在擦除階段所做的那樣)這段代碼就可以編譯

3. 轉(zhuǎn)型和警告

使用帶有泛型類型參數(shù)的轉(zhuǎn)型不會(huì)有任何效果,例如:(代碼)
class Storage<T> {

private Object obj;


Storage() {

obj = new Object();

}


@SuppressWarnings("unchecked")

public T pop() {

return (T)obj;

}

}


public class GenericCast {


public static void main(String[] args) {

Storage<String> storage = new Storage<>();

System.out.println(storage.pop());

}

}

如果沒有 @SuppressWarnings 注解,編譯器將對(duì) pop() 產(chǎn)生 “unchecked cast” 警告。由于擦除的原因,編譯器無法知道這個(gè)轉(zhuǎn)型是否是安全的,并且 pop() 方法實(shí)際上并沒有執(zhí)行任何轉(zhuǎn)型。 這是因?yàn)?,T 被擦除到它的第一個(gè)邊界,默認(rèn)情況下是 Object,因此 pop() 實(shí)際上只是將 Object 轉(zhuǎn)型為 Object


4. 重載

下面的程序是不能編譯的,因?yàn)椴脸?,所以重載方法產(chǎn)生了相同的類型簽名

public class UseList<W, T> {

void f(List<T> v) {}

void f(List<W> v) {}

}


文章來源網(wǎng)絡(luò),侵刪

如果你有更好的想法和意見可以一起討論一下哦~~~

可以點(diǎn)擊視頻看更多知識(shí),評(píng)論還有更多資料免費(fèi)領(lǐng)取哦~

尚學(xué)堂最新2020版Java300集教程課程

Java 泛型詳解的評(píng)論 (共 條)

分享到微博請(qǐng)遵守國(guó)家法律
平阴县| 永善县| 三门县| 黄平县| 奉化市| 南木林县| 琼中| 芜湖市| 长白| 奉新县| 渝中区| 曲阳县| 潮安县| 宜黄县| 马尔康县| 济宁市| 浮山县| 霞浦县| 闻喜县| 富蕴县| 溧阳市| 左贡县| 叙永县| 赤水市| 巴东县| 浦江县| 安吉县| 寻乌县| 韶关市| 庆云县| 潜江市| 靖宇县| 宁安市| 无锡市| 涞水县| 遵义县| 阳东县| 怀集县| 遂溪县| 平罗县| 光山县|