jdk1.8源码填坑计划-1-Object

从所有类的基类开始——java.lang.Object, Object类是所有类的基类,任何类都直接或间接继承此类,Object类中能访问的方法在所有类中都可以调用。

Object的Structure

Structure of Object

java.lang包

让我感到意外的是,我居然不知道这个包不需要引用(应该是我觉得有些理所当然,从没有注意过),不需要我写import,在编译阶段都会加入这一包。编译器帮我们做了这些事。
针对import来说,比如写了 import java.util.*; 这里会加载哪些类进来呢?答案是只加载需要用的类,并不会影响java代码的运行效率,在编译阶段就已经把需要的整理好了,并不会加入所有的类。
还有一个问题,可以自己写java.lang下的东西吗,比如写一个

1
2
3
4
5
6
7
8
package java.lang;

public class String {
public static void main(String[] args) {
// TODO Auto-generated method stub
System.out.println("我是个重写的String");
}
}

答案是报错,这里会报错说找不到main方法,因为类加载器优先加载了jre的那个java.lang.String。除非我自己再重写一个类加载器实现优先加载我写的这个java.lang.String。

构造方法

问题来了,Object到底有没有构造方法呢?第一次被人问到这个问题,我居然懵了,好神奇哦,这个类里面居然看到构造方法啊, 😓 这里我又一次把自己绕进去了,曾经无数遍的写Object object = new Object()居然还会在这个事上犯迷糊。。。所有类都有构造方法,没写就是一个默认无参的构造方法。

getClass方法

1
public final native Class<?> getClass();

这个方法是native的,所以内部实现也就不看了,毕竟这是个jdk源码阅读计划,不是如何设计java语言计划。
getClass的返回是一个对象的运行时类对象:The Class object that represents the runtime class of this object. 注释中也写了实际返回的是Class<? extends X> 实际上是这样

1
2
String a = "";
Class<? extends String> aClass = a.getClass();

equals方法

1
2
3
public boolean equals(Object obj) {
return (this == obj);
}

这个里面的注释标识了equals必须要满足的四个条件(xyz都是非空的)

  • reflexive 自反性 x.equals(x)必须返回true
  • symmetric 对称性 x.equals(y)必须等于y.equals(x)
  • transitive 传递性 x.equals(y) == y.equals(z)为true那么x.equals(z)也为true
  • consistent 一致性 xy不被修改的时候,x.equals(y)无论调用多少次都是返回同一结果

同时注释中还说了,equals和hashcode这两个方法只要重写了其中一个,都要重写另外一个为好,为了保证相同的两个对象有相同的hash值。

hashCode方法

1
public native int hashCode();

返回这个对象的散列值,任何hashCode最大2^32,要尽可能稀疏。
HashCode和对象的特征如下:

  • 两个对象相等,其 hashCode 一定相同;
  • 两个对象不相等,其 hashCode 有可能相同;
  • hashCode 相同的两个对象,不一定相等;
  • hashCode 不相同的两个对象,一定不相等;

关于hashCode这个方法,我还是放在集合中写吧,真不是一句话两句话能说清楚的。

toString方法

1
2
3
public String toString() {
return getClass().getName() + "@" + Integer.toHexString(hashCode());
}

以前我总以为类默认的toString打印的是对象存储的内存位置,其实不是,是它的hashCode,而且hashCode是可能重复的。

clone方法

1
protected native Object clone() throws CloneNotSupportedException;

protected方法,也是native的,一个对象调用clone()它的类必须要是实现Cloneable接口,要不然会报CloneNotSupportedException异常。
这里牵扯到浅拷贝和深拷贝的问题,通过读代码注释可以发现clone方法是浅拷贝。clone中坑又不小,让人头大。

ShadowClone浅拷贝

所谓浅拷贝,就是拷贝一个副本出来,将当前对象的非静态属性复制到新的对象中,如果属性是值类型,那就对这个属性的值复制;如果是引用类型的,则复制这一属性的引用。这样的话原对象和新对象的引用类型属性其实是同一个对象,修改新对象的引用属性也会修改原对象的引用属性。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
public class ShadowClone implements Cloneable{

private int a; // 基本类型
private int[] b; // 非基本类型
// 重写Object.clone()方法,并把protected改为public
@Override
public Object clone(){
ShadowClone sc = null;
try
{
sc = (ShadowClone) super.clone();
} catch (CloneNotSupportedException e){
e.printStackTrace();
}
return sc;
}
public int getA() { return a; }
public void setA(int a) { this.a = a; }
public int[] getB() { return b; }
public void setB(int[] b) { this.b = b; }

public static void main(String[] args) {
ShadowClone c1 = new ShadowClone();
//对c1赋值
c1.setA(100) ;
c1.setB(new int[]{1000}) ;

System.out.println("克隆前c1: a="+c1.getA()+" b[0]="+c1.getB()[0]);
//克隆出对象c2,并对c2的属性A,B,C进行修改
ShadowClone c2 = (ShadowClone) c1.clone();
System.out.println("克隆后c1: a="+c1.getA()+" b[0]="+c1.getB()[0]);
System.out.println("克隆后c2: a="+c2.getA()+ " b[0]="+c2.getB()[0]);
//对c2进行修改
c2.setA(50) ;
int []a = c2.getB() ;
a[0]=5 ;
c2.setB(a);
System.out.println("修改后c1: a="+c1.getA()+" b[0]="+c1.getB()[0]);
System.out.println("修改后c2: a="+c2.getA()+ " b[0]="+c2.getB()[0]);
}
}

