首先來看它的繼承體系
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)了這個接口返弹,就能支持快速隨機訪問智润。
下面來看下它的用法:
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 < 0 || index >= 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();
}
}
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;