Java 的toString() 和 equals()函数简单介绍

来源:互联网 发布:牛顿迭代法算法 编辑:程序博客网 时间:2024/06/05 18:09

toString() 和 equals() 都是java面试中经常问到的地方.

特别是1条经典问题:  equals 和 "==" 的区别...


本文就从简单介绍下这两个object中的函数.


一. toString()函数

我们首先看1个简单的代码例子:

package Object_kng.Object_baseMethod;class Ob_1{}public class Ob_tostring_1{    public static void f(){        Ob_1 a = new Ob_1();        System.out.printf("%s\n", a.toString());        //System.out.printf("%s\n", a); //ok same as below        //System.out.println(a); //ok same as below        //System.out.printf("%s\n", a.getClass().getName());        //System.out.printf("%s\n", a.getClass().toString());    }    }

上面的例子定义了1个空的类 Ob_1. 在下面的启动类中首先实例化类Ob_1 的一个对象.  然后调用了a.toString()函数.

上面代码是能正确编译和执行的.


问题是既然Ob_1是1个空类, 里面并没有定义toString()这个函数, 为何还能调用呢.


1.1 java中所有的类都默认继承基类Object.

原因很简单, 因为Ob_1的toString()方法是继承自类Object的. 

Java中所有类都是基类Object的派生子孙类.

如果1个类A在定义中没有指明继承哪个类, 那么类A就继承了类Object!

如果类A extends B呢,  因为B肯定也是Object类的子孙类, 所以无论如何类A肯定是基类Object的派生类or子孙类.


1.2 toString()是类Object内定义的一个方法.

打开jdk api文档, 我们可以查到toString()函数是如下介绍的:


toString

public String toString()
返回该对象的字符串表示。通常,toString 方法会返回一个“以文本方式表示”此对象的字符串。结果应是一个简明但易于读懂的信息表达式。建议所有子类都重写此方法。
Object 类的 toString 方法返回一个字符串,该字符串由类名(对象是该类的一个实例)、at 标记符“@”和此对象哈希码的无符号十六进制表示组成。换句话说,该方法返回一个字符串,它的值等于:

getClass().getName() + '@' + Integer.toHexString(hashCode())
 
返回:
该对象的字符串表示形式。


根据上面的的介绍, 我们简单地得出如下的信息:

1. toString()函数返回1个String对象.

2. toString()函数可以被重写, 而且jdk介绍中建议我们为所有类重写这个方法.

3. 不重写这个方法的话, 返回值是 getClass().getName() + '@' + Integer.toHexString(hashCode())

4. toString()不是静态方法, 必须实例化对象才能调用.


1.3 不重写 toString()方法, 其返回值解释

例如上面的代码是没有重写toString()方法的.

执行结果如下:

[java] Object_kng.Object_baseMethod.Ob_1@19c8f4

Object_kng.Object_baseMethod.Ob_1@19c8f45 就是a.toString()的返回值.

根据上面的解析, 我们得知

Object_kng.Object_baseMethod.Ob_1 是由  a.getClass().getName() 来的.  这里实际上是类Ob_1的完整类名(包括所在包名).

而19c8f45 是 Integer.toHexString(a.hashCode()) 获得的.


1.3.1 getClass()方法

这里顺便解析一下getClass(),  getClass()方法也是属于基类Object的1个方法, 它返回的是1个对象所属的运行时类.

至于什么是运行时呢, 就是那个对象引用当前状态指向的对象的所属类.  这个跟多态有关.

本人语言表达能力有限, 还是用例子说明的吧:

package Object_kng.Object_baseMethod;class Ob_2{}class Ob_3 extends Ob_2{}public class Ob_getclass_1{public static void f(){Ob_2 a = new Ob_2();System.out.printf("%s\n", a.getClass());a = new Ob_3();System.out.printf("%s\n", a.getClass());}}

本例子有两个空类, 其中Ob_3 继承自 Ob_2

下面首先实力化1个Ob_2 的对象a, 然后输出a.getClass() (实际上输出a.getClass().toString())

然后把引用a 指向 Ob_3 的另1个对象(多态), 然后再输出1次a.getClass().


执行结果:

[java] class Object_kng.Object_baseMethod.Ob_2[java] class Object_kng.Object_baseMethod.Ob_3

可以见到两次输出是不同的, 第一次是Ob_2, 第二次是Ob_3.

也就是说运行时类不是指 引用a是属于哪个类, 而是指a 内存指向的对象属于哪个类, 这个需要多态的知识.


至于getClass().getName()就是获取当前运行时类的完整类名了.


1.3.2 Integer.toHexString(a.hashCode())

那么"@"后面那串字符是什么呢, 实际上它是该对象的hash码的16进制表示.

java中,每1个对象都有1个唯一的hashcode, 它跟这个对象的内存地址有关.


1.4 重写方法的1个简单例子:

package Object_kng.Object_baseMethod;class Point_1{private int x;private int y;public Point_1(int x, int y){this.x = x;this.y = y;}public String toString(){return "[" + x +"," + y +"]" ;}}public class Ob_tostring_2{public static void f(){Point_1 a = new Point_1(2,4);System.out.printf("%s\n", a.toString());System.out.printf("%s\n", a); //ok same as belowSystem.out.println(a); //ok same as below}}

上面定义了1个关于点的类, 有x, y的两个int类型成员(坐标)

然后重写了toString()方法. 不在输出类名和hashcode, 改成输出x和y的值, 并左一定的格式化.


输出如下:

[java] [2,4][java] [2,4][java] [2,4]

1.5 重写方法toString()方法的好处:

1. 方便自定义输出对象的信息. 

   而原来的运行时类名和hashcode有可能不是我们需要的.


2. 由上面例子中, System.out.prinft(), 和 System.out.println()里面的参数只放对象名a的话, 实质上会自动调用a.toString().

   这样在coding当中就可以利用这两个函数方便调试.


3. 实际上肯定java的自带的类都重写了toString()方法, 例如java.util.Date 日期这个类

1.6 toString()方法的一些小结.

通过上面的例子, 我相信读者最起码清楚如下1点:

toString()是定义在类Object的1个非静态方法.

这意味这个方法必须需要1个已实例化对象才能调用.


也就是为什么我们定义1个整形变量 int x时, 不能利用x.toString()方法把x转化为字符串.

因为x只是1个基本变量, 而不是1个对象啊.



二.equals()函数

2.1 "==" 符号比较的是指向对象的内存地址


下面开始讲另1个函数equals(), 在这之前我们看一下,另1个判断是否相等的符号"=="

接下来又是1个例子:

package Object_kng.Object_baseMethod;class Ob_4{private int id;private String name;public Ob_4(int id, String name){this.id = id;this.name = name;}}public class Ob_equal_1{public static void f(){Ob_4 a = new Ob_4(1,"Kent");Ob_4 a2 = new Ob_4(1,"Kent");System.out.println((a == a2));a2 = a;System.out.println((a == a2));}}

输出结果:

[java] false[java] true



上例子中定义了类Ob_4, 它有两个成员id 和 name.

下面启动类中,实例化了类Ob_4的两个对象a 和 a2, a和a2拥有相同的id和name成员.


但是用 "==" 来比较a 和 a2的话, 它们两者不相等的, 返回了false

接下来执行a2 = a; 这个语句意思就是把引用a2 指向a所指向的对象, 这样的话a2 和 a就指向Heap区的同一块地址了.

再用"==" 比较a 和 a2的话,就返回了true.




由此可见, 用"=="来比较两个对象的话, 实际是比较对象是否hashcode(), 只有两个对象"完全"相同, 才会返回true.



2.2 equals默认比较的是指向对象的内存地址

实际上equals()也是基类Object的一个方法.

public boolean equals(Object obj)


由此可见:

1. 非静态方法, 必须利用已实例化对象调用.

2. 返回boolean类型

3. 需要1个对象(Object)参数,  由于Object是java里所有其他类的超类, 这里也是运用了多态.

4. 冇final关键字, equals方法可以被重写.


我们将上面的代码改一下, 将" == " 改成 equals:

package Object_kng.Object_baseMethod;class Ob_5{private int id;private String name;public Ob_5(int id, String name){this.id = id;this.name = name;}}public class Ob_equals_2{public static void f(){Ob_5 a = new Ob_5(1,"Kent");Ob_5 a2 = new Ob_5(1,"Kent");System.out.println((a.equals(a2)));a2 = a;System.out.println((a.equals(a2)));}}

输出:

[java] false[java] true

见到输出结果跟上面例子是一样的, 所以基类Object的equals方法也是比较内存的地址.


实际上equals()方法在基类Object的源代码如下:

public boolean equals(Objectobj){

    return (this == obj)

}

也就是equals调用了 "==", 基本上是等价的.

2.3 equals 和 " == " 并非比较对象的hashCode().

因为hashCode()跟内存地址有关, 所以也有人讲equals 和 "=="比较的是对象的hashCode().


但这种说法是错误的, 因为hashCode()也是基类Object的方法, 也可以重写.

我们来尝试重写hashCode()方法, 来证明观点:

package Object_kng.Object_baseMethod;class Ob_6{private int id;private String name;public Ob_6(int id, String name){this.id = id;this.name = name;}public int hashCode(){return 1;}}public class Ob_equals_3{public static void f(){Ob_6 a = new Ob_6(1,"Kent");Ob_6 a2 = new Ob_6(2,"David");[System.out.println(a);System.out.println(a2);System.out.println((a.equals(a2)));System.out.println((a == a2));a2 = a;System.out.println((a.equals(a2)));System.out.println((a == a2));}}

输出:

     [java] Object_kng.Object_baseMethod.Ob_6@1     [java] Object_kng.Object_baseMethod.Ob_6@1     [java] false     [java] false     [java] true     [java] true


上面例子中, 我们在类Ob_6里重写了hashCode的方法, 都返回1.

下面实例化两个对象a 和 a2, 而且两个对象的 id, name成员都不一样.

但是输出对象信息(toString()) 方法, 见到@后面的数字都是1, 也就是它们的hashCode已经相等.

但是后面的比较还是跟之前的例子一样.

也就证明了 equals默认情况下 和 "=="都是比较对象的内存地址, 而非hashCode().




2.4 重写equals()方法.

但是实际生产中, 我需要比较两个不同的对象, 如果两者的所有成员相等, 我们就认为两者相等.

这是我们就可以重写equals()方法, 改变它的机制以适用我们的业务需要.


最常见的改写方法:

package Object_kng.Object_baseMethod;class Ob_7{private int id;private String name;public Ob_7(int id, String name){this.id = id;this.name = name;}public boolean equals(Object obj){Ob_7 ob;try{ob = (Ob_7)obj;}catch(ClassCastException e){System.out.printf("warning: the object is not belonged to Class Ob_7!!\n");return false;}catch(Exception e){e.printStackTrace();return false;}if (this.id == ob.id && this.name == ob.name){return true;}return false;}}public class Ob_equals_4{public static void f(){Ob_7 a = new Ob_7(1,"Kent");Ob_7 a2 = new Ob_7(1,"Kent");Ob_7 a3 = new Ob_7(2,"David");System.out.println((a.equals(a2)));System.out.println((a.equals(a3)));System.out.println((a.equals(new Ob_6(1,"kent"))));a2 = a;System.out.println((a.equals(a2)));}}

这个例子在类Ob_7 中重写了equals方法.

注意,重写这个方法涉及了多态的知识, 必须将形参强制转化为对应的类, 才可以比较成员.

为防止传错了其他类的对象的异常, 最好加上try{}语句来捕捉.


输出结果:

符合我们的需要了, 只要id和name相等, 我们就认为这两个对象相等:

     [java] hello ant, it's the my meeting with ant!     [java] true     [java] false     [java] warning: the object is not belonged to Class Ob_7!!     [java] false     [java] true


2.5 Java里有一些自带的类也重写了equals()方法.

最明显的例子就是String类. 下面例子:

package Object_kng.Object_baseMethod;public class Ob_equals_5{public static void f(){String s1 = "abc";String s2 = new String("abc");System.out.println((s1.equals(s2)));System.out.println((s1 == s2));}}


输出:

     [java] true     [java] false

上面定义了两个字符串对象, 值都是"abc"

用equals比较两者是相等的.

用 "==" 则不等.

由此可见String类改写了equals方法, 当然更可能是String的其中1个超类改写了equals方法.


2.5 Java里equals 和 "==" 的区别

以后大家可以这样回答面试官:

1. "==" 可以比较基本类型(如 int 和 boolean)或对象.

2. equals是对象的非静态方法, 只能用于比较对象, 不能比较基本类型.

3. "==" 用于对象时, 比较的是对象内存地址, equals() 通常情况下也是会比较内存地址.

4. 一些类如String() 改写了equals()方法, 比较的是String对象的值, 而不是内存地址.

5. 我们可以改写equals()方法, 根据需要来比较两个对象.







































0 0
原创粉丝点击