1.探索ArrayList源碼之實現(xiàn)的三個接口作用

首先來看它的繼承體系

image.png

public class ArrayList<E> extends AbstractList<E>
 implements List<E>, RandomAccess, Cloneable, java.io.Serializable

**它實現(xiàn)的三了接口忘蟹,相同點都是空接口嘁傀,作為一個標記出現(xiàn)

  • 1 RandomAccess:
public interface RandomAccess {
}

RandomAccess作為隨機訪問的標志,代表只要實現(xiàn)了這個接口返弹,就能支持快速隨機訪問智润。

下面來看下它的用法:

Collections類中的binarySearch()方法
instanceof其作用是用來判斷某對象是否為某個類或接口類型
由此可以看出悦冀,根據(jù)判斷l(xiāng)ist是否實現(xiàn)RandomAccess接口來決定實行indexedBinarySerach(list,key)或iteratorBinarySerach(list,key)方法安吁。

indexedBinarySerach(list,key)方法源碼:

private static <T> int indexedBinarySearch(List<? extends T> l, T key, Comparator<? super T> c) {
        int low = 0;
        int high = l.size()-1;

        while (low <= high) {
            int mid = (low + high) >>> 1;
            // 普通for循環(huán)獲取元素
            T midVal = l.get(mid);
            int cmp = c.compare(midVal, key);

            if (cmp < 0)
                low = mid + 1;
            else if (cmp > 0)
                high = mid - 1;
            else
                return mid; // key found
        }
        return -(low + 1);  // key not found
    }


/** List 接口 */
public interface List<E> extends Collection<E> {
      ...
      ...
/**
     * Returns the element at the specified position in this list.
     *
     * @param index index of the element to return
     * @return the element at the specified position in this list
     * @throws IndexOutOfBoundsException if the index is out of range
     *         (<tt>index &lt; 0 || index &gt;= size()</tt>)
     */
    E get(int index);
}

iteratorBinarySerach(list,key)方法源碼

 private static <T>
    int iteratorBinarySearch(List<? extends Comparable<? super T>> list, T key)
    {
        int low = 0;
        int high = list.size()-1;
        ListIterator<? extends Comparable<? super T>> i = list.listIterator();

        while (low <= high) {
            int mid = (low + high) >>> 1;
            // 迭代器獲取元素
            Comparable<? super T> midVal = get(i, mid);
            int cmp = midVal.compareTo(key);

            if (cmp < 0)
                low = mid + 1;
            else if (cmp > 0)
                high = mid - 1;
            else
                return mid; // key found
        }
        return - (low + 1);  // key not found
    }


    /**
     * Gets the ith element from the given list by repositioning the specified
     * list listIterator.
     */
    private static <T> T get(ListIterator<? extends T> i, int index) {
        T obj = null;
        int pos = i.nextIndex();
        if (pos <= index) {
            do {
                obj = i.next();
            } while (pos++ < index);
        } else {
            do {
                obj = i.previous();
            } while (--pos > index);
        }
        return obj;
    }

通過查看源代碼醉蚁,發(fā)現(xiàn)實現(xiàn)RandomAccess接口的List集合采用一般的for循環(huán)遍歷,而未實現(xiàn)這接口則采用迭代器鬼店。ArrayList用for循環(huán)遍歷比iterator迭代器遍歷快网棍,LinkedList用iterator迭代器遍歷比for循環(huán)遍歷快。

總結(jié):
判斷出接收的List子類是ArrayList還是LinkedList妇智,需要用instanceof來判斷List集合子類是否實現(xiàn)RandomAccess接口滥玷!從而能夠更好選擇更優(yōu)的遍歷方式,提高性能巍棱!

  • 2 Cloneable
    Cloneable也是一個標記接口惑畴,只有實現(xiàn)這個接口后,然后在類中重寫Object中的clone方法航徙,后面通過類調(diào)用clone方法才能克隆成功如贷,如果不實現(xiàn)這個接口,則會拋出CloneNotSupportedException(克隆不被支持)異常到踏。
    Object中的clone方法:
protected native Object clone throws CloneNotSupportedException;

這里有一個疑問:Object中的clone方法是一個空的方法杠袱,那么他是如何判斷類是否實現(xiàn)了cloneable接口呢?
原因在于這個方法中有一個native關(guān)鍵字修飾窝稿。

下面對 native 方法做簡單解釋:

