Object类是java中所有类的父类,所有类默认(而非显式)继承Object。这也就意味着,Object类中的所有公有方法也将被任何类所继承。如果,整个java类体系是一颗树,那么Object类毫无疑问就是整棵树的根。
文章结构:
1)源码分析
2)浅拷贝与深拷贝
3)探讨hashcode与equals的设计使用。
一、源码分析
public class Object {
/* 一个本地方法,具体是用C(C++)在DLL中实现的,然后通过JNI调用。*/
private static native void registerNatives();
/* 对象初始化时自动调用此方法*/
static {
registerNatives();
}
/* 返回这个Object的运行时Class对象。这个Class对象被所代表的类的static synchronized
方法锁定。*/
public final native Class<?> getClass();
/*
hashCode 的常规协定是:(本质 上是 返回该对象的哈希码值。 )
1.同一个对象在没修改的情况下的hashCode必须相同。在 Java 应用程序执行期间,在对同一对象多次调用 hashCode 方法时,必须一致地返回相同的整数,前提是将对象进行 equals 比较时所用的信息没有被修改。从某一应用程序的一次执行到同一应用程序的另一次执行,该整数无需保持一致。
2.如果根据 equals(Object) 方法比较,两个对象是相等的,那么对这两个对象中的每个对象调用 hashCode 方法都必须生成相同的整数结果。
3.如果根据 equals(java.lang.Object) 方法,两个对象不相等,那么对这两个对象中的任一对象上调用 hashCode 方法不 要求一定生成不同的整数结果。但是,程序员应该意识到,为不相等的对象生成不同整数结果可以提高哈希表的性能。支持这个方法是为了提高哈希表的性能,例如HashMap。
实际上,被Object类定义的hashCode方法对不同的对象确实返回不同的整数。(这是通过
把对象的内部地址转化为一个整数来实现的,但是这个实现技巧不被Java编程语言需要。)
*/
public native int hashCode();
//没有重写过的equals,就是使用==,比较内存地址。
public boolean equals(Object obj) {
return ( this == obj);
}
/*本地CLONE方法,用于对象的复制。*/
protected native Object clone() throws CloneNotSupportedException;
/*返回该对象的字符串表示。非常重要的方法*/
public String toString() {
return getClass().getName() + "@" + Integer.toHexString(hashCode());
}
/*唤醒在此对象监视器上等待的单个线程。*/
public final native void notify();
/*唤醒在此对象监视器上等待的所有线程。*/
public final native void notifyAll();
/*在其他线程调用此对象的 notify() 方法或 notifyAll() 方法前,导致当前线程等待。换句话说,此方法的行为就好像它仅执行 wait(0) 调用一样。
当前线程必须拥有此对象监视器。该线程发布对此监视器的所有权并等待,直到其他线程通过调用 notify 方法,或 notifyAll 方法通知在此对象的监视器上等待的线程醒来。然后该线程将等到重新获得对监视器的所有权后才能继续执行。*/
public final void wait() throws InterruptedException {
wait( 0 );
}
/*在其他线程调用此对象的 notify() 方法或 notifyAll() 方法,或者超过指定的时间量前,导致当前线程等待。*/
public final native void wait( long timeout) throws InterruptedException;
/* 在其他线程调用此对象的 notify() 方法或 notifyAll() 方法,或者其他某个线程中断当前线程,或者已超过某个实际时间量前,导致当前线程等待。*/
public final void wait( long timeout, int nanos) throws InterruptedException {
if (timeout < 0 ) {
throw new IllegalArgumentException( "timeout value is negative" );
}
if (nanos < 0 || nanos > 999999 ) {
throw new IllegalArgumentException(
"nanosecond timeout value out of range" );
}
if (nanos >= 500000 || (nanos != 0 && timeout == 0 )) {
timeout++;
}
wait(timeout);
}
/*当垃圾回收器确定不存在对该对象的更多引用时,由对象的垃圾回收器调用此方法。*/
protected void finalize() throws Throwable { }
}
equals方法详解:
可以看出默认情况下equals进行对象比较时只判断了对象是否是其自身(也就是对比的是内存地址),当我们有特殊的“相等”逻辑时,则需要覆盖equals方法。
equals方法的通用约定:
- 1)自反性:对于任何非null的引用值x,x.equals(x)必须返回true。
- 2)对称性:对于任何非null的引用值x和y,当且仅当y.equals(x)返回true时,x.equals(y)必须返回true。
- 3)传递性:对于任何非null的引用值x、y、z,如果x.equals(y)返回true,并且y.equals(z)返回true,那么x.equals(z)也必须返回ture。
- 4)一致性:对于任何非null的引用值x和y,只要equals的比较操作在对象中所用的 信息 没有被修改,多次调用x.equals(y)就会一致的返回ture,或者一致的返回false。
- 5)非空性:对于任何非null的引用值x,x.equals(null)必须返回false。
clone方法详解:
1)简介:
clone方法将创建和返回该对象的一个拷贝。这个“拷贝”的精确含义取决于该对象的类。一般的含义是,对于任何对象x,表达式x.clone() != x 将会是true,并且,表达式x.clone().getClass() == x.getClass()将会是true。并且通常情况下,表达式x.clone().equals(x)将会是true。
clone在第一步是和new相似的, 都是分配内存,调用clone方法时,分配的内存和源对象(即调用clone方法的对象)相同,然后再使用原对象中对应的各个域,填充新对象的域, 填充完成之后,clone方法返回,一个新的相同的对象被创建,同样可以把这个新对象的引用发布到外部。但是hashCode会不一样了。
2)一个标准的clone实现需要做到的两点:
1)调用super.clone()方法
2)对于对象中的所有引用类型,均需要实现Cloneable接口,并重写clone方法,然后对每个引用执行clone方法。
@Override
protected Fruit clone() throws CloneNotSupportException{
Fruit fruit = (Fruit) super.clone();
fruit.color = new String(color);
return fruit;
}
3)使用clone方法的优点:
1)速度快。clone方法最终会调用Object.clone()方法,这是一个native方法,本质是内存块复制,所以在速度上比使用new创建对象要快。
2)灵活。可以在运行时动态的获取对象的类型以及状态,从而创建一个对象。
4)使用clone方法创建对象的缺点同样非常明显:
1)实现深拷贝较为困难,需要整个类继承系列的所有类都很好的实现clone方法。
2)需要处理CloneNotSupportedException异常。Object类中的clone方法被声明为可能会抛出CloneNotSupportedException,因此在子类中,需要对这一异常进行处理。
建议:对于浅拷贝,我们不应该实现Cloneable接口,而应该使用拷贝构造器或者拷贝工厂。
为什么?
它们不依赖于对象创建机制;它们不会与final域的正常使用发生冲突;它们不会抛出不必要的受检异常(check exception);他们不需要进行类型转换。
更关键的,这样更加的面向对象、
二、浅拷贝与深拷贝:
任何变成语言中,其实都有浅拷贝和深拷贝的概念,Java 中也不例外。我们需要对其概念非常清晰,才能帮助自己快速排查问题。
(1)什么是浅拷贝和深拷贝:
首先我们知道拷贝是针对一个已有对象的操作。
在 Java 中,除了基本数据类型(元类型)之外,还存在 类的实例对象 这个引用数据类型。而一般使用 = 号做赋值操作的时候。对于基本数据类型,实际上是拷贝的它的值,但是对于对象而言,其实赋值的只是这个对象的引用,将原对象的引用传递过去,他们实际上还是指向的同一个对象。
而浅拷贝和深拷贝就是在这个基础之上做的区分,如果在拷贝这个对象的时候,只对基本数据类型进行了拷贝,而对引用数据类型只是进行了引用的传递,而没有真实的创建一个新的对象,则认为是浅拷贝。反之,在对引用数据类型进行拷贝的时候,创建了一个新的对象,并且复制其内的成员变量,则认为是深拷贝。
(2)举例说明:
浅拷贝:
public class CloneDemo implements Cloneable {
public String name;
public CloneChildDemo cloneChildDemo;
@Override
public Object clone(){
try {
return super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return null;
}
}
public class CloneChildDemo{
private String name;
}
场景验证:
public static void main(String[]args){
CloneDemo cloneDemo = new CloneDemo();
cloneDemo.name = "辅助";
cloneDemo.cloneChildDemo = new CloneChildDemo();
CloneDemo cloneDemo1 = (CloneDemo) cloneDemo.clone();
System.out.println(cloneDemo.name);
System.out.println(cloneDemo1.name);
System.out.println(cloneDemo.hashCode());
System.out.println(cloneDemo1.hashCode());
System.out.println(cloneDemo == cloneDemo1);
System.out.println("--------------");
System.out.println(cloneDemo.cloneChildDemo == cloneDemo1.cloneChildDemo);
System.out.println(cloneDemo.cloneChildDemo.hashCode());
System.out.println(cloneDemo1.cloneChildDemo.hashCode());
}
/*
辅助
辅助
460141958
1163157884
false
------------
true
159413332
159413332
*/
可以看到,使用 clone() 方法,从 == 和 hashCode 的不同可以看出,clone() 方法实则是真的创建了一个新的对象。
但是,这只是一个浅拷贝操作。为什么?从最后对 cloneChildDemo的输出可以看到,cloneDemo 和cloneDemo1 的 child 对象,实际上还是指向了统一个对象,只对它的引用进行了传递。
深拷贝:
既然已经了解了对 clone() 方法,只能对当前对象进行浅拷贝,引用类型依然是在传递引用。
那么我们如何进行一次深拷贝?
1)序列化(serialization)这个对象,再反序列化回来,就可以得到这个新的对象,无非就是序列化的规则需要我们自己来写。
2)继续利用 clone() 方法,既然 clone() 方法,是我们来重写的,实际上我们可以对其内的引用类型的变量,再进行一次 clone()。
public class CloneDemo implements Cloneable {
public String name;
public CloneChildDemo cloneChildDemo;
@Override
public Object clone(){
try {
CloneDemo cloneDemo = (CloneDemo) super.clone();
cloneDemo.cloneChildDemo = (CloneChildDemo) this.cloneChildDemo.clone();
return cloneDemo;
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return null;
}
}
验证深拷贝:
public static void main(String[]args){
CloneDemo cloneDemo = new CloneDemo();
cloneDemo.name = "辅助";
cloneDemo.cloneChildDemo = new CloneChildDemo();
CloneDemo cloneDemo1 = (CloneDemo) cloneDemo.clone();
System.out.println(cloneDemo.name);
System.out.println(cloneDemo1.name);
System.out.println(cloneDemo.hashCode());
System.out.println(cloneDemo1.hashCode());
System.out.println(cloneDemo == cloneDemo1);
System.out.println("--------------");
System.out.println(cloneDemo.cloneChildDemo == cloneDemo1.cloneChildDemo);
System.out.println(cloneDemo.cloneChildDemo.hashCode());
System.out.println(cloneDemo1.cloneChildDemo.hashCode());
}
/*
辅助
辅助
1349393271
1338668845
false
--------------
false
159413332
1028214719
*/
(3)使用建议:
针对浅拷贝,我们不应该实现Cloneable接口,而应该使用拷贝构造器或者拷贝工厂。
而对于深拷贝,还是推荐使用 clone() 方法,这样只需要每个类自己维护自己即可,而无需关心内部其他的对象中,其他的参数是否也需要 clone() 。
三、探讨hashcode与equals的设计使用:
我们先来看来两段代码:
(1)不重写hashcode与equals:
public class Person {
private String name;
public Person(String name) {
this.name = name;
}
public static void main(String[]args){
HashMap hm = new HashMap();
Person person = new Person ("辅助");
Person person1 = new Person ("辅助");
hm.put(person , "a");
hm.put(person1 , "b");
System.out.println(hm.size());
}
}
//输出size为2
}
(2)重写hashcode与equals:
public class Person {
private String name;
public Person(String name) {
this.name = name;
}
@Override
public int hashCode(){
return Objects.hash(name);
}
@Override
public boolean equals(Object obj) {
if(this == obj){
return true;
}
if(obj instanceof Person){
Person person=(Person)obj;
if(this.name.equals(person.name)){
return true;
}
}
return false;
}
public static void main(String[]args){
HashMap hm = new HashMap();
Person person = new Person ("辅助");
Person person1 = new Person ("辅助");
hm.put(person , "a");
hm.put(person1 , "b");
System.out.println(hm.size());
}
}
//输出是1
从上面可以看出,我们重写hashCode与equals方法时,注意自己的设计,取得不好只是会影响容器的效率以及影响到你插入重复key。
(3)为什么会这样?
首先,我们阅读源码知道HashMap等相关容器 的设计就是对象的hashCode进行比较,去重设计。
然后我们思考一下,我们已经可以通过equals方法进行比较了,甚至原始的Objuct可以比较内存地址,那我们为什么还需要一个hashCode的去进行重写?
试想一下,如果集合中已经存在一万条数据或者更多的数据,如果采用equals方法去逐一比较,效率必然是一个问题。此时hashCode方法的作用就体现出来了,当集合要添加新的对象时,先调用这个对象的hashCode方法,得到对应的hashcode值。然后容器基于这个的hashCode值进行设计运作。这样的话,实际调用equals方法的次数就大大降低了。
所以就两点原因:
1)hashCode()方法存在的主要目的就是提高效率。
2)在集合中判断两个对象相等的条件,其实无论是往集合中存数据,还是从集合中取数据,包括如果控制唯一性等,都是用这个条件判断的。
结语
好了,深入Java基础(一)--Object类分析讲完了。是自己大四开始回归博客世界的第一篇,继续总结分享,欢迎各位前来交流。