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

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 below
		System.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()方法, 根据需要来比较两个对象.







































  • 6
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

nvd11

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值