native關(guān)鍵字的函數(shù)都是操作系統(tǒng)實現(xiàn)的楣富,java只能調(diào)用。簡單地講讹躯,一個Native 方法就是一個java調(diào)用非java代碼的接口菩彬。
java是跨平臺的語言缠劝,既然是跨了平臺,所付出的代價就是犧牲一些對底層的控制骗灶,而java要實現(xiàn)對底層的控制惨恭,就要一些其他語言的幫助,這個就是native的作用了
native的意思就是通知操作系統(tǒng)耙旦,這個函數(shù)你必須給我實現(xiàn)脱羡,因為我要使用。所以native關(guān)鍵字的函數(shù)都是操作系統(tǒng)實現(xiàn)的
本地方法非常有用免都,因為它有效地擴充了jvm.事實上锉罐,我們所寫的java代碼已經(jīng)用到了本地方法,在sun的java的并發(fā)(多線程)的機制實現(xiàn)中绕娘,許多與操作系統(tǒng)的接觸點都用到了本地方法脓规,這使得java程序能夠超越java運行時的界限。有了本地方法险领,java程序可以做任何應用層次的任務侨舆。
每一個native方法在jvm中都有一個同名的實現(xiàn)體,native方法在邏輯上的判斷都是由實現(xiàn)體實現(xiàn)绢陌,另外這種native修飾的方法對返回類型挨下,異常控制等都沒有約束脐湾。
由此可見臭笆,這里判斷是否實現(xiàn)cloneable接口,是在調(diào)用jvm中的實現(xiàn)體時進行判斷的秤掌。

JVM怎樣使Native Method跑起來:

我們知道愁铺,當一個類第一次被使用到時,這個類的字節(jié)碼會被加載到內(nèi)存闻鉴,并且只會加載一次帜讲。
在這個被加載的字節(jié)碼的入口維持著一個該類所有方法描述符的list,這些方法描述符包含這樣一些信息:方法代碼存于何處椒拗,它有哪些參數(shù)似将,方法的描述符(public之類)等等。
如果一個方法描述符內(nèi)有native蚀苛,這個描述符塊將有一個指向該方法的實現(xiàn)的指針在验。
這些實現(xiàn)在一些DLL文件內(nèi),但是它們會被操作系統(tǒng)加載到java程序的地址空間堵未。
當一個帶有本地方法的類被加載時腋舌,其相關(guān)的DLL并未被加載,因此指向方法實現(xiàn)的指針并不會被設(shè)置渗蟹。
當本地方法被調(diào)用之前块饺,這些DLL才會被加載赞辩,這是通過調(diào)用java.system.loadLibrary()實現(xiàn)的。

最后需要提示的是授艰,使用本地方法是有開銷的辨嗽,它喪失了java的很多好處。如果別無選擇淮腾,我們才選擇使用本地方法糟需。

深入理解深度克隆與淺度克隆:

  • 淺度克鹿瘸:
    定義一個學生類
public class Student{
    private String name;   //姓名
    private int age;       //年齡
    private StringBuffer sex;  //性別
     
     get洲押、set、toString方法
 }

定義一個學校類圆凰,類中重寫clone方法

public  class School implements Cloneable{  
    private String schoolName;   //學校名稱 
    private int stuNums;         //學校人數(shù) 
    private Student stu;         //一個學生
    
    get杈帐、set、toString方法
    
    @Override
    protected School clone() throws CloneNotSupportedException {
        // TODO Auto-generated method stub
        return (School)super.clone();
    }
}

最后寫一個main類來測試一下:

public class CloneTest {

