JDK源码分析 – Object

Object类的申明

1 public class Object {…}

Object类是Java中所有类的父类,每个类都默认继承Object这个超类,这也说明Object中所有公有方法都被任何类继承,Java中所有对象(包括数组)也都继承了Object的方法。

Object部分方法分析

Object内部实现了如下一系列方法:

public final native Class<?> getClass()

使用public final native修饰,表示这是一个不可以被重写的本地方法,该方法的作用是返回对象的运行时的类对象,那么在java中获取类对象还有另外两种种方式:类名.class和Class.forName(“xxx”),这三者有什么区别呢?我们可以通过下面的例子了解一下:

准备两个类:

 1 public class ParentA{
 2 
 3     private  Integer age;
 4 
 5    private  String name;
 6 
 7     public  ParentA(){
 8         System.out.println("执行构造函数");
 9     }
10 
11     //static 代码块 只在类的加载时执行一次,它的作用是初始化类
12     static {
13 
14         System.out.println("执行了 static 代码块");
15     }
16 
17     //构造代码块 在实例化类时执行,它的作用是给所有对象属性进行初始化赋值
18     {
19        this.age = 10;
20        System.out.println("执行了 构造代码块");
21     }
22 
23     public Integer getAge() {
24         return age;
25     }
26 
27     public void setAge(Integer age) {
28         this.age = age;
29     }
30 
31         public Strinh getName() {
32         return name;
33     }
34 
35     public void setName(String name) {
36         this.name = name;
37     }
38 }
1 public class ChildA extends  ParentA {}

  测试.getClass()

System.out.println("******************** p.getClass() *******************************");
ParentA p = new ChildA();
System.out.println("p.getClass():"+p.getClass());

  输出:

  测试 .class

1 System.out.println("********************* ParentA.class ******************************");
2 Class test2 = ParentA.class;  //加载到内存并没有初始化,所有此种方式没有执行 静态代码块
3 //test2.getClassLoader().loadClass("ParentA"); //该方法将Class文件加载到内存时,并不会执行类的初始化
4 System.out.println("ParentA.class:"+test2);

  输出:

  测试Class.forName()

1 System.out.println("********************** Class.forName *****************************");
2 Class classForName = Class.forName("ParentA"); //该方法在将Class文件加载到内存的同时,会执行类的初始化.
3 System.out.println("Class.forName:"+classForName);

  输出:

首先我们看下面三个的输出:

System.out.println("Class.forName:"+classForName);
System.out.println("p.getClass():"+p.getClass());
System.out.println("ParentA.class:"+ParentA.class);

可以发现:p.getClass() 获取的并不是ParentA而是ChildA,这也印证了上面的定义“返回对象运行时的类对象”,因此如果只是知道一个实例,可以通过getClass()方法获得该对象的类类型,如果明确知道一个类,则直接使用.class的方式获得该类型的类对象

另外我们还可以发现Class.forName方法在加载类的时候执行了静态初始化代码块,而通过.class的方式什么都没有执行。

总结:

 

自身区别

返回类对象的区别

类加载区别

对象.getClass()

方法

能够获取类对象(运行时确定)

既然连实例都有了,说明类之前已经加载到内存了

类名.class

属性

