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方法:
- 使用经典的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()
该方法的是用于回收垃圾,当垃圾回收器确定不存在对该对象的引用时,对象的垃圾回收器会调用此方法。