1 Object 类
Object 类是 Java 中所有类的父类。
在 Java 中任何一个类,如果追根溯源的话,归根结底都是 Object 类的直接或者间接子类。
首先,Object 类既然是所有类型的父类,那么在 Java 中所有的对象,都能够赋值给 Object类型的引用。这是因为子类对象可以直接赋值给父类引用,而所有 Java 中的类都是 Object类的子类。
其次,由于子类中能够继承父类中的公开方法。因此,Object 类中所有的公开方法都能被子类继承。也就是说,Object 类中的公开方法,是 Java 中所有对象都拥有的方法。接下来,我们就仔细来研究一下 Object 类的公开方法。
1.1 finalize
finalize 是一个 protected 的方法。虽然不是 public 的,但是这个方法也同样能够被所有子类继承(考虑一下,protected 能够被同包以及非同包的子类访问,也就是能够被所有子类访问)。
这个方法会在对象被垃圾回收时由垃圾回收器调用。
class Student{ String name; int age; } public class TestStudent{ public static void main(String args[]){ Student stu1 = new Student(); stu1 = new Student(); } }
在这段代码中,创建了两个不同的 Student 对象。内存中的结构如下图:
可以看出,一开始分配的那块内存(Student 对象 1),由于之后把 stu1 指向了 Student对象 2(创建了一个新对象并把首地址赋值给 stu1 引用),因此再也没有引用指向 Student对象 1。因此结果就是,这个对象占据着内存空间,但是没有引用指向这个对象,因此这个对象无法被使用。于是,这种没法使用但又占据内存空间的对象,就被称为垃圾对象。这些垃圾对象占用内存空间,如果一直不处理的话,会造成内存空间的浪费,严重的话会造成程序的崩溃。
而在 Java 中,程序员只需要负责分配空间(也就是 new 对象),而不需要操心处理垃圾对象的问题。JVM 有一个自动垃圾回收的机制,这个机制能够自动回收垃圾对象所占用的内存。这样,程序员就只需要负责分配空间、创建对象而不用担心内存的回收。
当 JVM 进行一个对象的垃圾回收工作时,会自动调用这个对象的 finalize 方法。我们应该如何来看待这个 finalize 方法呢?这要从垃圾回收的时机说起。在 JVM 的规范中,只规定了 JVM 必须要有垃圾回收机制,但是什么时候回收却没有明确说明。也就是说,对象成为了垃圾对象之后,并不一定会马上就被垃圾回收。
在 Java 中还有一个 System.gc()方法。调用这个方法,就相当于通知 JVM,程序员希望能够进行垃圾回收。但是调用 gc 也不能保证马上就能进行垃圾回收,这一切都要看当时 JVM的运行状态。
总结一下:finalize 方法在对象被垃圾回收的时候调用。但是,由于 Sun 公司的 JVM 采用的是“最少”回收的机制,因此不应当把释放资源的代码写在 finalize 方法中。
1.2 getClass
getClass 方法是 Object 类中的一个公开方法,这个方法的作用是:返回对象的实际类型。
class Animal{} class Dog extends Animal{} class Courser extends Dog{} public static boolean isDog(Animal ani){ if (ani instanceof Dog){ return true; }else return false; }
这样的代码却有一个问题,当传入的 ani 参数所指向的对象是一个 Courser 对象时,这个函数同样返回 true。因为根据多态,Courser 是 Dog 类的子类,因此当 ani 指向一个 Courser 对象时,返回值也是 true。这与我们的要求并不一致,我们希望的是,仅仅当 ani 参数指向一个实际类型为 Dog 类的对象时才返回 true。
针对这个问题,我们可以使用 getClass 方法来解决。getClass 方法能够返回一个对象的实际类型,通过比较两个对象 getClass 的返回值,就能够判断这两个对象是否是同一个类型。例如有如下代码:
Dog d1 = new Dog(); Dog d2 = new Dog(); Dog d3 = new Courser();
//d1 和 d2 实际类型相同,因此 getClass 方法返回值相同,输出 true
System.out.println(d1.getClass() == d2.getClass());
//d1 和 d3 实际类型不同,因此 getClass 方法返回值不同,输出 false
System.out.println(d1.getClass() == d3.getClass());
因此,利用 getClass 方法,就能够避免 instanceof 操作符的麻烦。改写 isDog 方法如下:
public static boolean isDog(Animal ani){ Dog d = new Dog(); if (ani.getClass() == d.getClass() ){ return true; }else return false; }
1.3 equals
equals 方法是 Object 类中定义的方法,其方法签名为:
public boolean equals(Object obj)
由于 equals 是 Object 类中的公开方法,这意味着在 java 中所有对象都包含了 equals 方法。这个方法用来判断两个对象内容是否相等。要注意的是 equals 方法和双等号“==”之间的区别。
“==”比较两个对象类型,比较的是两个引用中保存的地址,而不是对象的值。
用 equals 方法来比较两个对象的内容是否相等。
注意 equals 方法的签名:这个方法接受一个 Object 类型的对象 obj 做为参数。equals方法比较的就是当前对象(this)和 obj 这两个对象的内容。例如,对于 str1.equals(str2)这个比较而言,当前对象就是 str1 对象,而 obj 对象就是 str2 对象。
要注意的是,在某些情况下需要程序员覆盖 equals 方法。例如下面的代码:
class Student { String name; int age; public Student(){} public Student(String name, int age){ this.name = name; this.age = age; } } public class TestStudent{ public static void main(String args[]){ Student stu1 = new Student(“Tom”, 18); Student stu2 = new Student(“Tom”, 18); System.out.println(stu1 == stu2); System.out.println(stu1.equals(stu2)); } }
上面这个程序,输出结果为两个 false。
第二个输出语句中,我们调用了 equals 方法来比较两个对象。在这两个对象中,两个学生的姓名相同,年龄也相同,但是比较的结果依然是 false。
在我们的 Student 对象中并没有定义 equals 方法,因此在调用 stu1.equals 方法时,调用的实际上是 Student 类从 Object 类中继承的 equals 方法。那 Object 类中的 equals 方法进行判断时,判断的就是引用是否相等。以下代码来源于 Sun 公司 JDK6 的源码
public boolean equals(Object obj) { return (this == obj); }
可以看到,在 Object 类中的 equals 方法,使用的是双等号进行的引用的比较,因此,Object 类中的 equals 方法不能够帮助我们比较两个学生对象的内容是否相等。为了替换掉 Object 类中对 equals 的实现,我们应当覆盖 equals 方法。也就是说,程序员应当自己来指定两个对象的比较准则。
要注意的是,实现的 equals 方法,应当满足以下几个条件:
自反性。自反性指的是,如果一个引用 x 不等于 null,则 x.equals(x)应当为 true。也就是说,无论什么情况,一个对象自己跟自己比较,必须为 true。
对称性。对称性指的是,如果有两个不为 null 的引用 x 和 y,如果 x.equals(y)为 true,则 y.equals(x)也为 true;而如果 x.equals(y)为 false,则 y.equals(x)也为 false。
传递性。这条准则指的是,如果有三个不为 null 的引用 a、b、c,如果 a.equals(b)为 true,b.equals(c)为 true,则 a.equals(c)也必然为 true。
一致性。这条准则比较简单,指的是如果有两个不为 null 的引用 x 和 y,在不改变x 和 y 的属性的前提下,每次调用 x.equals(y)返回的值都相同。也就是说,如果你不改变 x 和 y 的属性,则每次比较这两个对象,结果应该一致,不能因为时间的变化等因素而影响比较的结果。
如果 x 不为 null,则 x.equals(null)应当返回 false。 public boolean equals(Object obj){ //判断 obj 是否和 this 相等,保证自反性 if (obj == this) return true; //判断 obj 是否为 null,保证最后一条准则 if (obj == null) return false; //判断两个对象的实际类型是否相等, //如果不相等,则说明比较的是两个不同种类的对象,应当返回 false if (obj.getClass() != this.getClass()) return false; //强制类型转换 //由于之前已经使用 getClass 判断过实际类型,因此这里强转是安全的 Student stu = (Student) obj; //判断每个属性是否相等 // 对于基本类型的属性用“==”比较,对象类型的属性用 equals 比较 if (this.age == stu.age && this.name.equals(stu.name) ) return true; else return false; }
总结一下,覆盖 equals 方法的五个步骤:
1、 判断 this == obj
2、 判断 obj == null
3、 判断两个对象的实际类型(使用 getClass()方法)
4、 强制类型转换
5、 依次判断两个对象的属性是否相等
1.4 toString
toString 方法也是 Object 类中定义的方法,这意味着这个方法是 Java 中所有对象都有的方法。这个方法的签名如下:
public String toString()
这个方法没有参数,返回值类型是一个 String 类型。这个方法的返回值是某个对象的字符串表现形式。
class Student{ String name; int age; public Student(){} public Student(String name, int age){ this.name = name; this.age = age; } } public class TestToString{ public static void main(String args[]){ Student stu = new Student(); System.out.println(stu.toString());//手动调用 toString 方法 System.out.println(stu); //直接打印 stu 对象 } }
上面的两行代码运行结果如下:
Student@c17164
Student@c17164
可以看出,无论是手动调用 toString 方法,还是直接打印 stu 对象,所得的结果都是一样的。这意味着,如果打印 stu 对象的话,就相当于打印 stu 的 toString 方法返回值。
我们调用学生对象的 toString 方法,目的是获知学生对象的相关信息,例如学生的姓名和年龄等。而由于在上面的代码中,我们没有为 Student 类写 toString 方法,这意味着学生对象中的 toString 方法是从 Object 类中继承来的,因此打印出的是类名以及相关地址。为了让 toString 方法能打印出我们想要的内容,我们可以覆盖这个方法如下:
class Student{ String name; int age; public Student(){} public Student(String name, int age){ this.name = name; this.age = age; } public String toString(){ return name + " " + age; } } public class TestToString{ public static void main(String args[]){ Student stu = new Student("Tom", 18); System.out.println(stu.toString()); System.out.println(stu); } }
运行结果如下:
Tom 18
Tom 18
可以看出,无论用哪种方式,都打印的是 Student 类中 toString()方法的返回值。此外,我们在介绍 String 类型时曾经提过,String 类型与任何其他类型相加,结果都是String 类型。这其中,“其他类型”既包括简单类型,又包括对象类型。
Student stu = new Student(“Tom”, 18); String str = “my string ” + stu; System.out.println(str);
我们使用一个“my string”字符串与一个学生对象相加,结果还是一个字符串。在生成这个字符串的时候,会把一个对象转换成一个字符串,转换的方式,就是调用这个对象的 toString 方法并获取其返回值。
这句代码表示把“my string”字符串和 stu 的 toString 方法返回值相加,并把相加的结果赋值给 str 变量。
上述代码的运行结果如下:
my string Tom 18