首先来看它的继承体系
public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable
**它实现的三了接口,
相同点都是空接口,作为一个标记出现
- 1 RandomAccess:
public interface RandomAccess {
}
RandomAccess作为随机访问的标志,代表只要实现了这个接口,就能支持快速随机访问。
下面来看下它的用法:
instanceof其作用是用来判断某对象是否为某个类或接口类型
由此可以看出,根据判断list是否实现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循环获取元素
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;
}
通过查看源代码,发现实现RandomAccess接口的List集合采用一般的for循环遍历,而未实现这接口则采用迭代器。ArrayList用for循环遍历比iterator迭代器遍历快,LinkedList用iterator迭代器遍历比for循环遍历快。
总结:
判断出接收的List子类是ArrayList还是LinkedList,需要用instanceof来判断List集合子类是否实现RandomAccess接口!从而能够更好选择更优的遍历方式,提高性能!
-
2 Cloneable
Cloneable也是一个标记接口,只有实现这个接口后,然后在类中重写Object中的clone方法,后面通过类调用clone方法才能克隆成功,如果不实现这个接口,则会抛出CloneNotSupportedException(克隆不被支持)异常。
Object中的clone方法:
protected native Object clone throws CloneNotSupportedException;
这里有一个疑问:Object中的clone方法是一个空的方法,那么他是如何判断类是否实现了cloneable接口呢?
原因在于这个方法中有一个native关键字修饰。
下面对 native 方法做简单解释:
native关键字的函数都是操作系统实现的,java只能调用。简单地讲,一个Native 方法就是一个java调用非java代码的接口。
java是跨平台的语言,既然是跨了平台,所付出的代价就是牺牲一些对底层的控制,而java要实现对底层的控制,就要一些其他语言的帮助,这个就是native的作用了
native的意思就是通知操作系统,这个函数你必须给我实现,因为我要使用。所以native关键字的函数都是操作系统实现的
本地方法非常有用,因为它有效地扩充了jvm.事实上,我们所写的java代码已经用到了本地方法,在sun的java的并发(多线程)的机制实现中,许多与操作系统的接触点都用到了本地方法,这使得java程序能够超越java运行时的界限。有了本地方法,java程序可以做任何应用层次的任务。
每一个native方法在jvm中都有一个同名的实现体,native方法在逻辑上的判断都是由实现体实现,另外这种native修饰的方法对返回类型,异常控制等都没有约束。
由此可见,这里判断是否实现cloneable接口,是在调用jvm中的实现体时进行判断的。
JVM怎样使Native Method跑起来:
我们知道,当一个类第一次被使用到时,这个类的字节码会被加载到内存,并且只会加载一次。
在这个被加载的字节码的入口维持着一个该类所有方法描述符的list,这些方法描述符包含这样一些信息:方法代码存于何处,它有哪些参数,方法的描述符(public之类)等等。
如果一个方法描述符内有native,这个描述符块将有一个指向该方法的实现的指针。
这些实现在一些DLL文件内,但是它们会被操作系统加载到java程序的地址空间。
当一个带有本地方法的类被加载时,其相关的DLL并未被加载,因此指向方法实现的指针并不会被设置。
当本地方法被调用之前,这些DLL才会被加载,这是通过调用java.system.loadLibrary()实现的。
最后需要提示的是,使用本地方法是有开销的,它丧失了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; //学校人数
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的基本数据类型与Stirng类型,不会改变school的基本数据类型与Stirng类型。
基本数据类型例如int,在clone的时候会重新开辟一个四个字节的大小的空间,将其赋值。而String则由于String变量的唯一性,如果在school2中改变了String类型的值,则会生成一个新的String对象,对之前的没有影响。 这就是浅度隆。
- 深度克隆:
需要让student实现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方法执行
school的hashCode:204349222; school的student的hashCode:231685785
school2的hashCode:114935352; school2的student的hashCode:2110121908
但是,school2修改sex的值,发现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);
控制台打印:
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类型没有实现cloneable接口,也没有重写clone方法。在clone的时候将StringBuffer对象的地址传递了过去}
这种情况应该怎么解决呢
StringBuffer是没有实现cloneable接口的,所以无法克隆,因此在设置stu2的sex时,创建一个新的StringBuffer对象
// student2.setSex(student2.getSex().append(6666));
// 创建一个新的StringBuffer对象。解决修改sex影响源数据的问题
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接口也是一个标记,没有方法
首先来看下如何把对象输出到文件中,创建一个类实现序列化接口
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 创建对象
Person person = new Person("小王", 18);
// 2 创建对象输出流
ObjectOutputStream objOut = new ObjectOutputStream(new
FileOutputStream("C:/Users/Administrator/Desktop/photo/person.txt"));
// 3 调用方法将对象写入流中
objOut.writeObject(person);
// 4 关闭输出流
objOut.close();
}
}
Serializable的步骤:
1.首先要创建某些OutputStream对象:
OutputStream outputStream = new FileOutputStream("output.txt")
2.将其封装到ObjectOutputStream对象内:
ObjectOutputStream objectOutputStream = new ObjectOutputStream(outputStream);
3.此后只需调用writeObject()即可完成对象的序列化,并将其发送给OutputStream:objectOutputStream.writeObject(Object);
4.最后不要忘记关闭资源:
objectOutputStream.close(), outputStream .close();
Serializable的一些说明:
对象的序列化处理非常简单,只需对象实现了Serializable 接口即可(该接口仅是一个标记,没有方法)
序列化的对象包括基本数据类型,所有集合类以及其他许多东西,还有Class 对象
对象序列化不仅保存了对象的“全景图”,而且能追踪对象内包含的所有句柄并保存那些对象;
接着又能对每个对象内包含的句柄进行追踪
使用transient、static关键字修饰的的变量,在序列化对象的过程中,该属性不会被序列化。
但是,会发现static修饰的成员变量在序列化后然后紧接着反序列化,打印出来的这个类的成员变量是有值的!!!原因是读取的值是当前jvm中的方法区对应此变量的值。
反序列化:
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 创建输入流
ObjectInputStream objIn = new ObjectInputStream(new FileInputStream("C:/Users/Administrator/Desktop/photo/person.txt"));
// 2 调用方法读取对象
Person person = (Person) objIn.readObject();
// 3 关闭流
objIn.close();
return person.toString();
}
}
控制台打印:
Person{name='小王', age=18}
- 关于序列化冲突问题:
就是当创建一个类实现了Serializable接口之后,会为这个类添加序列化号
当修改这个类的时候,这个类的序列化号也会发生改变。
因此,在修改类前序列化;修改类之后反序列化,会校验文件的序列化号和该类的序列化号是否一致,不一致则抛出序列化号不一致的异常,也就是序列化冲突
解决方案:
在指定的类中指定序列化号
private static final long serialVersionUID = 123456789L;