走近Java类的祖先-Object

走近Java类的祖先-Object

从接触Java语言开始,就知道了Object类是所有Java类的祖先。笔主也有去看过Object类的源码,但一直没有把对Object类的学习和理解形成文章记录下来,今天借这篇文档重新学习下Object类。
查看JDK中Object类的源码,类的注释如下:

/**
 * Class {@code Object} is the root of the class hierarchy.
 * Every class has {@code Object} as a superclass. All objects,
 * including arrays, implement the methods of this class.
 *
 * @author  unascribed
 * @see     java.lang.Class
 * @since   JDK1.0
 */

这段话翻译过来的意思是:在类的层次体系中,Object是根。每一个类都把Object作为自己的父类。所有的类,包括数组和重写Object类方法的类。
验证这句话的正确性,只需要如下代码:

public class Parent {

	public static void main(String[] args) {
		Object parent = new Parent();
		System.out.println(parent.toString());
		Object array = new int [5];
		System.out.println(array.toString());
	}
}

我们都知道Java语言,可以把父类的引用指向子类的具体对象。所以,上边这段代码使用Object的引用分别指向Parent类的一个对象和一个长度为5的int数组。同时,分别打印出toString()的结果。代码运行结果如下:

com.zhaoheng.Parent@7852e922
[I@4e25154f

运行结果第一行描述了Parent类的对象的信息,第二行描述了int数组的信息。
通过这段代码,我们论证了每一个类(包括数组)都把Object类作为父类。
接下来,我们通过源码重新认识下Object类,Object源码如下:

public class Object {

    private static native void registerNatives();
   
    static {
        registerNatives();
    }
   
    public final native Class<?> getClass();
 
    public native int hashCode();

    public boolean equals(Object obj) {
        return (this == obj);
    }
 
    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();

    public final native void wait(long timeout) throws InterruptedException;
  
    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 > 0) {
            timeout++;
        }

        wait(timeout);
    }
 
    public final void wait() throws InterruptedException {
        wait(0);
    }
    
    protected void finalize() throws Throwable { }
}

下面就对Object类中的方法逐一介绍:
1、registerNatives(),通过源码可以看出私有静态无返回值的方法,同时这个方法还使用了native关键字修饰,说明这个方法是一个本地方法。这个方法是为了JVM找到本地方法,本地方法也都是通过C语言实现的。同时,registerNatives()被放在了静态代码块中,这个方法只会在Object类第一次加载时执行一次。
2、getClass(),返回运行时这个对象的Class对象。这个方法也是用native关键字修饰的,说明这个方法也是一个本地方法。
3、hashCode(),返回对象的hashcode值。这个方法是给使用哈希表的数据结构提供的,例如HashMap。此方法可以被子类重写。
4、equals(Object obj),比较传入的对象与当前对象是否是同一对象,Object类默认比较的是两个对象在内存中的虚拟地址。此方法可以被子类重写。
5、clone(),创建并返回对象的一个副本,对于重写此方法的类要求实现Cloneable接口。此方法可以被子类重写。
6、toString(),返回当前对象的字符串表示形式,默认是:getClass().getName() + “@” + Integer.toHexString(hashCode())。此方法可以被子类重写。
7、notify(),唤醒正在等待对象监视器的某个线程。注意,此方法必须用在synchronized修饰的同步环境中。
8、notifyAll(),唤醒正在等待对象监视器的所有线程。注意,此方法必须用在synchronized修饰的同步环境中。
9、wait(long timeout) ,使当前线程等待,直到某个线程调用此对象的notify()方法或notifyAll()方法,或某些线程中断当前线程,或等待指定的时间。注意,此方法必须用在synchronized修饰的同步环境中。
10、wait(),使当前线程等待,直到某个线程调用此对象的notify()方法或notifyAll()方法,或某些线程中断当前线程。注意,此方法必须用在synchronized修饰的同步环境中。
11、 finalize(),当垃圾回收器确定不再有此对象的引用时,调用垃圾回收方法,该方法只会被JVM调用一次。此方法可以被子类重写。

当一个Java程序员去面试时,关于Object类问的最多的问题如下:
1、为什么说equals相等,hashcode相等?
2、为什么说hashcode相等,equals不一定相等?
3、为什么重写equals一定要重写hashcode?

为什么说equals相等,hashcode相等?

我们知道如果不重写equals(Object obj),Object类中的equals(Object obj)默认使用“==”比较两个对象的虚拟地址是否相等,如果虚拟地址相等,说明是同一个对象。hashcode()方法返回了对象的hashcode值,既然是同一个对象,hashcode值自然也要相等。所以说equals相等,hashcode相等。
试想,如果同一个对象的hashcode值可以不相等,那么底层使用哈希表的数据结构如HashSet是不是就无法保证内部元素唯的唯一性了。

为什么说hashcode相等,equals不一定相等?
这里我们使用一段简单的代码来验证这个问题,代码如下:
public class HashCodeTest {

	public static void main(String[] args) {
		Users1 u1 = new Users1();
		u1.setId(1);
		u1.setName("张三");
		
		Users2 u2 = new Users2();
		u2.setId(1);
		u2.setName("张三");
		
		System.out.println("u1 equals u2: " + u1.equals(u2));  
		System.out.println("u1 hashcode: " + u1.hashCode()); 
		System.out.println("u2 hashcode: " + u2.hashCode()); 
	}
}
class Users1 {
	
	private int id;
	
