`

【转】Java中的数据比较(再谈==与equals的区别)

    博客分类:
  • Java
阅读更多
Java中的变量与对象有区别吗?

引子:变量与对象
变量是Java中最基本的存储单元,为变量赋值可以使用赋值表达式。如:
int i = 10;  

该表达式的含义是将一个字面量(literal)10赋值给一个类型为int型的变量,变量名为i。这是一个为基本数据类型的变量赋值的例子,它表达了一个非常朴素的信息,那就是变量i的值为10。
那么这种赋值表达式引申到引用类型的变量时,其含义又有什么变化呢?再看一个赋值表达式:
String str = null; 

该表达式的含义是将空内存地址(null)赋值给String类型的变量,变量名为str。朴素的说法是变量str的值为null。对于引用类型的变量而言,赋值操作只是将对象的内存地址保存到变量中。也就是说引用类型的变量值是对象的内存地址而不是对象的内容。如下例:
String str1 = "abc";  
String str2 = new String("abc"); 

上述两种赋值操作本质上没有任何区别,最大的区别是生成对象的方法不同(这一点与赋值操作无关)。对于变量而言,其值仍然是所指对象的内存地址。

相对于变量,对象也是存储单元的一种。对象有自己的属性与方法,其内容的表现形式由实例化该对象所用的类决定。如:
new java.sql.Time(0L); 

要使用对象,必须将对象的内存地址指定到一个引用类型的变量中(也就是变量的赋值操作)。该变量的类型可以与对象的类型一致,也可以是对象类型的父类,或者是对象类型实现的接口。后两种是典型的多态应用。如:
java.util.Date date = new java.sql.Time(0L);  

当然,我们只能通过变量去调用对象的方法或者设置对象的属性,其作用无非是取得或者修改对象的内容。如:
java.util.Date date = new java.sql.Time(0L);  
date.setTime(3600000L);  
System.out.println(date.toString());  

注意,变量的内涵只有一个,就是它的值。我们通过变量调用对象的方法时,可以改变的也只是对象的内容。区分变量与对象是很有必要的,当我们讨论变量时总是与它们的值有关;当我们讨论对象时更多的是在讨论如何取得或修改它们的内容。记住:

变量的值只能通过赋值表达式来改变;对象的内容只能通过自身的方法或属性来改变。

变量值的比较
当我们讨论变量之间是否相等时,通常使用“==”关系运算符。如:
int i = 10;  
int j = 20;  
if (i == j) {  
    System.out.println("两个变量的值相等");  
}  

上例是基本数据类型之间的比较,本质上是变量之间值的比较。对于两个引用变量的比较,如:
String str1 = "abc";  
String str2 = new String("abc");  
if (str1 == str2) {  
    // 判断无法成立,因为两个变量所指对象的内存地址不同。  
    System.out.println("两个变量的值相等");  
}  

本质上引用类型的变量之间的比较也是值的比较,也就是内存地址的比较。上例不会打印出“两个变量的值相等”,因为两个变量指向了不同内存地址的对象。

对象内容的比较
对于引用变量而言,如果我们不想仅限于对内存地址的比较,而是想做更深层次的(比如对象的内容)比较。如何实现呢?Java的Object类提供了equals方法,此方法实现了对象之间内容上的比较。由于Object类是所有Java类的父类,所以我们只要在自己的类中改写equals方法,就可实现该类对象之间的内容比较。如:
String str1 = "abc";  
String str2 = new String("abc");  
if (str1.equals(str2)) {  
    // 判断成立,因为两个对象的内容都是"abc"。  
    System.out.println("两个对象的内容相等");  
}  

关于如何改写equals方法以及与之相关的hashCode方法,可以参考潘爱民翻译的《Effective Java中文版》一文中第7条:在改写equals的时候请遵守通用约定 以及 第8条:改写equals时总是要改写hashCode。

老生常谈:==与equals的区别
对于Java初学者而言,==与equals是容易混淆的。当然区分它们也是简单的,只要记住:

==只针对变量的值;equals只针对对象的内容。

记住上句话的同时,请记住下面的一句话:

引用类型的变量值是所指对象的内存地址。

附1:Java函数调用中,参数的传递方式只有一种:值传递
关于值传递的定论,网上有许多许多例子可以证明。这里也举个例子:
public class Test {  
  
    public static void main(String[] args){  
        java.util.Date date = new java.util.Date(0);  
  
        System.out.println(date);  
        change(date);  
        System.out.println(date);  
    }  
  
    public static void change(java.util.Date date) {  
        // 此处改变了参数(变量)的值  
        date = new java.util.Date(3600000L);  
        System.out.println(date);  
    }  
}  

上例的输出结果是:
Thu Jan 01 08:00:00 CST 1970
Thu Jan 01 09:00:00 CST 1970
Thu Jan 01 08:00:00 CST 1970

我们稍微改动一下内容:
public class Test {  
  
    public static void main(String[] args){  
        java.util.Date date = new java.util.Date(0L);  
  
        System.out.println(date);  
        change(date);  
        System.out.println(date);  
    }  
  
    public static void change(java.util.Date date) {  
        // 此处改变了对象的内容  
        date.setTime(3600000L);  
        System.out.println(date);  
    }  
}  

上例的输出结果是:
Thu Jan 01 08:00:00 CST 1970
Thu Jan 01 09:00:00 CST 1970
Thu Jan 01 09:00:00 CST 1970

通过这两个例子可以证明什么呢?
当函数调用时,JVM会生成第二个引用类型的变量,并将原始引用变量的值(对象的内存地址)复制给第二个引用变量。 注意,值传递的本质是变量值的复制而不是对象内容的复制
第一个例子中,函数内部改变的是第二个引用变量的值,原始引用变量的值没有改变。第二个例子中,函数内部改变的是引用变量所指对象的内容,由于原始引用变量与第二个引用变量的值相等(指向同一个对象),所以导致了函数调用后对象的内容已经改变的事实。
附2:关键字final只作用于变量的值,不作用于对象的内容。
关键字final的作用是变量只能赋值一次。举个例子:
final int i = 10;  
i = 20; // 编译时报错

但只能赋值一次的限制只作用于变量的值,而不是对象的内容。如:
final java.util.Date date = new java.util.Date(0L);  
date.setTime(3600000L); // 编译可以通过,并且运行正常。 

如此引来了另一个问题,如何定义常量。
定义一个常量,首先要做到使用关键字final对变量进行限制。但这样做只限制了变量的值,如果变量的类型是引用类型时,还必须保证该对象是一个值对象(value objects)。换言之值对象的类必须是一个不可变类(immutable classes)。
String类就是一个不可变类,我们可以用String类直接定义常量。而java.util.Date类可以通过自身的方法改变其内容,所以不能用这种类型定义常量。如:
final String CHINA = "china";  
 
关于如何实现不可变类的论述,可以参考《Practical Java》的第63条:审慎地定义和实现不可变类。

From:http://blog.csdn.net/darxin/archive/2010/01/23/5247391.aspx
分享到:
评论

相关推荐

    Java 2023最新面试知识点总结.pdf

    谈一谈”=“与”equals0"的区别。 《Think in Java》中说:“关系操作符生成的是一个boolean结果,它们计算的是操作数的值之间的关系”。 "=="判断的是两个对象的内存地址是否一样,适用于原始数据类型和枚举类型...

    equals 和 hashCode两者效果分析详解.docx

    原因是因为,在Java自带的容器HashMap和HashSet中, 都需同时要用到对象的hashCode()和equals()方法来进行判断,然后再插入删除元素,这点我们一会再谈。 那么我们还是单独来看hashCode(),为什么HashMap需要用到...

    JAVA入门1.2.3:一个老鸟的JAVA学习心得 PART1(共3个)

    3.4 小结:基本数据类型—— Java中一切数据和运算的基础 63 3.5 习题 65 第4章 Java中的程序执行流程 67 教学视频:1小时57分钟 4.1 顺序执行 67 4.2 使用if-else让程序懂得判断 68 4.2.1 if语句 68 4.2.2 ...

    Java入门1·2·3:一个老鸟的Java学习心得.PART3(共3个)

    3.4 小结:基本数据类型—— Java中一切数据和运算的基础 63 3.5 习题 65 第4章 Java中的程序执行流程 67 教学视频:1小时57分钟 4.1 顺序执行 67 4.2 使用if-else让程序懂得判断 68 4.2.1 if语句 68 4.2.2 ...

    java深入解析

    103 话题18 一成不变——不可修改的String对象 107 话题19 钩深索隐——String字符最大长度的探索 111 话题20 追本溯源——追寻String字面常量的“极限” 116 话题21 旧调重弹——再论equals方法与“==”的 区别 ...

    Java入门教程(微学苑)-part1

    3.22 再谈Java包 56 3.23 如何实现包 56 3.24 包的调用 56 3.24.1.1 1) 在每个类名前面加上完整的包名 57 3.24.1.2 2) 通过 import 语句引入包中的类 57 3.25 类的路径 57 3.26 包的访问权限 58 3.27 源文件的声明...

    21天学通Java-由浅入深

    60分钟) 217 11.1 异常处理基本介绍 217 11.1.1 try和catch捕获异常 217 11.1.2 try-catch语句使用注意点 218 11.1.3 finally语句的使用 220 11.1.4 再谈异常处理注意点 222 11.2 异常的分类 223 11.2.1 捕获异常 ...

    sesvc.exe 阿萨德

    如果当前桶有值( Hash 冲突),那么就要比较当前桶中的 key、key 的 hashcode 与写入的 key 是否相等,相等就赋值给 e,在第 8 步的时候会统一进行赋值及返回。 如果当前桶为红黑树,那就要按照红黑树的方式写入数据...

    net学习笔记及其他代码应用

    24.在C#中,string str = null 与 string str = “” 请尽量使用文字或图象说明其中的区别。 答:string str = null 是不给他分配内存空间,而string str = \"\" 给它分配长度为空字符串的内存空间。 25.请详述在...

    javaSE代码实例

    第2章 基本数据类型——构建Java 大厦的基础 12 2.1 源代码注释 12 2.1.1 单行注释 12 2.1.2 区域注释 12 2.1.3 文档注释 13 2.2 基本数据类型 14 2.2.1 整型 15 2.2.2 浮点型 17 2.2.3 char型 17...

    Scala程序设计(第2版)

    4.9 再谈case语句的变量绑定 104 4.10 再谈类型匹配 104 4.11 封闭继承层级与全覆盖匹配 105 4.12 模式匹配的其他用法 107 4.13 总结关于模式匹配的评价 111 4.14 本章回顾与下一章提要 111 第5章...

Global site tag (gtag.js) - Google Analytics