Object类之常用方法
1.public int hashCode()
int
hashCode()
返回该对象的哈希码值。 支持此方法是为了提高哈希表(例如java.util.Hashtable
提供的哈希表)的性能。
hashCode
的常规协定是:
- 在 Java 应用程序执行期间,在对同一对象多次调用 hashCode 方法时,必须一致地返回相同的整数,前提是将对象进行 equals 比较时所用的信息没有被修改。从某一应用程序的一次执行到同一应用程序的另一次执行,该整数无需保持一致。
- 如果根据 equals(Object) 方法,两个对象是相等的,那么对这两个对象中的每个对象调用
hashCode
方法都必须生成相同的整数结果。- 如果根据
equals(java.lang.Object)
方法,两个对象不相等,那么对这两个对象中的任一对象上调用 hashCode 方法不 要求一定生成不同的整数结果。但是,程序员应该意识到,为不相等的对象生成不同整数结果可以提高哈希表的性能。实际上,由 Object 类定义的 hashCode 方法确实会针对不同的对象返回不同的整数。(这一般是通过将该对象的内部地址转换成一个整数来实现的,但是 JavaTM 编程语言不需要这种实现技巧。)
首先我们知道,当我们在JAVA中声明一个对象时,就会在内存的堆中开辟一块存储空间并分配一个内存地址,该内存地址即为该对象的物理地址。
而散列表(Hash table,也叫哈希表),是根据关键码值(Key value)而直接进行访问的数据结构。也就是说,它通过把关键码值映射到表中一个位置来访问记录,以加快查找的速度。这个映射函数叫做散列函数,存放记录的数组叫做散列表。由于在映射时会产生冲突(详情了解数据结构),所以不同的对象可能会映射到数组的同一个位置。所以对于hashCode的常规协定中的第二、三条简单来说:对象相等,返回值相等;对象不相等,返回值不一定不相等。所以理论上来说不能根据该方法的返回值来判断两对象是否相等。
简单来说就是将内存地址通过某种算法得到另外一个具体的数值,即将物理地址映射到人为构建的哈希表中以达到快速查找所需数据的目的。
在JAVA中hashCode()方法即为获取哈希值的方法,下面我们简单了解一下部分类中实现该方法的源码。
(1)Object.class
public native int hashCode();
native说明是一个本地方法,它的实现是根据本地机器相关的。
使用native关键字说明这个方法是原生函数,也就是这个方法是用C/C++语言实现的,并且被编译成了DLL,由java去调用。
简单地讲,一个Native Method就是一个java调用非java代码的接口。这里可以理解为JAVA调用C/C++的底层函数实现在不同平台上对操作系统的访问。由于JAVA是一种跨平台语言,所以在底层实现上就不会像C/C++一样高效,所以有时候就必须凭借其他语言的接口实现对底层的控制,有得必有失,没有语言是完美的。
(2)String.class
private int hash; // hash为整形变量,默认为0
public String(String original) {
this.value = original.value;
this.hash = original.hash;
}//证明了在使用字符串为参数的构造函数时,两个对象的hash相同
public int hashCode() {
int h = hash;
if (h == 0 && value.length > 0) {
char val[] = value;
for (int i = 0; i < value.length; i++) {
h = 31 * h + val[i];
}
hash = h;
}
return h;
}
//首先获取原有hash,如果不为0则证明hash已经被赋值,直接返回原值。
//如果为0且字符串有内容,则证明是新对象,根据算法(h = 31 * h + val[i])得到hash值。
(3)Arrays.class
以整形数组为例(其他数组类似)
public static int hashCode(int a[]) {
if (a == null)
return 0; //如果数组为空,hash为0
int result = 1;
for (int element : a)
result = 31 * result + element; //根据算法(31 * result + element)得到hash值
return result;
}
2.public final Class<?> getclass()
Class<?>
getClass()
返回此Object
的运行时类。返回此
Object
的运行时类。返回的Class
对象是由所表示类的static synchronized
方法锁定的对象。
该方法在学习反射时进行详细剖析。
3.public String toString()
String
toString()
返回该对象的字符串表示。返回该对象的字符串表示。通常,
toString
方法会返回一个“以文本方式表示”此对象的字符串。结果应是一个简明但易于读懂的信息表达式。建议所有子类都重写此方法。
Object
类的toString
方法返回一个字符串,该字符串由类名(对象是该类的一个实例)、at 标记符“@
”和此对象哈希码的无符号十六进制表示组成。换句话说,该方法返回一个字符串,它的值等于:getClass().getName() + '@' + Integer.toHexString(hashCode())
正如规范中所说,该方法的默认实现对于我们来说没有太大意义,一般建议重写该方法。
如果直接打印对象的引用,会默认调用对象的toString()方法。
(1)Object.class(默认实现)
public String toString() {
return getClass().getName() + "@" + Integer.toHexString(hashCode());
}
//类名@hashCode的十六进制表现形式(hashCode()相关资料请看上文)
(2)String.class(重写)
public String toString() {
return this;
}
//本身即为字符串,所以直接返回this
(3)Arrays.class(重写)
public static String toString(long[] a) {
if (a == null)
return "null"; //如果字符串为null,返回"null"
int iMax = a.length - 1;
if (iMax == -1)
return "[]"; //如果是空字符串,返回 "[]"
StringBuilder b = new StringBuilder(); //构建StringBuilder对象,可变长字符串
b.append('['); //拼接字符串
for (int i = 0; ; i++) {
b.append(a[i]);
if (i == iMax)
return b.append(']').toString();
b.append(", ");
} //返回"[元素1, 元素2,......, 元素n]"
}
4.public boolean equals(Object obj)
boolean
equals(Object obj)
指示其他某个对象是否与此对象“相等”。指示其他某个对象是否与此对象“相等”。
equals
方法在非空对象引用上实现相等关系:
- 自反性:对于任何非空引用值
x
,x.equals(x)
都应返回true
。- 对称性:对于任何非空引用值
x
和y
,当且仅当y.equals(x)
返回true
时,x.equals(y)
才应返回true
。- 传递性:对于任何非空引用值
x
、y
和z
,如果x.equals(y)
返回true
,并且y.equals(z)
返回true
,那么x.equals(z)
应返回true
。- 一致性:对于任何非空引用值
x
和y
,多次调用 x.equals(y) 始终返回true
或始终返回false
,前提是对象上equals
比较中所用的信息没有被修改。- 对于任何非空引用值
x
,x.equals(null)
都应返回false
。
Object
类的 equals 方法实现对象上差别可能性最大的相等关系;即,对于任何非空引用值x
和y
,当且仅当x
和y
引用同一个对象时,此方法才返回true
(x == y
具有值true
)。注意:当此方法被重写时,通常有必要重写 hashCode 方法,以维护 hashCode 方法的常规协定,该协定声明相等对象必须具有相等的哈希码。
(1)Object.class
public boolean equals(Object obj) {
return (this == obj);
}
该方法比较的是两个对象的地址值
底层实现就是用==
没有实际意义,一般在开发中主要用来重写
Object为所有类的父类,所以传参时涉及了多态
按照需要可以进行向下转型
(2)String.class
private final char value[]; //value即为底层中该字符串对应的字符数组
public boolean equals(Object anObject) {
if (this == anObject) { //先比较是否是同一对象
return true;
}
if (anObject instanceof 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;
}
String类中既重写了equals方法,又重写了hashCode方法,当两数组内的元素相同时,返回的哈希值相同且equals方法返回true,遵循了规范中所说的注意事项(当此方法被重写时,通常有必要重写 hashCode 方法,以维护 hashCode 方法的常规协定,该协定声明相等对象必须具有相等的哈希码)。一般我们大多数在重写equals方法时,并没有遵守该规范去重写hashCode方法。
(3)Arrays.class
以long[]数组为例
public static boolean equals(long[] a, long[] a2) {
if (a==a2) //先比较是否指向同一对象
return true;
if (a==null || a2==null) //判断是否为空数组
return false;
int length = a.length;
if (a2.length != length) //比较两数组长度
return false;
for (int i=0; i<length; i++) //比较数组内各个元素
if (a[i] != a2[i])
return false;
return true;
}
注意:当比较两数组使用equals方法时,如下使用为错
long[] a = new long[]{1,2,3};
long[] b = new long[]{1,2,3};
System.out.println(a.equals(b)); //false
因为Arrays类中的equals方法定义为:
public static boolean equals(long[] a,long[] a2)
即为静态方法,当如上使用时,调用的为Object类中的方法,比较的是两对象地址,所以返回为false,正确使用如下:
long[] a = new long[]{1,2,3};
long[] b = new long[]{1,2,3};
System.out.println(Arrays.equals(a,b)); //true
(4)==和equals的异同点:
共同点:都可以用来作比较,返回值都是boolean
区别:
1.==是比较运算符,既可以比较基本数据类型,也可以比较引用数据类型。基本数据类型比较的是值,引用数据类型比较的是地址值。
2.equals方法只能用于引用数据类型,没有重写前,比较的是地址值,底层依赖的是==,但是比较地址值没有意义,我们需要重写equals方法来比较属性值。