    public static void main(String[] args) throws CloneNotSupportedException {

        Student student = new Student();
        student.setAge(18);
        student.setName("小王");
        student.setSex(new StringBuffer("男"));

        School school = new School();
        school.setSchoolName("實驗小學");
        school.setStuNums(100);
        school.setStu(student);

        System.out.println("school的hashCode:"+school.hashCode() + "; 
                               school的student的hashCode:" + school.getStu().hashCode());

        School school2 = school.clone();
        System.out.println("school2的hashCode:"+school2.hashCode() + "; 
                               school2的student的hashCode:" + school.getStu().hashCode());
    }
}

控制臺打幼ǘぁ:

school的hashCode:204349222; school的student的hashCode:231685785
school2的hashCode:114935352; school2的student的hashCode:231685785

可以看到克隆的school2跟源對象school的hashCode不同娘荡,也就是說clone方法并不是把school的引用賦予school2,而是在堆中重新開辟了一塊空間驶沼,將school復制過去,將新的地址返回給school2争群。
但是回怜,它們里面的student對象的hashCode仍然是相同的,這兩個指向了同一個對象换薄。
也就是說修改school2的基本數(shù)據(jù)類型與Stirng類型玉雾,不會改變school的基本數(shù)據(jù)類型與Stirng類型。

基本數(shù)據(jù)類型例如int轻要,在clone的時候會重新開辟一個四個字節(jié)的大小的空間复旬,將其賦值。而String則由于String變量的唯一性冲泥,如果在school2中改變了String類型的值驹碍,則會生成一個新的String對象,對之前的沒有影響凡恍。 這就是淺度隆志秃。

  • 深度克隆:
    需要讓student實現(xiàn)cloneable接口嚼酝,重寫clone方法
 @Override
 protected Student clone() throws CloneNotSupportedException {
     return (Student) super.clone();
 }

然后浮还,在school的clone方法中將school中的stu對象手動clone。

 @Override
 protected School clone() throws CloneNotSupportedException {
     School school = null;
     school = (School)super.clone();
     school.stu = stu.clone();
     return school;
 }

再次main方法執(zhí)行

school的hashCode:204349222; school的student的hashCode:231685785
school2的hashCode:114935352; school2的student的hashCode:2110121908

但是闽巩,school2修改sex的值钧舌,發(fā)現(xiàn)school也隨著改變了

// 修改school2的student2的值
Student student2 = school2.getStu();
student2.setSex(student2.getSex().append(6666));
student2.setName("如花");
student2.setAge(68);
// 修改school2其它值
school2.setSchoolName("希望小學");
school2.setStuNums(1000);
System.out.println("school: " + school);
System.out.println("school2: " + school2);

控制臺打拥L馈:

school的hashCode:204349222; school的student的hashCode:231685785
school2的hashCode:114935352; school2的student的hashCode:2110121908
school: School{schoolName='實驗小學', stuNums=100, stu=Student{name='小王', age=18, sex=男6666}}
school2: School{schoolName='希望小學', stuNums=1000, stu=Student{name='如花', age=68, sex=男6666}}

原因在于sex的類型是Stringbuffer,而StringBuffer類型沒有實現(xiàn)cloneable接口洼冻,也沒有重寫clone方法崭歧。在clone的時候?qū)tringBuffer對象的地址傳遞了過去}

這種情況應該怎么解決呢
StringBuffer是沒有實現(xiàn)cloneable接口的,所以無法克隆,因此在設(shè)置stu2的sex時碘赖,創(chuàng)建一個新的StringBuffer對象

// student2.setSex(student2.getSex().append(6666));
// 創(chuàng)建一個新的StringBuffer對象驾荣。解決修改sex影響源數(shù)據(jù)的問題
student2.setSex(new StringBuffer("newString"));

控制臺打印:

school2的hashCode:114935352; school2的student的hashCode:2110121908
school: School{schoolName='實驗小學', stuNums=100, stu=Student{name='小王', age=18, sex=男}}
school2: School{schoolName='希望小學', stuNums=1000, stu=Student{name='如花', age=68, sex=newString}}
  • 3 Serializable

Serializable接口也是一個標記普泡,沒有方法

首先來看下如何把對象輸出到文件中,創(chuàng)建一個類實現(xiàn)序列化接口

public class Person implements Serializable {

    private String name;
    private int age;
    ...
}

測試類

package com.Serializable;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;

/**
 * @author: jk
 * @since: 2020-03-08 01:14
 * <p>
 * 描述
 * </p>
 **/
public class SerializableTest {

    public static void main(String[] args) throws IOException {

        // 1 創(chuàng)建對象
        Person person = new Person("小王", 18);

        // 2 創(chuàng)建對象輸出流
        ObjectOutputStream objOut = new ObjectOutputStream(new 
                                          FileOutputStream("C:/Users/Administrator/Desktop/photo/person.txt"));

        // 3 調(diào)用方法將對象寫入流中
        objOut.writeObject(person);

        // 4 關(guān)閉輸出流
        objOut.close();
    }
}
image.png

Serializable的步驟:

1.首先要創(chuàng)建某些OutputStream對象:
OutputStream outputStream = new FileOutputStream("output.txt")

2.將其封裝到ObjectOutputStream對象內(nèi):
ObjectOutputStream objectOutputStream = new ObjectOutputStream(outputStream);

3.此后只需調(diào)用writeObject()即可完成對象的序列化播掷,并將其發(fā)送給OutputStream:objectOutputStream.writeObject(Object);

4.最后不要忘記關(guān)閉資源:
objectOutputStream.close(), outputStream .close();

Serializable的一些說明:

對象的序列化處理非常簡單,只需對象實現(xiàn)了Serializable 接口即可(該接口僅是一個標記撼班,沒有方法)
序列化的對象包括基本數(shù)據(jù)類型歧匈,所有集合類以及其他許多東西,還有Class 對象
對象序列化不僅保存了對象的“全景圖”砰嘁,而且能追蹤對象內(nèi)包含的所有句柄并保存那些對象件炉;
接著又能對每個對象內(nèi)包含的句柄進行追蹤

