static
int
hash
(
Object
.
..
objects
)
返回一个散列码
,
由提供的所有对象的散列码组合而得到
。
•
static
int
hashCode
(
Object
a
)
如果
a
为
null
返回
0
,
否则返回
a
.
hashCode
(
)
0
Java的基础非常重要,在面试中也会被经常问到,但是本人之前却总是去关注一些框架的技术,反而忽视了基础的重要性,因此读一下Java核心卷一并把自己学到的记录在此,希望可以把Java的基础打牢固。
Object: 所有类的超类
Object类概述
Object
类是
Java
中所有类的始祖
,
在
Java
中每个类都是由它扩展而来的
。
但是并不需
要这样写
: 不需要显示继承
public class Employee extends Object
如果没有明确地指出超类
,
Object
就被认为是这个类的超类
。
由于在
Java
中
,
每个类都
是由
Object
类扩展而来的
,
所以
,
熟悉这个类提供的所有服务十分重要
可以使用
Object
类型的变量引用任何类型的对象
:
Object obj = new EmployeeC'Harry Hacker", 35000);
当然
,
Object
类型的变量只能用于作为各种值的通用持有者
。
要想对其中的内容进行具体的
操作
,
还需要清楚对象的原始类型
,
并进行相应的类型转换
:
Employee e = (Employee) obj ;
所有的数组类塱
,
不管是对象数组还是基本类型的数组都扩展了
Object
类
。
Employee[] staff = new Employee[10];
obj = staff; // OK
obj = new int[10]; // OK
Object类中的方法
(
在
Object
中有几个只在处理线程 时才会被调用的方法)这里不提及,以后写到线程会写到
equals 方法
public boolean equals(Object obj) {
return (this == obj);
}
Object
类中的
equals
方法用于检测一个对象是否等于另外一个对象
。
在
Object
类中
,
这
个方法将判断两个对象是否具有相同的引用
。
如果两个对象具有相同的引用
,
它们一定是相
等的
。
从这点上看
,
将其作为默认操作也是合乎情理的
。
然而
,
对于多数类来说
,
这种判断
并没有什么意义
。
例如
,
采用这种方式比较两个
PrintStream
对象是否相等就完全没有意义
。
然而
,
经常需要检测两个对象状态的相等性
,
如果两个对象的状态相等
,
就认为这两个对象
是相等的
。
1.equals()的所属以及内部原理(即Object中equals方法的实现原理)
说起equals方法,我们都知道是超类Object中的一个基本方法,用于检测一个对象是否与另外一个对象相等。而在Object类中这个方法实际上是判断两个对象是否具有相同的引用,如果有,它们就一定相等
实际上我们知道所有的对象都拥有标识(内存地址)和状态(数据),同时“==”比较两个对象的的内存地址,所以说 Object 的 equals() 方法是比较两个对象的内存地址是否相等、
2.equals()与‘==’的区别
或许这是我们面试时更容易碰到的问题”equals方法与‘==’运算符有什么区别?“,并且常常我们都会胸有成竹地回答:“equals比较的是对象的内容,而‘==’比较的是对象的地址。”。但是从前面我们可以知道equals方法在Object中的实现也是间接使用了‘==’运算符进行比较的,所以从严格意义上来说,我们前面的回答并不完全正确。
public static void main(String[] args) {
User u = new User("张三");
User u1 = new User("张三");
System.out.println(u.equals(u1));
System.out.println(u == u1);
}
都是false,对于‘==’运算符比较两个User对象,返回了false,这点我们很容易明白,毕竟它们比较的是内存地址,而u与u1是两个不同的对象,所以内存地址自然也不一样。现在的问题是,我们希望name相同的情况下就认为这两个相等,但是运行的结果是尽管name相同,但equals的结果却反回了false。当然对于equals返回了false,我们也是心知肚明的,因为equal来自Object超类,访问修饰符为public,而我们并没有重写equal方法,故调用的必然是Object超类的原始方equals方法,根据前面分析我们也知道该原始equal方法内部实现使用的是’==’运算符,所以返回了false。因此为了达到我们的期望值,我们必须重写Userr的equal方法,让其比较的是对象的批次(即对象的内容),而不是比较内存地址,于是修改如下:
@Override
public boolean equals(Object obj) {
if (obj instanceof User) {
User u = (User) obj;
// 判断是否内容
return name == u.name;
}
return false;
}
instanceof 严格来说是Java中的一个双目运算符,用来测试一个对象是否为一个类的实例,用法为:boolean
result = obj
instanceof
Class
其中 obj 为一个对象,Class 表示一个类或者一个接口,当 obj 为 Class 的对象,或者是其直接或间接子类,或者是其接口的实现类,结果result 都返回 true,否则返回false。
注意:编译器会检查 obj 是否能转换成右边的class类型,如果不能转换则直接报错,如果不能确定类型,则通过编译,具体看运行时定。
重新运行
因为前面的面试题我们应该这样回答更佳
总结:默认情况下也就是从超类Object继承而来的equals方法与‘==’是完全等价的,比较的都是对象的内存地址,但我们可以重写equals方法,使其按照我们的需求的方式进行比较,如String类重写了equals方法,使其比较的是字符的序列,而不再是内存地址。
3.equals()的重写规则
前面我们已经知道如何去重写equals方法来实现我们自己的需求了,但是我们在重写equals方法时,还是需要注意如下几点规则的。Java 语言规范要求 equals 方法具有下面的特性:
-
自反性。对于任何非null的引用值x,x.equals(x)应返回true。
-
对称性。对于任何非null的引用值x与y,当且仅当:y.equals(x)返回true时,x.equals(y)才返回true。
-
传递性。对于任何非null的引用值x、y与z,如果y.equals(x)返回true,y.equals(z)返回true,那么x.equals(z)也应返回true。
-
一致性。对于任何非null的引用值x与y,假设对象上equals比较中的信息没有被修改,则多次调用x.equals(y)始终返回true或者始终返回false。
-
对于任何非空引用值x,x.equal(null)应返回false。
当然在通常情况下,如果只是进行同一个类两个对象的相等比较,一般都可以满足以上5点要求
但是如果是子类与父类混合比较,那么情况就不太简单了
在前面的例子中
,
如果发现类不匹配
,
equals
方法就返冋 false
:
但是
,
许多程序员
却喜欢使用 instanceof
进行检测:
if (
KotherObject
instanceof
Employee
)
)
return
false;
这样做不但没有解决 otherObject
是子类的情况
,
并且还有可能会招致一些麻烦
。这就是建议
不要使用这种处理方式的原因所在
然而
,
就对称性来说
,
当参数不属于同一个类的时候需要仔细地思考一下
。
请看下面这
个调用
:
e
.
equals
(
in
)
这里的 e
是一个
Employee
对象
,
m
是一个
Manager
对象
,
并且两个对象具有相同的姓名、
薪水和雇佣日期。
如果在
Employee
.
equals
中用
instanceof
进行检测
,
则返回
true
,
. 然而这意
味着反过来调用:
m .equals
(
e)
也需要返回 true
、
对称性不允许这个方法调用返回
false
,
或者抛出异常:
.重写equals()中getClass与instanceof的区别
虽然前面我们都在使用instanceof(当然前面我们是根据需求(批次相同即相等)而使用instanceof的),但是在重写equals() 方法时,一般都是推荐使用 getClass 来进行类型判断(除非所有的子类有统一的语义才使用instanceof),不是使用 instanceof。我们都知道 instanceof 的作用是判断其左边对象是否为其右边类的实例,返回 boolean 类型的数据。可以用来判断继承中的子类的实例是否为父类的实现。
下面给出编写一个完美的 equals 方法的建议:
1
)
显式参数命名为
otherObject
,
稍后需要将它转换成另一个叫做
other
的变量
。
2
)
检测
this
与
otherObject
是否引用同一个对象
:
if
(
this
=
otherObject
)
return
true
;
这条语句只是一个优化
。
实际上
,
这是一种经常采用的形式
。
因为计算这个等式要比一
个一个地比较类中的域所付出的代价小得多
。
3
)
检测
otherObject
是否为
null
,
如 果 为
null
,
返 回
false
。
这项检测是很必要的
。
if
(
otherObject
=
null
)
return
false
;
4
)
比较
this
与
otherObject
是否属于同一个类
。
如果
equals
的语义在每个子类中有所改
变
,
就使用
getClass
检测
:
if
(
getClass
(
)
!
=
otherObject
.
getCIass()
)
return
false
;
如果所有的子类都拥有统一的语义,就使用 instanceof 检测:
if (!(otherObject instanceof ClassName)) return false;
5
)
将
otherObject
转换为相应的类类型变量
:
ClassName
other
=
(
ClassName
)
otherObject
6
)
现在开始对所有需要比较的域进行比较了
。
使用
=
比较基本类型域
,
使用
equals
比
较对象域
。
如果所有的域都匹配
,
就返回
true
;
否 则 返 回
false
。
return
fieldl ==
other.field &&
Objects.equa1s
(
fie1d
2
,
other
.
field
2
)
如果在子类中重新定义
equals
,
就要在其中包含调用
super
.
equals
(
other
。
hashCode 方法
散列码
(
hash
code
)
是由对象导出的一个整型值
。
散列码是没有规律的
。
如果
x
和
y
是
两个不同的对象
,
x
.
hashCode
(
)
与
y
.
hashCode
(
)
基本上不会相同
。
在表
5
-
1
中列出
T
几个通
过调用
String
类的
hashCode
方法得到的散列码
。
由于
hashCode
方法定义在
Object
类中
,
因此每个对象都有一个默认的散列码
,
其值为
对象的存储地址
。
来看下面这个例子
。
表
5
-
2
列出了结果
请注意
,
字符串
s
与
t
拥有相同的散列码
,
这是因为字符串的散列码是由内容导出
的
。
而字符串缓冲
sb
与
tb
却有着不同的散列码
,
这是因为在
StringBuffer
类中没有定义
hashCode
方法
,
它的散列码是由
Object
类的默认
hashCode
方法导出的对象存储地址
。
Equals
与
hashCode
的定义必须一致
:
如果
x
.
equals
(
y
)
返回
true
,
那么
x
.
hashCode
(
)
就必
须与
y
.
hashCode
(
)
具有相同的值
。
例如
,
如果用定义的
Employee
.
equals
比较雇员的
ID
,
那
么
hashCode
方法就需要散列
ID
,
而不是雇员的姓名或存储地址
。
•
int
hashCode
(
)
返回对象的散列码
。
散列码可以是任意的整数
,
包括正数或负数
。
两个相等的对象要
求返回相等的散列码
static
int
hash
(
Object
.
..
objects
)
返回一个散列码
,
由提供的所有对象的散列码组合而得到
。
•
static
int
hashCode
(
Object
a
)
如果
a
为
null
返回
0
,
否则返回
a
.
hashCode
(
)
static
int
hashCode
(
(
int
11
ong
|
short
|
byte
|
double
|
f
1
oat
|
char
|
boolean
) value
)
8
返回给定值的散列码
。
toString 方法
在
Object
中还有一个重要的方法
,
就是
toString
方法
,
它用于返回表示对象值的字符
串
。
下面是一个典型的例子
。
Point
类的
toString
方法将返回下面这样的字符串
:
java
.
awt
.
Point
[
x
=
10
,y=
20
]
绝大多数
(
但不是全部
)
的
toString
方法都遵循这样的格式
:
类的名字
,随后是一对方括
号括起来的域值
。
下面是
Employee
类中的
toString
方法的实现
:
实际上
,
还可以设计得更好一些
。
最好通过调用
getClaSS
( )
.
getName
(
)
获得类名的字符
串
,
而不要将类名硬加到
toString
方法中
。
toString
方法也可以供子类调用
。
当然
,
设计子类的程序员也应该定义自己的
toString
方法
,
并将子类域的描述添加进去。 如果超类使用了 getClass
( )
.
getName
(
)
,
那么子类只要调用
super
.
toString
(
)
就可以了
。
例如, 下面是 Manager
类中的
toString
方法
:
现在
,
Manager
对象将打印输出如下所示的内容
随处可见
toString
方法的主要原因是
:
只要对象与一个字符串通过操作符
“
+
”
连接起
来
,
Java
编译就会自动地调用
toString
方法
,
以便获得这个对象的字符串描述
。
例如
,
在调用
x
.
toString
(
)
的地方可以用
"
"
+
x
替代
。
这条语句将一个空串与
x
的字符串
表示相连接
。
这里的
x
就是
x
.
toString
(
。
)
与
toString
不同的是
,
如果
x
是基本类型
,
这
条语句照样能够执行
。
Object 类定义了 toString 方法, 用来打印输出对象所属的类名和散列码。例如, 调用
System
.
out.println
(
System.out) 将输出下列内容:java
.
io
.
Pri
ntStream
@
2
f
6684
之所以得到这样的结果是因为
PrintStream
类的设计者没有覆盖
toString
方法
。