摘要:首先,我們來(lái)按照泛型的標(biāo)準(zhǔn)重新設(shè)計(jì)一下類。注意參數(shù)為而不是泛型。利用形式的通配符,可以實(shí)現(xiàn)泛型的向上轉(zhuǎn)型,來(lái)看例子。需要注意的是,無(wú)法從這樣類型的中取出數(shù)據(jù)。
00、故事的起源
“二哥,要不我上大學(xué)的時(shí)候也學(xué)習(xí)編程吧?”有一天,三妹突發(fā)奇想地問(wèn)我。
“你確定要做一名程序媛嗎?”
“我覺(jué)得女生做程序員,有著天大的優(yōu)勢(shì),尤其是我這種長(zhǎng)相甜美的。”三妹開始認(rèn)真了起來(lái)。
“好像是啊,遇到女生提問(wèn),我好像一直蠻熱情的。”
“二哥,你不是愛(ài)好寫作嘛,還是一個(gè) Java 程序員,不妨寫個(gè)專欄,名字就叫《教妹學(xué) Java》。我高考完就開始跟著你學(xué)習(xí)編程,還能省下一筆培訓(xùn)費(fèi)。”三妹看起來(lái)已經(jīng)替我籌劃好了呀。
“真的很服氣你們零零后,蠻有想法的。剛好我最近在寫 Java 系列的專欄,不妨試一試!”
PS:親愛(ài)的讀者朋友們,我們今天就從晦澀難懂的“泛型”開始吧!(子標(biāo)題是三妹提出來(lái)的,內(nèi)容由二哥我來(lái)回答)
01、二哥,為什么要設(shè)計(jì)泛型啊?三妹啊,聽哥慢慢給你講啊。
Java 在 5.0 時(shí)增加了泛型機(jī)制,據(jù)說(shuō)專家們?yōu)榇嘶ㄙM(fèi)了 5 年左右的時(shí)間(聽起來(lái)很不容易)。有了泛型之后,尤其是對(duì)集合類的使用,就變得更規(guī)范了。
看下面這段簡(jiǎn)單的代碼。
ArrayList list = new ArrayList();
list.add("沉默王二");
String str = list.get(0);
但在沒(méi)有泛型之前該怎么辦呢?
首先,我們需要使用 Object 數(shù)組來(lái)設(shè)計(jì) Arraylist 類。
class Arraylist {
private Object[] objs;
private int i = 0;
public void add(Object obj) {
objs[i++] = obj;
}
public Object get(int i) {
return objs[i];
}
}
然后,我們向 Arraylist 中存取數(shù)據(jù)。
Arraylist list = new Arraylist();
list.add("沉默王二");
list.add(new Date());
String str = (String)list.get(0);
你有沒(méi)有發(fā)現(xiàn)兩個(gè)問(wèn)題:
Arraylist 可以存放任何類型的數(shù)據(jù)(既可以存字符串,也可以混入日期),因?yàn)樗蓄惗祭^承自 Object 類。
從 Arraylist 取出數(shù)據(jù)的時(shí)候需要強(qiáng)制類型轉(zhuǎn)換,因?yàn)榫幾g器并不能確定你取的是字符串還是日期。
對(duì)比一下,你就能明顯地感受到泛型的優(yōu)秀之處:使用類型參數(shù)解決了元素的不確定性——參數(shù)類型為 String 的集合中是不允許存放其他類型元素的,取出數(shù)據(jù)的時(shí)候也不需要強(qiáng)制類型轉(zhuǎn)換了。
02、二哥,怎么設(shè)計(jì)泛型啊?三妹啊,你一個(gè)小白只要會(huì)用泛型就行了,還想設(shè)計(jì)泛型啊?!不過(guò),既然你想了解,那么哥義不容辭。
首先,我們來(lái)按照泛型的標(biāo)準(zhǔn)重新設(shè)計(jì)一下 Arraylist 類。
class Arraylist<E> {
private Object[] elementData;
private int size = 0;
public Arraylist(int initialCapacity) {
this.elementData = new Object[initialCapacity];
}
public boolean add(E e) {
elementData[size++] = e;
return true;
}
E elementData(int index) {
return (E) elementData[index];
}
}
一個(gè)泛型類就是具有一個(gè)或多個(gè)類型變量的類。Arraylist 類引入的類型變量為 E(Element,元素的首字母),使用尖括號(hào) <> 括起來(lái),放在類名的后面。
然后,我們可以用具體的類型(比如字符串)替換類型變量來(lái)實(shí)例化泛型類。
Arraylist list = new Arraylist();
list.add("沉默王三");
String str = list.get(0);
Date 類型也可以的。
Arraylist list = new Arraylist();
list.add(new Date());
Date date = list.get(0);
其次,我們還可以在一個(gè)非泛型的類(或者泛型類)中定義泛型方法。
class Arraylist<E> {
public T[] toArray(T[] a) {
return (T[]) Arrays.copyOf(elementData, size, a.getClass());
}
}
不過(guò),說(shuō)實(shí)話,泛型方法的定義看起來(lái)略顯晦澀。來(lái)一副圖吧(注意:方法返回類型和方法參數(shù)類型至少需要一個(gè))。
現(xiàn)在,我們來(lái)調(diào)用一下泛型方法。
Arraylist list = new Arraylist<>(4);
list.add("沉");
list.add("默");
list.add("王");
list.add("二");
String [] strs = new String [4];
strs = list.toArray(strs);
for (String str : strs) {
System.out.println(str);
}
最后,我們?cè)賮?lái)說(shuō)說(shuō)泛型變量的限定符 extends。在解釋這個(gè)限定符之前,我們假設(shè)有三個(gè)類,它們之間的定義是這樣的。
class Wanglaoer {
public String toString() {
return "王老二";
}
}
class Wanger extends Wanglaoer{
public String toString() {
return "王二";
}
}
class Wangxiaoer extends Wanger{
public String toString() {
return "王小二";
}
}
我們使用限定符 extends 來(lái)重新設(shè)計(jì)一下 Arraylist 類。
class Arraylist<E extends Wanger> {
}
當(dāng)我們向 Arraylist 中添加 Wanglaoer 元素的時(shí)候,編譯器會(huì)提示錯(cuò)誤:Arraylist 只允許添加 Wanger 及其子類 Wangxiaoer 對(duì)象,不允許添加其父類 Wanglaoer。
Arraylist list = new Arraylist<>(3);
list.add(new Wanger());
list.add(new Wanglaoer());
// The method add(Wanger) in the type Arraylist is not applicable for the arguments
// (Wanglaoer)
list.add(new Wangxiaoer());
也就是說(shuō),限定符 extends 可以縮小泛型的類型范圍。
03、二哥,聽說(shuō)虛擬機(jī)沒(méi)有泛型?三妹,你功課做得可以啊,連虛擬機(jī)都知道了啊。哥可以肯定地回答你,虛擬機(jī)是沒(méi)有泛型的。
啰嗦一句哈。我們編寫的 Java 代碼(也就是源碼,后綴為 .java 的文件)是不能夠被操作系統(tǒng)直接識(shí)別的,需要先編譯,生成 .class 文件(也就是字節(jié)碼文件)。然后 Java 虛擬機(jī)(JVM)會(huì)充當(dāng)一個(gè)翻譯官的角色,把字節(jié)碼翻譯給操作系統(tǒng)能聽得懂的語(yǔ)言,告訴它該干嘛。
怎么確定虛擬機(jī)沒(méi)有泛型呢?我們需要把泛型類的字節(jié)碼進(jìn)行反編譯——強(qiáng)烈推薦超神反編譯工具 Jad !
現(xiàn)在,在命令行中敲以下代碼吧(反編譯 Arraylist 的字節(jié)碼文件 Arraylist.class)。
jad Arraylist.class
命令執(zhí)行完后,會(huì)生成一個(gè) Arraylist.jad 的文件,用文本編輯工具打開后的結(jié)果如下。
// Decompiled by Jad v1.5.8g. Copyright 2001 Pavel Kouznetsov.
// Jad home page: http://www.kpdus.com/jad.html
// Decompiler options: packimports(3)
// Source File Name: Arraylist.java
package com.cmower.java_demo.fanxing;
import java.util.Arrays;
class Arraylist
{
public Arraylist(int initialCapacity)
{
size = 0;
elementData = new Object[initialCapacity];
}
public boolean add(Object e)
{
elementData[size++] = e;
return true;
}
Object elementData(int index)
{
return elementData[index];
}
private Object elementData[];
private int size;
}
類型變量
既然如此,那如果泛型類使用了限定符 extends,結(jié)果會(huì)怎么樣呢?我們先來(lái)看看 Arraylist2 的源碼。
class Arraylist2<E extends Wanger> {
private Object[] elementData;
private int size = 0;
public Arraylist2(int initialCapacity) {
this.elementData = new Object[initialCapacity];
}
public boolean add(E e) {
elementData[size++] = e;
return true;
}
E elementData(int index) {
return (E) elementData[index];
}
}
字節(jié)碼文件 Arraylist2.class 使用 Jad 反編譯后的結(jié)果如下。
// Decompiled by Jad v1.5.8g. Copyright 2001 Pavel Kouznetsov.
// Jad home page: http://www.kpdus.com/jad.html
// Decompiler options: packimports(3)
// Source File Name: Arraylist2.java
package com.cmower.java_demo.fanxing;
// Referenced classes of package com.cmower.java_demo.fanxing:
// Wanger
class Arraylist2
{
public Arraylist2(int initialCapacity)
{
size = 0;
elementData = new Object[initialCapacity];
}
public boolean add(Wanger e)
{
elementData[size++] = e;
return true;
}
Wanger elementData(int index)
{
return (Wanger)elementData[index];
}
private Object elementData[];
private int size;
}
類型變量
通過(guò)以上兩個(gè)例子說(shuō)明,Java 虛擬機(jī)會(huì)將泛型的類型變量擦除,并替換為限定類型(沒(méi)有限定的話,就用 Object)。
04、二哥,類型擦除會(huì)有什么問(wèn)題嗎?三妹啊,你還別說(shuō),類型擦除真的會(huì)有一些“問(wèn)題”。
我們來(lái)看一下這段代碼。
public class Cmower {
public static void method(Arraylist list) {
System.out.println("Arraylist list" );
}
public static void method(Arraylist list) {
System.out.println("Arraylist list" );
}
}
在淺層的意識(shí)上,我們會(huì)想當(dāng)然地認(rèn)為 Arraylist
但由于類型擦除的原因,以上代碼是不會(huì)通過(guò)編譯的——編譯器會(huì)提示一個(gè)錯(cuò)誤(這正是類型擦除引發(fā)的那些“問(wèn)題”):
Erasure of method method(Arraylist) is the same as another method in type Cmower
Erasure of method method(Arraylist) is the same as another method in type Cmower
大致的意思就是,這兩個(gè)方法的參數(shù)類型在擦除后是相同的。
也就是說(shuō),method(Arraylist
有句俗話叫做:“百聞不如一見”,但即使見到了也未必為真——泛型的擦除問(wèn)題就可以很好地佐證這個(gè)觀點(diǎn)。
05、二哥,聽說(shuō)泛型還有通配符?三妹啊,哥突然覺(jué)得你很適合作一枚可愛(ài)的程序媛啊!你這預(yù)習(xí)的功課做得可真到家啊,連通配符都知道!
通配符使用英文的問(wèn)號(hào)(");extends 限定子類,也可以使用關(guān)鍵字 super 限定父類。
為了更好地解釋通配符,我們需要對(duì) Arraylist 進(jìn)行一些改進(jìn)。
class Arraylist<E> {
private Object[] elementData;
private int size = 0;
public Arraylist(int initialCapacity) {
this.elementData = new Object[initialCapacity];
}
public boolean add(E e) {
elementData[size++] = e;
return true;
}
public E get(int index) {
return (E) elementData[index];
}
public int indexOf(Object o) {
if (o == null) {
for (int i = 0; i < size; i++)
if (elementData[i]==null)
return i;
} else {
for (int i = 0; i < size; i++)
if (o.equals(elementData[i]))
return i;
}
return -1;
}
public boolean contains(Object o) {
return indexOf(o) >= 0;
}
public String toString() {
StringBuilder sb = new StringBuilder();
for (Object o : elementData) {
if (o != null) {
E e = (E)o;
sb.append(e.toString());
sb.append(",").append(" ");
}
}
return sb.toString();
}
public int size() {
return size;
}
public E set(int index, E element) {
E oldValue = (E) elementData[index];
elementData[index] = element;
return oldValue;
}
}
1)新增 indexOf(Object o) 方法,判斷元素在 Arraylist 中的位置。注意參數(shù)為 Object 而不是泛型 E。
2)新增 contains(Object o) 方法,判斷元素是否在 Arraylist 中。注意參數(shù)為 Object 而不是泛型 E。
3)新增 toString() 方法,方便對(duì) Arraylist 進(jìn)行打印。
4)新增 set(int index, E element) 方法,方便對(duì) Arraylist 元素的更改。
你知道,Arraylist
利用 <"); 形式的通配符,可以實(shí)現(xiàn)泛型的向上轉(zhuǎn)型,來(lái)看例子。
Arraylist<");new Arraylist<>(4);
list2.add(null);
// list2.add(new Wanger());
// list2.add(new Wangxiaoer());
Wanger w2 = list2.get(0);
// Wangxiaoer w3 = list2.get(1);
list2 的類型是 Arraylist<");,翻譯一下就是,list2 是一個(gè) Arraylist,其類型是 Wanger 及其子類。
注意,“關(guān)鍵”來(lái)了!list2 并不允許通過(guò) add(E e) 方法向其添加 Wanger 或者 Wangxiaoer 的對(duì)象,唯一例外的是 null。為什么不能存呢?原因還有待探究(苦澀)。
那就奇了怪了,既然不讓存放元素,那要 Arraylist<"); 這樣的 list2 有什么用呢?
雖然不能通過(guò) add(E e) 方法往 list2 中添加元素,但可以給它賦值。
Arraylist list = new Arraylist<>(4);
Wanger wanger = new Wanger();
list.add(wanger);
Wangxiaoer wangxiaoer = new Wangxiaoer();
list.add(wangxiaoer);
Arraylist<");1);
System.out.println(w2);
System.out.println(list2.indexOf(wanger));
System.out.println(list2.contains(new Wangxiaoer()));
Arraylist<"); 語(yǔ)句把 list 的值賦予了 list2,此時(shí) list2 == list。由于 list2 不允許往其添加其他元素,所以此時(shí)它是安全的——我們可以從容地對(duì) list2 進(jìn)行 get()、indexOf() 和 contains()。想一想,如果可以向 list2 添加元素的話,這 3 個(gè)方法反而變得不太安全,它們的值可能就會(huì)變。
利用 <"); 形式的通配符,可以向 Arraylist 中存入父類是 Wanger 的元素,來(lái)看例子。
Arraylist<");super Wanger> list3 = new Arraylist<>(4);
list3.add(new Wanger());
list3.add(new Wangxiaoer());
// Wanger w3 = list3.get(0);
需要注意的是,無(wú)法從 Arraylist<"); 這樣類型的 list3 中取出數(shù)據(jù)。為什么不能取呢?原因還有待探究(再次苦澀)。
雖然原因有待探究,但結(jié)論是明確的:<"); 可以取數(shù)據(jù),<"); 可以存數(shù)據(jù)。那么利用這一點(diǎn),我們就可以實(shí)現(xiàn)數(shù)組的拷貝——<"); 作為源(保證源不會(huì)發(fā)生變化),<"); 作為目標(biāo)(可以保存值)。
public class Collections {
public static void copy(Arraylist<");super T> dest, Arraylist<"); {
for (int i = 0; i < src.size(); i++)
dest.set(i, src.get(i));
}
}
06、故事的未完待續(xù)
“二哥,你今天苦澀了啊!嘿嘿。竟然還有你需要探究的。”三妹開始調(diào)皮了起來(lái)。
“......”
“不要不好意思嘛,等三妹啥時(shí)候探究出來(lái)了原因,三妹給你講,好不好?”三妹越說(shuō)越來(lái)勁了。
“......”
“二哥,你還在想泛型通配符的原因啊!那三妹先去預(yù)習(xí)下個(gè)知識(shí)點(diǎn)了啊,你思考完了,再給我講!”三妹看著我陷入了沉思,扔下這句話走了。
“......”
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://specialneedsforspecialkids.com/yun/7392.html
摘要:是一個(gè)可運(yùn)行于和瀏覽器的靜態(tài)語(yǔ)言。近日發(fā)布了正式版本。語(yǔ)言包及工具此次發(fā)布的版本中包括完整的語(yǔ)言規(guī)范。命令行工具集,包括針對(duì)和的編譯器文檔編譯器。功能強(qiáng)大的模塊架構(gòu),可用于組織代碼,管理依賴,在運(yùn)行時(shí)隔離模塊。 Ceylon是一個(gè)可運(yùn)行于JVM、nodejs和瀏覽器的靜態(tài)OO語(yǔ)言。 showImg(http://segmentfault.com/img/bVbA0f); 近日Cey...
摘要:靜態(tài)變量是被泛型類的所有實(shí)例所共享的。所以引用能完成泛型類型的檢查。對(duì)于這個(gè)類型系統(tǒng),有如下的一些規(guī)則相同類型參數(shù)的泛型類的關(guān)系取決于泛型類自身的繼承體系結(jié)構(gòu)。事實(shí)上,泛型類擴(kuò)展都不合法。 前言 和C++以模板來(lái)實(shí)現(xiàn)靜多態(tài)不同,Java基于運(yùn)行時(shí)支持選擇了泛型,兩者的實(shí)現(xiàn)原理大相庭徑。C++可以支持基本類型作為模板參數(shù),Java卻只能接受類作為泛型參數(shù);Java可以在泛型類的方法中取得...
摘要:可以看到,如果我們給泛型類制定了上限,泛型擦除之后就會(huì)被替換成類型的上限。相應(yīng)的,泛型類中定義的方法的類型也是如此。參考語(yǔ)言類型擦除下界通配符和的區(qū)別 本篇博客主要介紹了Java類型擦除的定義,詳細(xì)的介紹了類型擦除在Java中所出現(xiàn)的場(chǎng)景。 1. 什么是類型擦除 為了讓你們快速的對(duì)類型擦除有一個(gè)印象,首先舉一個(gè)很簡(jiǎn)單也很經(jīng)典的例子。 // 指定泛型為String List list1 ...
摘要:參數(shù)化的類型其中是參數(shù)化的類型。類型參數(shù)的實(shí)例或?qū)嶋H類型參數(shù)其中是類型參數(shù)的實(shí)例或?qū)嶋H類型參數(shù)。它們并沒(méi)有重載,而且泛型中也不存在重載這一說(shuō)法。除此之外,我們應(yīng)該盡量地多用泛型方法,而減少對(duì)整個(gè)類的泛化,因?yàn)榉盒头椒ǜ菀装咽虑檎f(shuō)明白。 泛型是適用于許多許多的類型 ---《JAVA編程思想》 在Java的面向?qū)ο缶幊踢^(guò)程中, 或許你知道運(yùn)用繼承、接口等一系列面向?qū)ο蟮膭?dòng)作來(lái)實(shí)現(xiàn)代碼復(fù)用...
閱讀 1004·2021-11-25 09:43
閱讀 1672·2019-08-30 13:59
閱讀 1589·2019-08-30 11:22
閱讀 2123·2019-08-30 11:06
閱讀 1299·2019-08-28 17:51
閱讀 3717·2019-08-26 12:12
閱讀 778·2019-08-26 12:11
閱讀 443·2019-08-26 12:10