Object类是Java一个比较特殊的类,它是类层次结构的根,位于继承树的顶层,即Java中所有的类从根本上都继承自Object类。它是Java中唯一没有父类的类。 如果一个类没有使用extends关键字明确标识继承另外一个类,那么这个类就默认继承Object类,因此,Object类是Java类层中的最高层类,是所有类的超类。换句话说,Java中任何一个类都是它的子类。由于所有的类都是由Object类衍生出来的,所以Oject类中的方法适用于所有类,因此下面的两种类的定义形式,从本质上讲是完全一样的。
public class Person { //当没有指定父类时,会默认Object类为其父类
//...
}
// 上面的程序等价于:
public class Person extends Object {
//...
}
以下介绍几种Object类中的常用的方法:
返回值类型 | 方法名 | 输入参数 | 抛出异常 | 操作功能 |
构造方法 | Object | 创建Object对象 | ||
Object | clone | CloneNotSupportedException | 创建并返回对象的一个副本 | |
boolean | equals | Object | 指示其他某个对象是否与此对“相等” | |
void | finalize | Throwable | 当垃圾回收器确定不存在对该对象的更多引用时,由对象的垃圾回收器调用此方法 | |
Class | getClass | 返回此Object的运行时类 | ||
int | hashCode | 返回该对象的哈希码值 | ||
void | notify | IlleagalMonitorStateException | 唤醒在此对象监视器上等待的某个线程 | |
void | notifyAll | IlleagalMonitorStateException | 唤醒在此对象监视器上等待的所有线程 | |
void | wait | InterruptedException | 在其他线程调用此对象的notifyAll()方法时,导致当前线程等待 | |
String | toString | 返回该对象的字符串表示 | ||
void | wait | long | InterruptedException | 在其他线程调用此对象的 notify() 或 notifyAll() 方法,或者超过指定的时间量前,导致当前线程等待 |
void | wait | long,int | InterruptedException | 在其他线程调用此对象的 notify() 或 notifyAll() 方法,或者其他某个线程中断当前线程,或者超过指定的时间量前,导致当前线程等待 |
1、取得对象信息:toString()
Object类的toString()方法是在打印对象时被调用,将对象信息变为字符串返回。 默认的toString()方法有一个特点:为了适用于所有的子类,toString()在默认情况下输出对象地址,当然,每一个子类也可以自己进行修改。
例:
package object;
class Person extends Object {
String name = "张三";
int age = 25;
}
public class ObjectDemo1 {
public static void main(String[] args) {
Person p = new Person();
System.out.println(p); // 直接输出对象p
System.out.println(p.toString()); // 调用toString()
}
}
【结果】
对象输出时,会默认调用Object类的toString()方法,将对象信息变为字符串返回。 但是从程序中可以看到,在打印对象p的时候实际上打印出来的是一些无序的字符串,这样的字符串很少有人能看懂是什么意思。
再观察下面的范例,覆写了Object类中的toString()方法:
package object;
class Person extends Object {
String name = "张三";
int age = 25;
public String toString() { // 覆写toString()
return "我是:" + this.name + ",今年:" + this.age + "岁。";
}
}
public class ObjectDemo1 {
public static void main(String[] args) {
Person p = new Person();
System.out.println(p); // 直接输出对象p
System.out.println(p.toString()); // 调用toString()
}
}
【结果】
2、对象相等判断方法:equals()
equals(),此方法用于比较对象是否相等,而且此方法必须被覆写。
为什么要覆写它呢?请看下面的范例,这是一个没有覆写equals()方法的范例。
package object;
class Person {
private String name;
private int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
}
public class ObjectEquals {
public static void main(String[] args){
Person p1 = new Person("张三", 25);
Person p2 = new Person("张三", 25);
// 判断p1和p2的内容是否相等
System.out.println(p1.equals(p2));
}
}
【结果】
从程序中可以看到,两个对象的内容完全相等,但为什么比较的结果是不相等呢?这是因为p1与p2的内容分别在不同的内存空间指向了不同的内存地址。在用equals对两个对象进行比较时,实际上是比较两个对象的地址。
若想比较两个对象的内容,则必须覆写equals()方法,如下:
package object;
class Person {
private String name;
private int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
// 覆写父类(Object类)中的equals()方法
public boolean equals(Object obj) {
boolean temp = true;
// 声明一个p1对象,此对象实际上就是当前调用equals()方法的对象
Person p1 = this;
// 判断Object类对象是否是Person的实例
if(obj instanceof Person) {
// 如果是Person类实例,则进行向下转型
Person p2 = (Person)obj;
// 调用String类中的equals()方法,String类中的方法已经覆写过,可用于比较内容
if(!(p1.name.equals(p2.name)&&p1.age == p2.age)) {
temp = false;
}
}
else {
// 如果不是Person类的实例,则直接返回false
temp = false;
}
return temp;
}
}
public class ObjectEquals {
public static void main(String[] args){
Person p1 = new Person("张三", 25);
Person p2 = new Person("张三", 25);
// 判断p1和p2的内容是否相等
System.out.println(p1.equals(p2));
}
}
【结果】
由上例可以看出通过覆写后的equals方法能够准确的对两个对象进行比较。所以在开发中往往需要覆写equals方法。
3、对象签名的hashCode()
Object类有两种方法来推断对象的标识:equals() 和 hashCode()。
如果根据equals() 方法判断两个对象是相等的,那么对这两个对象中的每个对象调用 hashCode()方法都必须生成相同的整数结果。
但是反过来,如果两个hashCode()返回的结果相等,两个对象的equals()方法却不一定相等。在默认情况下equals()方法用来比较两个对象的地址值,而原始的hashCode()方法用来返回其所在对象的物理地址。
class Person {
private String name;
private int id;
public Person(String name, int id) {
this.name = name;
this.id = id;
}
public int hashCode() { // 覆写hashCode方法
return id*(name.hashCode());
}
// 覆写父类(Object类)中的equals()方法
public boolean equals(Object obj) {
boolean temp = true;
// 声明一个p1对象,此对象实际上就是当前调用equals()方法的对象
Person p1 = this;
// 判断Object类对象是否是Person的实例
if(obj instanceof Person) {
// 如果是Person类实例,则进行向下转型
Person p2 = (Person)obj;
// 调用String类中的equals()方法,String类中的方法已经覆写过,可用于比较内容
if(!(p1.name.equals(p2.name)&&p1.id == p2.id)) {
temp = false;
}
}
else {
// 如果不是Person类的实例,则直接返回false
temp = false;
}
return temp;
}
}
public class HashCode {
public static void main(String[] args) {
Person p1 = new Person("小刚", 1);
Person p2 = new Person("小刚", 1);
Person p3 = new Person("小光", 2);
System.out.println(p1.equals(p2));
System.out.println(p1.equals(p3));
System.out.println(p1.hashCode());
System.out.println(p2.hashCode());
System.out.println(p3.hashCode());
}
}
【结果】
有结果可以看出p1.equals(p2)输出为true,p1.equals(p3)输出为false。由于重写了equals(),此时比较的不再是对象地址而是对象内容。 此时p1和p2的hash值相同,p1和p3的hash值是不同的。由于重写了hashCode (),此时返回的不再是地址,而是根据对象内容算出的一个值。
在覆写equals()方法的时候也必须覆写hashCode()方法。这样才能确保相等的两个对象拥有相等的.hashCode。
4、使用Object接收任意引用类数据类型对象
由于Object类是所有类的父类,所有类的对象都可以使用Object接收,Object类不光可以接收对象,还可以接收任意的引用数据类型(类、接口、数组)。
看下面这个例子:
public class ObjectArray {
public static void main(String[] args) {
int[] temp = {1,2,3,4,5};
Object obj = temp; // 用Object接收数组
print(obj);
}
public static void print(Object obj) {
if(obj instanceof int[]) { // 判断是否是整型数组
int[] x = (int[])obj;
for(int i = 0; i < x.length; i++) {
System.out.print(x[i] + "\t");
}
}
}
}
【结果】
虽然数组和Object类之间是不存在任何的定义关系的,但是因为数组是引用类型,所以照样可以使用Object接收。
接口和Object更不会有任何的关系,因为接口本身不可能去继承任何的一个类,可是在Java设计的时候由于很多地方都需要接口的支持,所以Object依然可以接收接口类型。
如下:
interface A {
public String getInfo();
}
class B implements A {
public String getInfo() {
return "Hello World!";
}
}
public class ObjectInterface {
public static void main(String[] args) {
A a = new B(); // 向上转型,为接口实例化
Object obj = a; // 向上转型,使Object接收a
A x = (A)obj; // 向下转型
System.out.println(x.getInfo());
}
}
【结果】
由上例知,只要是个引用类型,都可以通过Object接收,可以先把a向上转型为Object类型,因为Object是所有类的父类,也就是基类,所以所有的类都可以向上转型成为Object,然后他又将obj强制转换回了A,第1System.out里面调用了x的getInfo()方法,因为x一直是指向B的实例的,所以调用的就是B的getInfo()方法。