运行结果
  克隆前c1: a=100 b[0]=1000
  克隆后c1: a=100 b[0]=1000
  克隆后c2: a=100 b[0]=1000
  修改后c1: a=100 b[0]=5
  修改后c2: a=50 b[0]=5
可以看到浅拷贝的现象,非基本类型的属性只是做了一个引用副本而已。这里有几个奇葩:String、Integer、Long等类型。这几个是引用类型,但是它又可以和int一样被拷贝,为什么会这样,放到下篇中写吧。

DeepClone深拷贝

深拷贝就是所有属性全部都重新复制一份新的,和原对象的属性毫无关系。
实现深拷贝有如下方法

  • 重写clone方法的时候把引用属性挨个都重新复制,这个适用于引用属性比较少的情况
  • 把原对象序列化,然后反序列化生成一个新对象作为拷贝对象,这个需要所有的序列化类都实现了Serializable。
  • 第三方的工具,比如什么Beanutils.copyProperties、Cglib的BeanCopier之类的,用的都是上一种的原理

方法1 引用属性赋值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
public class DeepClone implements Cloneable {

private int a; // 基本类型
private int[] b; // 非基本类型
// 重写Object.clone()方法,并把protected改为public
@Override
public Object clone(){
DeepClone sc = null;
try
{
sc = (DeepClone) super.clone();
int[] t = sc.getB();
int[] b1 = new int[t.length];
System.arrayCopy(t, 0, b1, 0, t.length);
sc.setB(b1);
} catch (CloneNotSupportedException e){
e.printStackTrace();
}
return sc;
}
public int getA()
{
return a;
}
public void setA(int a)
{
this.a = a;
}
public int[] getB() {
return b;
}
public void setB(int[] b) {
this.b = b;
}
}

方法2 序列化
实现一个深拷贝类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;

public class DeepClone implements Cloneable, Serializable{
private static final long serialVersionUID = 1L;

private int a; // 基本类型
private int[] b; // 非基本类型
// 重写Object.clone()方法,并把protected改为public
@Override
public Object clone(){
try {
//序列化
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos);

oos.writeObject(this);

//反序列化
ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bis);

return ois.readObject();
} catch (Exception e){
e.printStackTrace();
}
return null;
}
public int getA()
{
return a;
}
public void setA(int a)
{
this.a = a;
}
public int[] getB() {
return b;
}
public void setB(int[] b) {
this.b = b;
}


public static void main(String[] args) {
DeepClone c1 = new DeepClone();
//对c1赋值
c1.setA(100) ;
c1.setB(new int[]{1000}) ;

System.out.println("克隆前c1: a="+c1.getA()+" b[0]="+c1.getB()[0]);
//克隆出对象c2,并对c2的属性A,B,C进行修改
DeepClone c2 = (DeepClone) c1.clone();
System.out.println("克隆后c1: a="+c1.getA()+" b[0]="+c1.getB()[0]);
System.out.println("克隆后c2: a="+c2.getA()+ " b[0]="+c2.getB()[0]);
//对c2进行修改
c2.setA(50) ;
int []a = c2.getB() ;
a[0]=5 ;
c2.setB(a);
System.out.println("修改后c1: a="+c1.getA()+" b[0]="+c1.getB()[0]);
System.out.println("修改后c2: a="+c2.getA()+ " b[0]="+c2.getB()[0]);
}
}

运行结果
  克隆前c1: a=100 b[0]=1000
  克隆后c1: a=100 b[0]=1000
  克隆后c2: a=100 b[0]=1000
  修改后c1: a=100 b[0]=1000
  修改后c2: a=50 b[0]=5

方法3是封装好的方法2,我日常经常用,这里主要用的是反射(Beanutils.copyProperties)和动态代理(Cglib的BeanCopier),只要属性名对上了,就直接深拷贝。

notify()/notifyAll()/wait()

这里的坑比clone方法还大,暂时不在这里写,放在最后几篇中写

finalize方法

1
protected void finalize() throws Throwable {}

finalize方法是用来清理掉某个对象的,但是它和C++的析构函数是不一样的,析构方法调用之后,这个对象就彻底没了,但是java中调用了finalize方法,只是把这个加入gc的队列中,等下次gc的时候会把它干掉。。。我的理解是这样,应该是对的。

registerNatives方法

1
2
3
4
private static native void registerNatives();
static {
registerNatives();
}

写在static的程序在new Object()的那一刻就会执行了,所以每个对象执行的第一步都是这个registerNatives。这个是用来加载类中写了native的方法的,去找寻本地C写的方法然后映射起来。

0%