一、引言
我们知道Java是单继承语言,所有类的最终父节点都是Object(java.lang.Object)类,这一点与C++不同,因为C++既可以单继承也可是多继承。上述的所有类包括数组这些等。
二、分析
1.结构与源码
public class Object {
private static native void registerNatives();
static {
registerNatives();
}
// 获取对象的类型
public final native Class<?> getClass();
// 获取HashCode的方法
public native int hashCode();
// 对象的比较方法
public boolean equals(Object obj) {
return (this == obj);
}
// 对象的克隆的方法
protected native Object clone() throws CloneNotSupportedException;
// 多线程中唤醒单个线程的方法
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);
}
// GC回收前执行的方法
protected void finalize() throws Throwable { }
}
注意:
1.被native修饰的方法,都是由本地实现的,通过JNI调用C或C++操作系统底层。
native修饰的方法是一个原生态方法,方法对应的实现不是在当前文件,而是在用其他语言(如C和C++)实现的文件中。Java语言本身不能对操作系统底层进行访问和操作,但是可以通过JNI接口调用其他语言来实现对底层的访问。
2.关于Object中的具体每一个方法的作用我们将在下面仔细介绍,上面的注释就是帮助大家对其有个初步的认识。
2.分析
2.1 registerNatives()方法
// 这是native的方法
// 我们知道:
// ①native的方法有本地的其他语言或操作系统来实现;
// ②与此同时还要不在此类中装载此类;
// ③由于被private修饰,不能被类的外部调用。所以会在Object类加载时,通过其来注册native方法
// 作用:向Object类注册本地实现的方法。
private static native void registerNatives();
static {
registerNatives();
}
2.2 getClass()方法
// 本地实现的方法
// 作用:返回一个类运行时的类型
public final native Class<?> getClass();
思考:此方法和ClassType.class获取的Class对象有什么区别?
例子:
public class ObjectDemo {
public static void main(String[] args) {
Fu fu = new Zi();
System.out.println(fu.getClass());
System.out.println(Fu.class);
}
static class Fu {}
static class Zi extends Fu {}
}
运行结果如下:
区别:getClass获取的运行时类型;xxx.class获取的是编译时类型
2.3 hashCode()方法
// 作用:返回对象的散列码,是 int 类型的数值
// 尤其是在集合这块体现的
public native int hashCode();
HashCode的三个原则:
- 在程序运行时期间,只要对象的(字段的)变化不会影响equals方法的决策结果,那么,在这个期间,无论调用多少次hashCode,都必须返回同一个散列码。
- 通过equals调用返回true 的2个对象的hashCode一定一样。
- 通过equasl返回false 的2个对象的散列码不需要不同,也就是他们的hashCode方法的返回值允许出现相同的情况。
关于HashCode的更多参考这里:待完善
2.4 equals(Object obj)方法
// 作用:返回两个对象是否相等
public boolean equals(Object obj) {
return (this == obj);
}
看到这里,很自然的联想到 “= =”和equal()的区别是什么。通常,==用于基本类型的值是否相等或比较两个对象类型的引用是否相同。而equals用于比较两个对象是否相同,但是怎么判断这两个对象是否相同,定义是很宽泛的。
从Object类的源码中可以看到:Object中 == 和equals方法是等价的。
所以,如果我们自定义对象,如果不重写equals方法时,则调用的是Object的equals方法。如String中的equals的方法:
public boolean equals(Object anObject) {
if (this == anObject) {
// 如果地址引用相等,则这两个对象一定相等
return true;
}
if (anObject instanceof String) {
String anotherString = (String)anObject;
int n = value.length;
// String的长度相等的情况下
if (n == anotherString.value.length) {
// 逐个字符进行比较,只要有一个不相等,就直接返回false。否则就是true。
char v1[] = value;
char v2[] = anotherString.value;
int i = 0;
while (n-- != 0) {
if (v1[i] != v2[i])
return false;
i++;
}
return true;
}
}
// 如果不是String类型,直接返回false
return false;
}
由此我们看出String重写了equals方法:其比较的是两个字符串的内容。
Java中,关于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
下面我们定义一个测试类:来检测下。
public class ObjectDemo {
public static void main(String[] args) {
Person p1 = new Person("1001", "tom");
Person p2 = new Person("1001", "tom");
System.out.println(p1.equals(p2)); // true
}
static class Person {
private String id;
private String name;
public Person() {}
public Person(String id, String name) {
this.id = id;
this.name = name;
}
public String getId() {
return id;
}
public void setId(String 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;
}
// 为null或不是Person类型,一定不相同
if (obj == null || !(obj instanceof Person)) {
return false;
}
Person p = (Person) obj;
// 只有当id和名字都相等,这两个对象才算相同
if (p.getId().equals(this.id) && p.getName().equals(this.name)) {
return true;
}
return false;
}
}
}
从上面代码可以看出:我们定义Person相等的尺度是地址值相等或id、名字相等才算相同。
这时看起来,我们重写的equals方法很完美啊。但是当我写一个学生类(Student)继承Person类时,也重写了equals方法,错误就出现了。
public static void main(String[] args) {
Person p1 = new Person("1001", "tom");
Student s = new Student("1001", "tom", "909");
System.out.println(p1.equals(s)); // true
System.out.println(s.equals(p1)); // false
}
static class Student extends Person {
private String classRoom;
public Student() {}
public Student(String id, String name, String classRoom) {
super(id, name);
this.classRoom = classRoom;
}
@Override
public boolean equals(Object obj) {
// 地址值相等,一定相同
if (this == obj) {
return true;
}
// 为null或不是Person类型,一定不相同
if (obj == null || !(obj instanceof Student)) {
return false;
}
Student s = (Student) obj;
// 只有当id和名字及classRoom都相等,这两个对象才算相同
if (super.getId().equals(s.getId()) && super.getName().equals(s.getName()) && this.classRoom.equals(s.getClassRoom())) {
return true;
}
return false;
}
public String getClassRoom() {
return classRoom;
}
public void setClassRoom(String classRoom) {
this.classRoom = classRoom;
}
}
运行结果为:
通过打印结果:Person.equals(student)为true,但是Student.equals(person)为false。这违反了equals的对称性。
其实问题出现在instanceof关键字的判断的那句。关于instanceof的用法可以参考这篇文章,总结的很好。
Student是 Person 的子类,person instanceof Student结果当然是false。这违反了对称性原则。
实际上用 instanceof 关键字是做不到对称性的要求的。这里推荐做法是用 getClass()方法取代 instanceof 运算符。getClass() 关键字也是 Object 类中的一个方法,作用是返回一个对象的运行时类,具体如何使用我们将会在下面进行介绍。
public class ObjectDemo {
public static void main(String[] args) {
Person p1 = new Person("1001", "tom");
Student s = new Student("1001", "tom", "909");
System.out.println(p1.equals(s)); // true
System.out.println(s.equals(p1)); // false
}
static class Person {
private String id;
private String name;
public Person() {}
public Person(String id, String name) {
this.id = id;
this.name = name;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public boolean equals(Object obj) {
// 1.地址值相等,一定相同
if (this == obj) {
return true;
}
// 2.为null,一定不相等
if (obj == null) {
return false;
}
// 3.检测是否是一个类,如果不是一个类直接返回false
if (getClass() != obj.getClass()) {
return false;
}
// 4.如果不是Person类型,一定不相同
if ( !(obj instanceof Person)) {
return false;
}
// 5.将obj转为对应的类型
Person p = (Person) obj;
// 6.对对象的属性进行比较,== 基本类型 equals是对象类型
if (p.getId().equals(this.id) && p.getName().equals(this.name)) {
return true;
}
// 6-2.如果是在子类,子类的equals方法要包含父类的equals方法。如在子类Student中
return false;
}
}
static class Student extends Person {
private String classRoom;
public Student() {}
public Student(String id, String name, String classRoom) {
super(id, name);
this.classRoom = classRoom;
}
@Override
public boolean equals(Object obj) {
// 1.地址值相等,一定相同
if (this == obj) {
return true;
}
// 2.为null,一定不相等
if (obj == null) {
return false;
}
// 3.检测是否是一个类,如果不是一个类直接返回false
if (getClass() != obj.getClass()) {
return false;
}
// 4.如果不是Person类型,一定不相同
if ( !(obj instanceof Student)) {
return false;
}
// 5.将obj转为对应的类型
Student s = (Student) obj;
// 6.对对象的属性进行比较,== 基本类型 equals是对象类型
return super.equals(s) && this.classRoom.equals(s.getClassRoom());
// 6-2.如果是在子类,子类的equals方法要包含父类的equals方法。如在子类Student中
}
public String getClassRoom() {
return classRoom;
}
public void setClassRoom(String classRoom) {
this.classRoom = classRoom;
}
}
}
改造后的打印结果均为:
注意 :
- 重写equals方法的时候,都要重写equals方法,以保持其统一性。
- 当然getClass()的这种比较方式不是唯一性的,我们要根据自己业务的情况来定义定义对象是否相等的标准。比如说java中集合类,即使getClass这种方式也不太合适,这种类不适合指定相等的标准。
2.5 clone()方法
/**
* Creates and returns a copy of this object. The precise meaning
* of "copy" may depend on the class of the object. The general
* intent is that, for any object {@code x}, the expression:
* <blockquote>
* <pre>
* x.clone() != x</pre></blockquote>
* will be true, and that the expression:
* <blockquote>
* <pre>
* x.clone().getClass() == x.getClass()</pre></blockquote>
* will be {@code true}, but these are not absolute requirements.
* While it is typically the case that:
* <blockquote>
* <pre>
* x.clone().equals(x)</pre></blockquote>
* will be {@code true}, this is not an absolute requirement.
* <p>
* By convention, the returned object should be obtained by calling
* {@code super.clone}. If a class and all of its superclasses (except
* {@code Object}) obey this convention, it will be the case that
* {@code x.clone().getClass() == x.getClass()}.
* <p>
* By convention, the object returned by this method should be independent
* of this object (which is being cloned). To achieve this independence,
* it may be necessary to modify one or more fields of the object returned
* by {@code super.clone} before returning it. Typically, this means
* copying any mutable objects that comprise the internal "deep structure"
* of the object being cloned and replacing the references to these
* objects with references to the copies. If a class contains only
* primitive fields or references to immutable objects, then it is usually
* the case that no fields in the object returned by {@code super.clone}
* need to be modified.
* <p>
* The method {@code clone} for class {@code Object} performs a
* specific cloning operation. First, if the class of this object does
* not implement the interface {@code Cloneable}, then a
* {@code CloneNotSupportedException} is thrown. Note that all arrays
* are considered to implement the interface {@code Cloneable} and that
* the return type of the {@code clone} method of an array type {@code T[]}
* is {@code T[]} where T is any reference or primitive type.
* Otherwise, this method creates a new instance of the class of this
* object and initializes all its fields with exactly the contents of
* the corresponding fields of this object, as if by assignment; the
* contents of the fields are not themselves cloned. Thus, this method
* performs a "shallow copy" of this object, not a "deep copy" operation.
* <p>
* The class {@code Object} does not itself implement the interface
* {@code Cloneable}, so calling the {@code clone} method on an object
* whose class is {@code Object} will result in throwing an
* exception at run time.
*
* @return a clone of this instance.
* @throws CloneNotSupportedException if the object's class does not
* support the {@code Cloneable} interface. Subclasses
* that override the {@code clone} method can also
* throw this exception to indicate that an instance cannot
* be cloned.
* @see java.lang.Cloneable
*/
protected native Object clone() throws CloneNotSupportedException;
我们可以看出这是一个本地实现的方法。其作用:创建并返回此对象的一个副本。
思考:
- 为什么要有克隆?
- Java中的克隆是怎么样的?
- Object中的clone的方法与Cloneable接口的关系?
- 深度拷贝和浅度拷贝的区别是什么?
关于这些疑问?请看这篇文章写得很好。我就不赘述了。Cloneable接口和Object的clone()方法
2.6 toString()方法
// 可以看出toString打印的是类运行时的类名 加上 对象的hash码十六进制表示。
// 以此来表示其在内存中地址值。
public String toString() {
return getClass().getName() + "@" + Integer.toHexString(hashCode());
}
2.7 notifyAll()、wait(long timeout)、wait()等方法
详细参考线程系列文章多线程系列文章
2.8 finalize()方法
作用:用于垃圾回收,一般由 JVM 自动调用,一般不需要程序员去手动调用该方法。一般会在对象被垃圾回收器清理前调用此方法。
三、结语
- 学习Object类,有利于我们进一步的理解OOP的思想。
- 重点理解equals、clone等方法的使用和意义。