使用transient、static關(guān)鍵字修飾的的變量矮湘,在序列化對象的過程中斟冕,該屬性不會被序列化。
但是缅阳,會發(fā)現(xiàn)static修飾的成員變量在序列化后然后緊接著反序列化磕蛇,打印出來的這個類的成員變量是有值的!J臁秀撇!原因是讀取的值是當前jvm中的方法區(qū)對應此變量的值。

反序列化:

public class SerializableTest {

    public static void main(String[] args) throws IOException, ClassNotFoundException {

       // serializable();
        System.out.println(deserialization());

    }

private static String deserialization() throws IOException, ClassNotFoundException {
    // 1 創(chuàng)建輸入流
    ObjectInputStream objIn = new ObjectInputStream(new FileInputStream("C:/Users/Administrator/Desktop/photo/person.txt"));
    // 2 調(diào)用方法讀取對象
    Person person = (Person) objIn.readObject();
    // 3 關(guān)閉流
    objIn.close();
    return person.toString();
   }
}

控制臺打酉蜃濉:

Person{name='小王', age=18}
  • 關(guān)于序列化沖突問題:

就是當創(chuàng)建一個類實現(xiàn)了Serializable接口之后呵燕,會為這個類添加序列化號
當修改這個類的時候,這個類的序列化號也會發(fā)生改變件相。
因此再扭,在修改類前序列化;修改類之后反序列化夜矗,會校驗文件的序列化號和該類的序列化號是否一致霍衫,不一致則拋出序列化號不一致的異常,也就是序列化沖突

解決方案:

在指定的類中指定序列化號
private static final long serialVersionUID = 123456789L;

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末侯养,一起剝皮案震驚了整個濱河市敦跌,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖柠傍,帶你破解...
    沈念sama閱讀 222,807評論 6 518
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件麸俘,死亡現(xiàn)場離奇詭異,居然都是意外死亡惧笛,警方通過查閱死者的電腦和手機从媚,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,284評論 3 399
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來患整,“玉大人拜效,你說我怎么就攤上這事「餮瑁” “怎么了紧憾?”我有些...
    開封第一講書人閱讀 169,589評論 0 363
  • 文/不壞的土叔 我叫張陵,是天一觀的道長昌渤。 經(jīng)常有香客問我赴穗,道長,這世上最難降的妖魔是什么膀息? 我笑而不...
    開封第一講書人閱讀 60,188評論 1 300
  • 正文 為了忘掉前任般眉,我火速辦了婚禮,結(jié)果婚禮上潜支,老公的妹妹穿的比我還像新娘甸赃。我一直安慰自己,他們只是感情好冗酿,可當我...
    茶點故事閱讀 69,185評論 6 398
  • 文/花漫 我一把揭開白布埠对。 她就那樣靜靜地躺著,像睡著了一般已烤。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上妓羊,一...
    開封第一講書人閱讀 52,785評論 1 314
  • 那天胯究,我揣著相機與錄音,去河邊找鬼躁绸。 笑死裕循,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的净刮。 我是一名探鬼主播剥哑,決...
    沈念sama閱讀 41,220評論 3 423
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼淹父!你這毒婦竟也來了株婴?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 40,167評論 0 277
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎困介,沒想到半個月后大审,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,698評論 1 320
  • 正文 獨居荒郊野嶺守林人離奇死亡座哩,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,767評論 3 343
  • 正文 我和宋清朗相戀三年徒扶,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片根穷。...
    茶點故事閱讀 40,912評論 1 353
  • 序言:一個原本活蹦亂跳的男人離奇死亡姜骡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出屿良,到底是詐尸還是另有隱情圈澈,我是刑警寧澤,帶...
    沈念sama閱讀 36,572評論 5 351
  • 正文 年R本政府宣布管引,位于F島的核電站士败,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏褥伴。R本人自食惡果不足惜谅将,卻給世界環(huán)境...
    茶點故事閱讀 42,254評論 3 336
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望重慢。 院中可真熱鬧饥臂,春花似錦、人聲如沸似踱。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,746評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽核芽。三九已至囚戚,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間轧简,已是汗流浹背驰坊。 一陣腳步聲響...
    開封第一講書人閱讀 33,859評論 1 274
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留哮独,地道東北人拳芙。 一個月前我還...
    沈念sama閱讀 49,359評論 3 379
  • 正文 我出身青樓,卻偏偏與公主長得像皮璧,于是被迫代替她去往敵國和親舟扎。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 45,922評論 2 361

推薦閱讀更多精彩內(nèi)容