Java:Object类详解

首先,我们都知道Java所有的类都继承自超类【Object】,也就是说所有的类都是Object的子类,这是一个隐式的,我们并不需要特别的指出。

我们定义一个类A。

class A {
	public A() {
		super();
	}
}

代码并不难懂,就是写了类A的构造函数。但是这里需要注意的是super关键词,调用的是父类的构造函数,我们可以跟进一下,就会跳到Object类去了。也就是说类A虽然没有明确的写出继承Object,但是Java还是让其继承了Object类。

Object类是有很多好处的,在本文中,我会慢慢讲述。
我们首先看看Object类主要的方法

protected Object clone()创建并返回此对象的一个副本。
boolean equals(Object obj)指示其他某个对象是否与此对象“相等”。
protected void finalize()当垃圾回收器确定不存在对该对象的更多引用时,由对象的垃圾回收器调用此方法。
Class<?> getClass()返回此 Object 的运行时类。
int hashCode()返回该对象的哈希码值。
String toString()返回该对象的字符串表示。

当然还有一些wait()、waitAll()等方法,与线程有关,就不提出来了。

1、我们先看看clone()方法:
首先clone()方法,Object的子类是不可直接用的,所以我们需要重写。请注意重写与重载的区别。重写是重写父类的方法重载是写多个方法名相同,参数类型或者参数个数或者返回类型不同的方法。
clone()方法就是用于深拷贝的。
我们首先来看看浅拷贝与深拷贝的区别。
深拷贝:复制出两个内容相同,但是引用地址完全不同的两个对象。
浅拷贝:创建了一个与一开始对象引用地址完全相同的两个对象。

浅拷贝典型的例子:

public class Main {
	public static void main(String[] args) {
		A a = new A("Aiden");
		A b = a;
		System.out.println(a);
		System.out.println(b);
		System.out.println(a.name.hashCode());
		System.out.println(b.name.hashCode());
	}
}

class A {
	String name;
	public A(String name) {
		this.name = name;
	}
}

我们看看输出结果:

test.A@2a139a55
test.A@2a139a55
63256261
63256261

可以看出对象a和对象b输出的内容是一样的。@前面表示的是对象映射的类,@后面表示的是对象的哈希地址。而name的哈希码也是相同的。

在更多的时候,我们需要深拷贝,即拷贝出两个内容相同、但引用地址不同的对象。我们就可以重写clone()方法去实现了。
下面是简单的实现。

public class Main {
	public static void main(String[] args) {
		A a = new A("Aiden");
		A b = a.clone();
		System.out.println(a);
		System.out.println(b);
		System.out.println(a.name.hashCode());
		System.out.println(b.name.hashCode());
	}
}

class A {
	String name;
	public A(String name) {
		this.name = name;
	}
	
	@Override
	protected A clone() {
		String name = new String(this.name);
		return new A(name);
	}
}

我们来看看输出的结果:

test.A@2a139a55
test.A@15db9742
63256261
63256261

我们看看,两个对象的引用确实不同了吧。可是两个对象的内容name初始的哈希码怎么还是一样的呢?好神奇。这是因为String是常量,所有的内容存储于常量池中,每次的赋值都会去常量池中找,如果找到了就将内容匹配的地址赋值给name,如果找不到,就新建一块内存,用于存储想要赋值的内容。

2、boolean equals(Object obj)方法
这个方法是用于比较两个对象是否相同的。**相同的定义包括了它们所属的类相同以及哈希码相同。**我们可以跟进Object类中的equals()方法。

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

如果不重写equals()方法的话,equals就是 == 符号的作用。

A a = new A("Aiden");
A b = new A("Aiden");
System.out.println(a.equals(b));
		
b = a;
System.out.println(a.equals(b));

输出结果:

false
true

但是,我们经常看到String对象如果要比较内容的话,使用的是equals()方法,也就是说String类的实现肯定是重写了equals()方法的。我们可以看看源码:

public boolean equals(Object anObject) {
    if (this == anObject) { // 如果两个对象的引用相同,则返回true
        return true;
    }
    if (anObject instanceof String) { // 如果anObject是String对象的
        String anotherString = (String)anObject;
        int n = value.length;
        // 比较每个字符串是否相同
        if (n == anotherString.value.length) {
            char v1[] = value;
            char v2[] = anotherString.value;
            int i = 0;
            while (n-- != 0) {
                if (v1[i] != v2[i])
                    return false;
                i++;
            }
            return true;
        }
    }
    return false;
}

源码还是很简单的,注释也写了,就不多说了。

3、protected void finalize()
该方法是用于回收”垃圾“的。当垃圾回收器确定不存在对该对象的更多引用时,由对象的垃圾回收器调用此方法。详情可以参考博文:点击参考

4、int hashCode()方法
该方法返回该对象的哈希码。关于哈希码的定义,官方是这么说的

