Java 的toString() 和 equals()函数简单介绍,tostringequals
Java 的toString() 和 equals()函数简单介绍,tostringequals
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. 方便自定义输出对象的信息.
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()方法, 根据需要来比较两个对象.
相关文章
- 暂无相关文章
用户点评