一、Object类 java.lang包中定义的Object类是所有Java类的根父类,其中定义了一些实现和支持面向对象机制的重要方法。任何Java对象,如果没有父类,就默认它继承了Object类。因此,实际上,以前的定义是下面的简略: public class Employee extends Object 和 public class Manager extends Employee Object类定义许多有用的方法,包括toString(),它就是为什么Java软件中每样东西都能转换成字符串表示法的原因。(即使这仅具有有限的用途)。 1.equals方法 Object类定义的equals方法用于判别某个指定的对象与当前对象(调用equals方法的对象)是否等价。在Java语言中数据等价的基本含义是指两个数据的值相等。 ”==”进行比较的时候,引用类型数据比较的是引用,即内存地址,基本数据类型比较的是值。 1.1 equals方法与“==”运算符的关系 equals()方法只能比较引用类型,"=="可以比较引用类型及基本类型; 特例:当用equals()方法进行比较时,对类File、String、Date及包装类来说,是比较类型及内容而不考虑引用的是否是同一个实例,因为这些类中已经重写了equals方法,实现了比较内容的功能。源码可参看JDK目录下的src.zip中的相应部分。 用"=="进行比较时,符号两边的数据类型必须一致(可自动转换的数据类型除外),否则编译出错,而用equals方法比较的两个数据只要都是引用类型即可。 示例如下: class MyDate { privateint day, month, year; public MyDate(int day, int month, int year) { this.day = day; this.month = month; this.year = year; } } publicclass Test { publicstaticvoid main(String[] args) { MyDate m1 = new MyDate(8, 8, 2008); MyDate m2 = new MyDate(8, 8, 2008);
if (m1 == m2) { System.out.println("m1==m2"); } else { System.out.println("m1!=m2"); } if (m1.equals(m2)) { System.out.println("m1 is equal to m2"); } else { System.out.println("m1 is not equal to m2"); }
m2 = m1; if (m1 == m2) { System.out.println("m1==m2"); } else { System.out.println("m1!=m2"); } } } 程序运行结果为: m1!=m2 m1 is not equal to m2 m1==m2 小结一下: 在引用类型的比较上,Object里面的equals方法默认的比较方式,基本上等同于“==”,都是比较内存地址,只有那几个特殊的是比较的对象的内容(属性值)。自己写的类也可以做这样的覆盖工作。下面介绍详细的做法。 1.2 覆盖equals方法 对于程序员来说,如果一个对象需要调用equals方法,应该在类中覆盖equals方法。如果覆盖了equals方法,那么具体的比较就按照你的实现进行比较了。 一般来讲:为了比较两个分离的对象(也就是内存地址不同的两个对象),自行覆盖的equals方法里面都是检查类型和值是否相同。上面那几个特殊的情况就是这样,比如String类,它覆盖了equals方法,然后在里面进行值的比较。 覆盖equals方法的一般步骤如下: (1)用==检查是否参数就是这个对象的引用 (2)判断要比较的对象是否为null,如果是null,返回false (3)用instanceof判断参数的类型是否正确 (4)把参数转换成合适的类型 (5)比较对象属性值是不是匹配 示例如下: 覆盖前equals和==比较的都是内存地址: public class Test{ public static void main(String[] args) { A a1 = new A(); a1.age = 3; A a2 = new A(); a2.age = 3; System.out.println("a1 == a2 test ="+(a1==a2)); System.out.println("a1 equals a2 test ="+a1.equals(a2)); } } class A{ public int age = 0; } 运行结果是: a1 == a2 test =false a1 equals a2 test =false 覆盖后equals比较的是值,==比较的是内存地址: public class Test{ public static void main(String[] args) { Test t = new Test(); A a1 = new A(); a1.age = 3; A a2 = new A(); a2.age = 3; System.out.println("a1 == a2 test ="+(a1==a2)); System.out.println("a1 equals a2 test ="+a1.equals(a2)); } } class A{ public int age = 0; public boolean equals(Object obj){ //第一步先判断是否同一个实例 if(this==obj){ returntrue; } //第二步判断要比较的对象是否为null if (obj == null){ returnfalse; } //第三步判断是否同一个类型 if(obj instanceof A){ //第四步类型相同,先转换成为同一个类型 A a = (A)obj; //第五步然后进行对象属性值的比较 if(this.age == a.age){ return true; }else{ return false; } }else{ //类型不同,直接返回false return false; } } } 说明:如果对象的属性又是一个引用类型的话,会继续调用该引用类型的equals方法,直到最后得出相同还是不同的结果。示例如下: public class Test{ public static void main(String[] args) { Test t = new Test(); A a1 = new A(); a1.age = 3; A a2 = new A(); a2.age = 3; System.out.println("a1 == a2 test ="+(a1==a2)); System.out.println("a1 equals a2 test ="+a1.equals(a2)); } } class A{ public int age = 0; public String name = "Java私塾"; public boolean equals(Object obj){ //第一步先判断是否同一个实例 if(this==obj){ returntrue; } //第二步判断要比较的对象是否为null if (obj == null){ returnfalse; } //第三步判断是否同一个类型 if(obj instanceof A){ //第四步类型相同,先转换成为同一个类型 A a = (A)obj; //第五步然后进行对象属性值的比较 if(this.age== a.age&&this.name.equals(a.name)){ return true; }else{ return false; } }else{ //类型不同,直接返回false return false; } 最后重要的一点规则:覆盖equals方法应该连带覆盖hashCode方法。 2.hashCode方法 hashCode是按照一定的算法得到的一个数值,是对象的散列码值。主要用来在集合(后面会学到)中实现快速查找等操作,也可以用于对象的比较。 在Java中,对hashCode的规定如下: (1)在同一个应用程序执行期间,对同一个对象调用hashCode(),必须返回相同的整数结果——前提是equals()所比较的信息都不曾被改动过。至于同一个应用程序在不同执行期所得的调用结果,无需一致。 (2)如果两个对象被equals(Object)方法视为相等,那么对这两个对象调用hashCode()必须获得相同的整数结果。 (3) 如果两个对象被equals(Object) 方法视为不相等,那么对这两个对象调用hashCode()不必产生不同的整数结果。然而程序员应该意识到,对不同对象产生不同的整数结果,有可能提升hashTable(后面会学到,集合框架中的一个类)的效率。 简单地说:如果两个对象相同,那么它们的hashCode值一定要相同;如果两个对象的hashCode相同,它们并不一定相同。 在Java规范里面规定,覆盖equals方法应该连带覆盖hashCode方法,这就涉及到一个如何实现hashCode方法的问题了。 实现一:偷懒的做法:对同一对象始终返回相同的hashCode,如下: public int hashCode(){ return 1; } 它是合法的,但是不好,因为每个对象具有相同的hashCode,会使得很多使用hashCode的类的运行效率大大降低,甚至发生错误。
实现二:采用一定的算法来保证 在高效Java编程这本书里面,给大家介绍了一个算法,现在eclipse自动生成equals方法和hashCode方法就是用的这个算法,下面介绍一下这个算法: (1) 将一个非0常数,例如31,储存于int result变量 (2)对对象中的每个有意义的属性f(更确切的说是被equals()所考虑的每一个属性)进行如下处理: A. 对这个属性计算出类型为int的hash 码 c: i. 如果属性是个boolean,计算(f ? 0 : 1)。 ii. 如果属性是个byte,char,short或int,计算(int)f。 iii. 如果属性是个long,计算(int)(f^(f >>> 32))。 iv. 如果属性是个float,计算Float.floatToIntBits(f)。 v. 如果属性是个double,计算Double.doubleToLongBits(f),然后将计算结果按步骤2.A.iii处理。 vi. 如果属性是个对象引用,而且class 的equals()通过递归调用equals()的方式来比较这一属性,那么就同样也对该属性递归调用hashCode()。如果需要更复杂的比较,请对该属性运算一个范式(canonical representation),并对该范式调用hashCode()。如果属性值是null,就返回0(或其它常数;返回0 是传统做法)。 vii. 如果属性是个数组,请将每个元素视为独立属性。也就是说对每一个有意义的元素施行上述规则,用以计算出hash 码,然后再依步骤2.B将这些数值组合起来。 B. 将步骤A计算出来的hash码 c按下列公式组合到变量result中: result = 31*result + c; (3) 返回result。 示例如下:这个就是用eclipse自动生成的 public class Test{ private byte byteValue; private char charValue; private short shortValue; private int intValue; private long longValue; private boolean booleanValue; private float floatValue; private double doubleValue; private String uuid; private int[] intArray = new int[3] public int hashCode() { final int prime = 31; int result = 1; result = prime * result + (booleanValue ? 1231 : 1237); result = prime * result + charValue; long temp; temp = Double.doubleToLongBits(doubleValue); result = prime * result + (int) (temp ^ (temp >>> 32)); result = prime * result + Float.floatToIntBits(floatValue); result = prime * result + Arrays.hashCode(intArray); result = prime * result + intValue; result = prime * result + (int) (longValue ^ (longValue >>> 32)); result = prime * result + shortValue; result = prime * result + ((uuid == null) ? 0 : uuid.hashCode()); return result; } } 3.toString方法 toString()方法是Object类中定义的另一个重要方法,其格式为: public String toString() { } 方法的返回值是String类型,用于描述当前对象的有关信息。Object类中实现的toString()方法是返回当前对象的类型和内存地址信息,但在一些子类(如String, Date等)中进行了重写,也可以根据需要在用户自定义类型中重写toString()方法,以返回更适用的信息。 除显式调用对象的toString()方法外,在进行String与其它类型数据的连接操作时,会自动调用toString()方法,其中又分为两种情况: (1)引用类型数据直接调用其toString()方法转换为String类型; (2)基本类型数据先转换为对应的包装类型,再调用该包装类的toString()方法转换为String类型。 另外,在System.out.println()方法输出引用类型的数据时,也先自动调用了该对象的toString()方法,然后再将返回的字符串输出。 示例如下: class MyDate{ privateint day,month,year; public MyDate(int d, int m, int y){ day = d; month = m; year = y; } } class YourDate{ privateint day,month,year; public YourDate(int d, int m, int y){ day = d; month = m; year = y; } public String toString(){ return day + "-" + month + "-" + year; } } publicclass Test{ publicstaticvoid main(String args[]){ MyDate m = new MyDate(8,8,2008); System.out.println(m); System.out.println(m.toString()); YourDate y = new YourDate(8,8,2008); System.out.println(y); } } 运行结果: cn.javass.java6.test.MyDate@1fb8ee3 cn.javass.java6.test.MyDate@1fb8ee3 8-8-2008 toString方法被用来将一个对象转换成String表达式。当自动字符串转换发生时,它被用作编译程序的参照。System.out.println()调用下述代码: Date now = new Date() System.out.println(now) 将被翻译成: System.out.println(now.toString()); 对象类定义缺省的toString()方法,它返回类名称和它的引用的地址(通常情况下不是很有用)。许多类覆盖toString()以提供更有用的信息。例如,所有的包装类覆盖toString()以提供它们所代表的值的字符串格式。甚至没有字符串格式的类为了调试目的常常实现toString()来返回对象状态信息。
Java私塾跟我学系列——JAVA篇 网址:http://www.javass.cn 电话:010-68434236 |