您好,登錄后才能下訂單哦!
首先可以肯定的是,數(shù)組是一個(gè)對(duì)象;但是在推導(dǎo)的過程中還是有些難以理解的問題,比如對(duì)于任意一個(gè)引用對(duì)象A
,
數(shù)組是協(xié)變的,所以Object[]
是A[]
的父類,即Object[] o = A[]
;
數(shù)組是一個(gè)對(duì)象,所以數(shù)組的父類是Object
,即Object oo = o
;
那么A[]
、Object[]
和Object
是什么關(guān)系呢?
是這樣嗎?
我們可以通過反射來觀察一下:
private?static?void?test05()?{ ??Object[]?o?=?new?String[2]; ??System.out.println(o.getClass().getName()); ??System.out.println(o.getClass().getSuperclass().getName()); ??String[]?s?=?(String[])?o; ??System.out.println(s.getClass().getSuperclass().getName()); ??Object?oo?=?s;? } 打?。?[Ljava.lang.String; java.lang.Object java.lang.Object
可以看見A[]
和Object[]
的直接父類都是Object
,所以他們之間的關(guān)系也一定不是上圖中的多繼承關(guān)系,那么數(shù)組協(xié)變產(chǎn)生的關(guān)系一定不同于extends
關(guān)鍵字產(chǎn)生的關(guān)系;
extends
關(guān)鍵字產(chǎn)生的繼承關(guān)系是怎么定義呢?
這里我們可以從《Virtual Machine Specifications》中找到答案:
//?ClassFile?結(jié)構(gòu)ClassFile?{ ??u4????????????????magic; ??u2????????????????minor_version; ??u2????????????????major_version; ??u2????????????????constant_pool_count; ??cp_info???????????constant_pool[constant_pool_count-1]; ??u2????????????????access_flags; ??u2????????????????this_class; ??u2????????????????super_class; ??u2????????????????interfaces_count; ??u2????????????????interfaces[interfaces_count]; ??u2????????????????fields_count; ??field_info????????fields[fields_count]; ??u2????????????????methods_count; ??method_info???????methods[methods_count]; ??u2????????????????attribute_count; ??attributes_info;??attributes[attributes_count];? }
可以看到extends
關(guān)鍵字產(chǎn)生的繼承關(guān)系是記錄在class文件中的super_class
里面的。我這里還沒有在 JDK 源碼里面找到數(shù)組協(xié)變關(guān)系的產(chǎn)生,但是可以猜想這個(gè)應(yīng)該是后來加的類似語法關(guān)系的結(jié)構(gòu)。這里先留著以后看源碼的時(shí)候確認(rèn)吧。
在準(zhǔn)備看Array源碼的時(shí)候,我直接就點(diǎn)開了java.lang.reflect.Array
,后來才知道這根本不是Array的源碼,看包名就知道,這是使用反射操作數(shù)組的一些方法。Array
的class是在運(yùn)行過程中動(dòng)態(tài)生成的。
那么在Array
的class中到底包含了什么呢?在很多的資料中都寫了,Array
中有類似public final int length
的成員變量。但是在《Java Language Specifications》10.1. Array Types
中明確寫了,length不是類型的一部分;
An array's length is not part of its type.
private?static?void?test06()?{ ??String[]?s?=?new?String[2]; ??System.out.println(s.length); ??System.out.println(s.getClass().getDeclaredFields().length);??try?{ ??System.out.println(s.getClass().getDeclaredField("length")); ??}?catch?(NoSuchFieldException?e)?{ ??System.out.println(e.toString()); ??}? } 打印:20java.lang.NoSuchFieldException:?length
可以看到length
并不是Array
的成員變量,那么length
是從哪里來的呢?
同樣我們可以從ClassFile
結(jié)構(gòu)中找打答案;
可以看到Array
的length
信息是記錄在對(duì)象頭中的,而讀取length
信息的時(shí)候,是使用的arraylength
字節(jié)碼指令來讀取的。
//?數(shù)組創(chuàng)建的幾種形式String[]?s?=?{"a",?"b",?"c"};??//?初始化器String[]?s1?=?new?String[3];???//?有維度表達(dá)式String[]?s2?=?(String[])?Array.newInstance(String.class,?3);?//?有維度表達(dá)式
數(shù)組創(chuàng)建流程
是否有維度表達(dá)式: ??無: ????創(chuàng)建的時(shí)候每個(gè)元素遞歸深入初始化,失敗則退出 ????變量類型檢查?->?與數(shù)組類型不兼容?->?編譯錯(cuò)誤 ????不是可具化類型(如:null)?->?編譯錯(cuò)誤? ????空間不足?->?OutOfMemoryError ??有: ????創(chuàng)建的時(shí)候,從左向右地計(jì)算,任意維度表達(dá)式計(jì)算失敗則退出 ????檢查所有維度值,有小于0?->?NegativeArraySizeException? ????分配空間,若空間不足?->?OutOfMemoryError ????只有一個(gè)維度表達(dá)式,創(chuàng)建一維數(shù)組,每個(gè)元素初始化化為初始值 ????有n個(gè)維度表達(dá)式,執(zhí)行深度為n-1的循環(huán)
逆變與協(xié)變用來描述類型轉(zhuǎn)換(type transformation)后的繼承關(guān)系
如果A、B表示類型,f(?)表示類型轉(zhuǎn)換,≤表示繼承關(guān)系(比如,A≤B表示A是由B派生出來的子類)
f(?)是逆變的,當(dāng)A≤B時(shí)有f(B)≤f(A)成立;
f(?)是協(xié)變的,當(dāng)A≤B時(shí)有f(A)≤f(B)成立;
f(?)是不變的,當(dāng)A≤B時(shí)上述兩個(gè)式子均不成立,即f(A)與f(B)相互之間沒有繼承關(guān)系。
正因?yàn)閿?shù)組是協(xié)變的,所以Object[] o = new A[];
有種看法認(rèn)為這是在泛型產(chǎn)生之前的妥協(xié)產(chǎn)物,比如在 JDK5 之前還沒有泛型,但是很多地方需要用泛型來解決,比如:
//?java.util.Arrayspublic?static?boolean?equals(Object[]?a,?Object[]?a2)?{??if?(a==a2)????return?true;???? ??if?(a==null?||?a2==null)????return?false;???? ??int?length?=?a.length;??if?(a2.length?!=?length)????return?false;???? ??for?(int?i=0;?i<length;?i++)?{ ????Object?o1?=?a[i]; ????Object?o2?=?a2[i];????if?(!(o1==null???o2==null?:?o1.equals(o2)))??????return?false; ??}??return?true;? }
最后調(diào)用的是Object.equals()
方法,但是不想全部都重寫equals
,這里最簡單的就是讓數(shù)組實(shí)現(xiàn)協(xié)變的特性;
這里簡單的講是因?yàn)榉盒褪遣蛔兊?,而?shù)組是協(xié)變的,所以不能使用泛型數(shù)組;
//?如果泛型也是協(xié)變的private?static?void?test07()?{ ??List<Object>?list?=?new?ArrayList<String>();??//?原本會(huì)編譯出錯(cuò) ??list.add(123); ??List<String>?list1?=?list; ??String?s?=?list1.get(0);????//?類型錯(cuò)誤}
可以看到如果泛型也是協(xié)變的,那么Collection?在存取數(shù)據(jù)的時(shí)候,就會(huì)產(chǎn)生類型轉(zhuǎn)換錯(cuò)誤;
private?static?void?test07()?{ ??Object[]?o?=?new?String[2]; ??o[0]?=?123; } 運(yùn)行時(shí): Exception?in?thread?"main"?java.lang.ArrayStoreException:?java.lang.Integer
可以看到數(shù)組,在存數(shù)據(jù)的時(shí)候,還會(huì)檢查數(shù)據(jù)類型是否兼容,所以數(shù)組可以是協(xié)變的。
C++ 中的數(shù)組只是一個(gè)指針,java 中的數(shù)組是一個(gè)對(duì)象
java 中訪問數(shù)組會(huì)有額外的范圍檢查
java 中會(huì)確保數(shù)組被初始化
private?static?final?int?SIZE?=?50000;private?static?final?Random?RANDOM?=?new?Random();private?static?void?test_array()?{ ??System.out.println("Array:");??long?start?=?System.currentTimeMillis(); ??String[]?s?=?new?String[SIZE];??for?(int?i?=?0;?i?<?SIZE;?i++)?{ ????s[i]?=?i?+?""; ??} ?? ??System.out.println("insert:"?+?(System.currentTimeMillis()?-?start));?? ??start?=?System.currentTimeMillis();??for?(int?i?=?0,?len?=?SIZE?*?10;?i?<?len;?i++)?{ ????String?ss?=?s[RANDOM.nextInt(SIZE)]; ??} ?? ??System.out.println("get:"?+?(System.currentTimeMillis()?-?start)); }?? private?static?void?test_list()?{ ??System.out.println("ArrayList:");??long?start?=?System.currentTimeMillis(); ??List<String>?list?=?new?ArrayList<>(SIZE);??for?(int?i?=?0;?i?<?SIZE;?i++)?{ ????list.add(i?+?""); ??} ?? ??System.out.println("insert:"?+?(System.currentTimeMillis()?-?start)); ??start?=?System.currentTimeMillis();??for?(int?i?=?0,?len?=?SIZE?*?10;?i?<?len;?i++)?{ ????String?s?=?list.get(RANDOM.nextInt(SIZE)); ??} ??System.out.println("get:"?+?(System.currentTimeMillis()?-?start)); } 打?。?Array: insert:13get:10ArrayList: insert:7get:22
對(duì)比可以看到,數(shù)組的插入和隨機(jī)訪問效率都要比ArrayList高,但是一般建議優(yōu)先使用列表,只有在優(yōu)先考慮效率的時(shí)候才考慮使用數(shù)組,因?yàn)?/p>
數(shù)組是協(xié)變的不能使用泛型
數(shù)組是具體化的,只有在運(yùn)行時(shí)才知道元素的類型
在看數(shù)組的時(shí)候,因?yàn)閏lass是動(dòng)態(tài)創(chuàng)建的,所以看了很久,但是根據(jù)數(shù)組的特性,基本可以認(rèn)為數(shù)組的域和方法,類似于:
class?A<T>?implements?Cloneable,?java.io.Serializable?{??public?final?int?length?=?X;?? ??public?T[]?clone()?{??try?{????return?(T[])?super.clone(); ??}?catch?(CloneNotSupportedException?e)?{????throw?new?InternalError(e.getMessage()); ??} ?} }
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。