能够获取类对象(编译时确定

将类加载到内存,但是并没有执行静态初始化

Class.forName()

方法

能够获取类对象(运行时确定)

将类将在到内存,并习执行静态初始化

 

另外,补充一段关于类加载的说明:

Java的“类加载”是一个类从被加载到虚拟机内存中开始,到卸载出虚拟机内存为值得整个生命周期的一个过程,包括加载,验证,准备,解析,初始化这个五个阶段,尔“加载”只得的类加载的第一个阶段,类中的静态块会在整个类加载过程中的初始化阶段执行,而不是在类加载过程中的加载阶段执行。

public boolean equals(Object  obj)

 Object类提供的此方法的实现如下,可以看到默认情况下equals和==是等效的,都是比较两个对象的引用是否一致。

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

Java中使用equals方法的使用通常需要遵守如下约定:

  • 自反性:对于任何非空引用值 x,x.equals(x)应该返回 true。
  • 对称性:对于任何非空引用值 x和y,x.equals(y) 返回true时 y.equals(x)也返回true。
  • 传递性:对于任何非空的参考值 x,y以及z,如果 x.equals(y)返回true并且 y.equals(z)返回true,那么 x.equals(z)也应该返回true。
  • 一致性:对于任何非空引用值 x和y ,在不修改x或者y值得前提下,多次执行x.equals(y)返回相同的结果
  • 非空性:对于任何非空引用值 x,x.equals(null) 都应返回 false。

 需要注意的是,在重写equals方法时通常也需要重写hashCode 方法,以便维护该hashCode方法的常规协定,该协定声明相等的对象必须具有相同的哈希代码

public int hashCode()

Object类提供的hashCode方法的实现如下,该方使用native修饰,返回对象的哈希值。

1 public native int hashCode();

Java中使用hashCode方法的使用通常需要遵守如下约定:

  • 在执行Java应用程序期间,只要对象的值得修改不会影响equals方法返回结果,那么执行hasCode,返回的哈希值也应该和之前一样,不受影响
  • 如果两个对象使用equals方法比较是相等的,那么这两个对象的hasCode也应该是一样的
  • 如果两个对象使用equals方法比较不等,那么最好两个对象的hasCode也不一样(允许一样),这样当对象作为Map的key时,可以提高哈希表的性能

反过来只要遵循上面的约定:如果两个对象,如果hasCode相同,这两个对象也不一定相同;如果hasCode不同,则两个对象一定不等

如何重写hasCode方法:

  1. 使用经典的17和31散列码方法,这里手写起来还是有点麻烦的,我们可以借助Apache Commons Lang下的EqualsBuilder和HashCodeBuilder 来辅助实现 
 1 @Override
 2 public int hashCode() {
 3     return new HashCodeBuilder(17, 37)
 4             .append(name)
 5             .append(age)
 6             .toHashCode();
 7 
 8 }
 9 
10 @Override
11 public boolean equals(Object obj) {
12     if(obj==this)
13         return true;
14     if(!(obj instanceof  ParentA))
15         return false;
16 
17     ParentA p = (ParentA)obj;
18    return new EqualsBuilder().append(this.age,p.getAge()).append(this.name,p.getName()).isEquals();
19 }

   2. 使用java.util.Objects同时重写equals和hasCode两个方法

 1 @Override
 2 public int hashCode() {
 3     return Objects.hash(age,name);
 4 }
 5 
 6 @Override
 7 public boolean equals(Object obj) {
 8    if(obj==this)
 9        return true;
10    if(!(obj instanceof  ParentA))
11        return false;
12 
13    ParentA p = (ParentA)obj;
14    return Objects.equals(this.name,p.name) && this.age == p.age;
15 }

public  String  toString()

Object类提供的toString方法的实现如下,

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

默认的toString方法返回的是类名+当前示例的hasCode十六进制字符串的形式,开发中常常会重写toString方法,将具体属性值拼接后输出:

1 @Override
2 public String toString() {
3     return "ParentA{" +
4             "age=" + age +
5             ", name='" + name + '\'' +
6             '}';
7 }

如使用Apache Commons Lang工具包中的ToStringBuilder类还可以比较简洁的输出

1 @Override
2 public String toString() {
3   return ToStringBuilder.reflectionToString(this,ToStringStyle.SHORT_PREFIX_STYLE);
4 }

在控制台输出某个对象时,使用System.out.println(p)和使用 System.out.println(p.toString())是一样的,System.out.println(p)也是默认调用了toString()方法。

private static native void registerNatives()

这又是一个使用native修饰的本地方法。

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

该方法的调用时在Object中的静态代码块中,由于静态代码块会在类加载时执行,Object是所有的的父类,所以这个方法的作用其实是在类加载的时候注册所有native方法(因为用native关键字修饰的函数表明该方法的实现并不是在Java中去完成,而是由C/C++去完成,并被编译成了.dll,由Java去调用)

protected void finalize() 

该方法的是用于回收垃圾,当垃圾回收器确定不存在对该对象的引用时,对象的垃圾回收器会调用此方法。

 

posted @ 2018-08-31 20:52  互联网荒漠  阅读(185)  评论(0编辑  收藏  举报