	private String name;

	public int getId() {
		return id;
	}

	public void setId(int id) {
		this.id = id;
	}

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	@Override
	public int hashCode() {
		final int prime = 31;
		int result = 1;
		result = prime * result + id;
		result = prime * result + ((name == null) ? 0 : name.hashCode());
		return result;
	}
	
}

class Users2 {
	
	private int id;
	
	private String name;

	public int getId() {
		return id;
	}

	public void setId(int id) {
		this.id = id;
	}

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	@Override
	public int hashCode() {
		final int prime = 31;
		int result = 1;
		result = prime * result + id;
		result = prime * result + ((name == null) ? 0 : name.hashCode());
		return result;
	}
	
}

分别定义两个类User1和Users2,使这两个类有相同的字段id和name。两个类都重写了hashcode(),计算hashcode值都使用了id和name字段。

创建Users1类的对象u1,设置u1的id为1,name为张三。 创建Users2类的对象u2,设置u2的id为1,name为张三。

程序运行结果如下:

u1 equals u2: false
u1 hashcode: 775881
u2 hashcode: 775881

由此结果可以验证,hashcode相等,equals不相等。

为什么重写equals一定要重写hashcode?
我们继续使用代码来验证这个问题。代码如下:
import java.util.HashMap;
import java.util.Map;

public class HashCodeTest {

	public static void main(String[] args) {
		Users1 u1 = new Users1();
		u1.setId(1);
		u1.setName("张三");
		Users1 u2 = new Users1();
		u2.setId(1);
		u2.setName("张三");
		System.out.println("u1 equals u2: " + u1.equals(u2));
		System.out.println("u1 hashcode: " + u1.hashCode());
		System.out.println("u2 hashcode: " + u2.hashCode());
		Map<Users1, String> map = new HashMap<Users1, String>();
		map.put(u1, "u1");
		System.out.println(map.get(u2));
	}
}
class Users1 {
	
	private int id;
	
	private String name;

	public int getId() {
		return id;
	}

	public void setId(int id) {
		this.id = id;
	}

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	@Override
	public boolean equals(Object obj) {
		if (this == obj)
			return true;
		if (obj == null)
			return false;
		if (getClass() != obj.getClass())
			return false;
		Users1 other = (Users1) obj;
		if (id != other.id)
			return false;
		if (name == null) {
			if (other.name != null)
				return false;
		} else if (!name.equals(other.name))
			return false;
		return true;
	}

}

我们重写了Users1的equals(Object obj),但没有重写hashcode()方法。

创建Users1类的对象u1,设置u1的id为1,name为张三。 创建Users1类的对象u2,设置u2的id为1,name为张三。

创建一个Map,在Map中设置一个key为u1,value为“u1”的元素。然后,使用u2作为key从Map中获取value。

程序运行结果如下:

u1 equals u2: true
u1 hashcode: 2018699554
u2 hashcode: 1311053135
null

从运行结果可以看出,重写equals(Object obj)后,u1和u2是相等的对象,但是hashcode值不一样。

1.这就不符合我们刚才讨论的第一个问题,equals相等,hashcode也相等。
2.u1和u2是相等的对象,使用u1作为key设置的value,使用u2作为key从Map中获取的结果是null。

由此结果可以验证,重写equals一定要重写hashcode。否则,底层使用哈希表的数据结构就无法保证内部元素的唯一性了。

继续修改上面的程序,重写equals(Object obj)后,同时重写hashcode()。代码如下:

mport java.util.HashMap;
import java.util.Map;

public class HashCodeTest {

	public static void main(String[] args) {
		Users1 u1 = new Users1();
		u1.setId(1);
		u1.setName("张三");
		Users1 u2 = new Users1();
		u2.setId(1);
		u2.setName("张三");
		System.out.println("u1 equals u2: " + u1.equals(u2));
		System.out.println("u1 hashcode: " + u1.hashCode());
		System.out.println("u2 hashcode: " + u2.hashCode());
		Map<Users1, String> map = new HashMap<Users1, String>();
		map.put(u1, "u1");
		System.out.println(map.get(u2));
	}
}
class Users1 {
	
	private int id;
	
	private String name;

	public int getId() {
		return id;
	}

	public void setId(int id) {
		this.id = id;
	}

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	@Override
	public int hashCode() {
		final int prime = 31;
		int result = 1;
		result = prime * result + id;
		result = prime * result + ((name == null) ? 0 : name.hashCode());
		return result;
	}

	@Override
	public boolean equals(Object obj) {
		if (this == obj)
			return true;
		if (obj == null)
			return false;
		if (getClass() != obj.getClass())
			return false;
		Users1 other = (Users1) obj;
		if (id != other.id)
			return false;
		if (name == null) {
			if (other.name != null)
				return false;
		} else if (!name.equals(other.name))
			return false;
		return true;
	}

}

程序运行结果如下:

u1 equals u2: true
u1 hashcode: 775881
u2 hashcode: 775881
u1

从运行结果可以看出,同时重写equals(Object obj)和hashcode(),u1和u2不仅是相等的对象,hashcode值也一样。使用u1作为key设置的value,使用u2作为key从Map中可以获取到value。

另外需要注意,重写equals(Object obj)和hashcode()时,equals(Object obj)参与比较的字段要和hashcode()参与计算的字段保持一致性。

至此,对Object类的学习完毕。

由于笔主水平有限,笔误或者不当之处还请批评指正。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值