hashcode方法返回该对象的哈希码值。支持该方法是为哈希表提供一些优点,例如,java.util.Hashtable 提供的哈希表。   
  
hashCode 的常规协定是:   
在 Java 应用程序执行期间,在同一对象上多次调用 hashCode 方法时,必须一致地返回相同的整数,前提是对象上 equals 比较中所用的信息没有被修改。从某一应用程序的一次执行到同一应用程序的另一次执行,该整数无需保持一致。   
如果根据 equals(Object) 方法,两个对象是相等的,那么在两个对象中的每个对象上调用 hashCode 方法都必须生成相同的整数结果。   
以下情况不 是必需的:如果根据 equals(java.lang.Object) 方法,两个对象不相等,那么在两个对象中的任一对象上调用 hashCode 方法必定会生成不同的整数结果。但是,程序员应该知道,为不相等的对象生成不同整数结果可以提高哈希表的性能。   
实际上,由 Object 类定义的 hashCode 方法确实会针对不同的对象返回不同的整数。(这一般是通过将该对象的内部地址转换成一个整数来实现的,但是 JavaTM 编程语言不需要这种实现技巧。)   
  
当equals方法被重写时,通常有必要重写 hashCode 方法,以维护 hashCode 方法的常规协定,该协定声明相等对象必须具有相等的哈希码。

官方定义给出我们对象的哈希码最好是不一致的。这样在equals()方法才能不同。
我们先看看如果给出相同哈希码的两个对象做比较时,会出现怎样的情况。我们就重写了hashCode()方法、equals()方法。

import java.util.HashSet;
import java.util.Set;

public class Main {
	public static void main(String[] args) {
		A a = new A("Aiden");
		A b = new A("Aiden");
		System.out.println(a == b);
		System.out.println(a.equals(b));
		
		Set<A> set = new HashSet<>();
		set.add(a);
		set.add(b);
		System.out.println(set.size());
	}
}

class A {
	String name;
	public A(String name) {
		this.name = name;
	}

	@Override
	public int hashCode() {
		return 0;
	}
	
	@Override
	public boolean equals(Object object) {
		if (object == null) 
			return false;
		if(object == this)
			return true;
		if (object instanceof A == false)
			return false;
		A b = (A)object;
		if(name.equals(b.name))
			return true;
		return false;
	}
}

我们用Set对象去检测两个对象是否哈希吗是否相同。我们看看具体的输出。

false
true
[test.A@0]

第一行输出false,理所当然,==号是比较两个对象的引用。
第二行输出true,是因为重写了equals()方法,比较的是两个对象的name是否相同。
第三行输出却只有一个内容,也就是说HashSet在add的时候,是比较两个对象的哈希码的,如果哈希码已经存在于Set集合中,就不加入了。如果不存在Set集合中,就加入。

看来哈希码,在HashMap、HashSet、Hashtable中有广泛的应用啊。可是怎样才能实现多个对象,哈希码不相同呢?
这里给出了通用的重写hashCode()的方案:

  1. 初始化一个整形变量,为此变量赋予一个非零的常数值,比如int result = 17;
  2. 选取equals方法中用于比较的所有域,然后针对每个域的属性进行计算:
    (1) 如果是boolean值,则计算f ? 1:0
    (2) 如果是byte\char\short\int,则计算(int)f
    (3) 如果是long值,则计算(int)(f ^ (f >>> 32))
    (4) 如果是float值,则计算Float.floatToIntBits(f)
    (5) 如果是double值,则计算Double.doubleToLongBits(f),然后返回的结果是long,再用规则(3)去处理long,得到int
    (6) 如果是对象应用,如果equals方法中采取递归调用的比较方式,那么hashCode中同样采取递归调用hashCode的方式。  否则需要为这个域计算一个范式,比如当这个域的值为null的时候,那么hashCode 值为0
    (7) 如果是数组,那么需要为每个元素当做单独的域来处理。如果你使用的是1.5及以上版本的JDK,那么没必要自己去    重新遍历一遍数组,java.util.Arrays.hashCode方法包含了8种基本类型数组和引用数组的hashCode计算,算法同上,
public static int hashCode(long a[]) {  
    if (a == null)  
        return 0;  
  
    int result = 1;  
    for (long element : a) {  
        int elementHash = (int)(element ^ (element >>> 32));  
        result = 31 * result + elementHash;  
    }  
  
    return result;  
}  

5、String toString()返回该对象的字符串表示。
我们可以重写这个方法,也可以重写。如果不重写,一般来说就会返回”className@hashCode“格式的字符串。也就是返回类名+”@“+哈希码。
其实我们在System.out.println(a);的时候就默认调用了a.toString()方法,所以才会输出刚刚的格式。

猜你喜欢

转载自blog.csdn.net/new_Aiden/article/details/50997217