Java中==与equals区别

2024-04-23 19:50:19 浏览数 (3)

1 划重点

1.1 ==

比较运算符

  • 基本数据类型变量:比较的就是“值”是否相等。
  • 引用数据类型变量:比较的是对象的地址是否一样(排除特殊 String)。

1.2 equals()方法

Java.lang.Object类中的方法,比较对象是否相等

  • 基本数据类型:不能使用! 基本数据类型不是对象,不能够调用Object中的方法。
  • 引用数据类型 : 默认情况下,比较的是地址值。
  • 诸如String、Date等类对equals方法进行了重写的话,比较的是所指向的对象的内容。

而我们在实际开发中,自定义类:一般比较对象,都是通过对象的属性值进行比较,所以自定义类,会经常覆在自定义的类中写Object中的此方法,把自己的比较规则写在方法里面。

覆写equals方法的意义:定义类,覆写equals方法,自己制定规则,比较自定义类对象的静态属性。

代码语言:javascript复制
String str1 = new String("hello");
String str2 = new String("hello");

System.out.println(str1==str2);
System.out.println(str1.equals(str2));

//输出结果
false
true

为什么第4行和第5行的输出结果不一样?==和equals方法之间的区别是什么?如果在初学Java的时候这个问题不弄清楚,就会导致自己在以后编写代码时出现一些低级的错误。

2 关系操作符“==”到底比较的是什么?

下面这个句话是摘自《Java编程思想》一书中的原话:

“关系操作符生成的是一个boolean结果,它们计算的是操作数的值之间的关系”。

这句话看似简单,理解起来还是需要细细体会的,说的简单点,==就是用来比较值是否相等。接下来让我们看一组例子:

代码语言:javascript复制
int n=3;
int m=3;

System.out.println(n==m);

String str = new String("hello");
String str1 = new String("hello");
String str2 = new String("hello");

System.out.println(str1==str2);

str1 = str;
str2 = str;

System.out.println(str1==str2);

//输出结果
true
false
true

n==m输出结果为true,这个很容易理解,变量n和变量m存储的值都为3,肯定是相等的。

而为什么str1和str2两次比较的结果不同?要理解这个其实只需要理解基本数据类型变量和非基本数据类型变量的区别。

在Java中共有8种基本数据类型:

  • 浮点型:float(4 byte), double(8 byte)
  • 整型:byte(1 byte), short(2 byte), int(4 byte) , long(8 byte)
  • 字符型: char(2 byte)
  • 布尔型: boolean(JVM规范没有明确规定其所占的空间大小,仅规定其只能够取字面值"true"和"false")

对于这8种基本数据类型的变量,变量直接存储的是“值”,因此在用关系操作符==来进行比较时,比较的就是 “值” 本身。要注意浮点型和整型都是有符号类型的,而char是无符号类型的(char类型取值范围为0~2^16-1)。

也就是说,对于

代码语言:javascript复制
int n=3;
int m=3; 

变量n和变量m都是直接存储的"3"这个数值,所以用==比较的时候结果是true。

而对于非基本数据类型的变量,在一些书籍中称作为引用类型的变量。比如上面的str1就是引用类型的变量,引用类型的变量存储的并不是 “值”本身,而是于其关联的对象在内存中的地址。比如下面这行代码:

代码语言:javascript复制
String str1;

这句话声明了一个引用类型的变量,此时它并没有和任何对象关联。而是通过

代码语言:javascript复制
new String("hello")

来产生一个对象(也称作为类String的一个实例),并将这个对象和str1进行绑定:

代码语言:javascript复制
str1 = new String("hello");

那么str1指向了一个对象(很多地方也把str1称作为对象的引用),此时变量str1中存储的是它指向的对象在内存中的存储地址,并不是“值”本身,也就是说并不是直接存储的字符串"hello"。这里面的引用和C/C 中的指针很类似。

因此在用==对str1和str2进行第一次比较时,得到的结果是false。因为它们分别指向的是不同的对象,也就是说它们实际存储的内存地址不同。

而在第二次比较时,都让str1和str2指向了str指向的对象,那么得到的结果毫无疑问是true。

3 equals比较的又是什么?

equals方法是基类Object中的方法,因此对于所有的继承于Object的类都会有该方法。为了更直观地理解equals方法的作用,直接看Object类中equals方法的实现。

下面是Object类中equals方法的实现:

代码语言:javascript复制
/**
 * Indicates whether some other object is "equal to" this one.
 * <p>
 * The {@code equals} method implements an equivalence relation
 * on non-null object references:
 * <ul>
 * <li>It is <i>reflexive</i>: for any non-null reference value
 *     {@code x}, {@code x.equals(x)} should return
 *     {@code true}.
 * <li>It is <i>symmetric</i>: for any non-null reference values
 *     {@code x} and {@code y}, {@code x.equals(y)}
 *     should return {@code true} if and only if
 *     {@code y.equals(x)} returns {@code true}.
 * <li>It is <i>transitive</i>: for any non-null reference values
 *     {@code x}, {@code y}, and {@code z}, if
 *     {@code x.equals(y)} returns {@code true} and
 *     {@code y.equals(z)} returns {@code true}, then
 *     {@code x.equals(z)} should return {@code true}.
 * <li>It is <i>consistent</i>: for any non-null reference values
 *     {@code x} and {@code y}, multiple invocations of
 *     {@code x.equals(y)} consistently return {@code true}
 *     or consistently return {@code false}, provided no
 *     information used in {@code equals} comparisons on the
 *     objects is modified.
 * <li>For any non-null reference value {@code x},
 *     {@code x.equals(null)} should return {@code false}.
 * </ul>
 * <p>
 * The {@code equals} method for class {@code Object} implements
 * the most discriminating possible equivalence relation on objects;
 * that is, for any non-null reference values {@code x} and
 * {@code y}, this method returns {@code true} if and only
 * if {@code x} and {@code y} refer to the same object
 * ({@code x == y} has the value {@code true}).
 * <p>
 * Note that it is generally necessary to override the {@code hashCode}
 * method whenever this method is overridden, so as to maintain the
 * general contract for the {@code hashCode} method, which states
 * that equal objects must have equal hash codes.
 *
 * @param   obj   the reference object with which to compare.
 * @return  {@code true} if this object is the same as the obj
 *          argument; {@code false} otherwise.
 * @see     #hashCode()
 * @see     java.util.HashMap
 */
public boolean equals(Object obj) {
	return (this == obj);
}

很显然,在Object类中,equals方法是用来比较两个对象的引用是否相等,即是否指向同一个对象。

代码语言:javascript复制
String str1 = new String("hello");
String str2 = new String("hello");

System.out.println(str1.equals(str2));

//输出结果
true

但是,为什么上面一段代码的输出结果是true?

要知道究竟,可以看一下String类的equals方法的具体实现,String.java为String类的实现。

下面是String类中equals方法的具体实现:

代码语言:javascript复制
/**
 * Compares this string to the specified object.  The result is {@code
 * true} if and only if the argument is not {@code null} and is a {@code
 * String} object that represents the same sequence of characters as this
 * object.
 *
 * @param  anObject
 *         The object to compare this {@code String} against
 *
 * @return  {@code true} if the given object represents a {@code String}
 *          equivalent to this string, {@code false} otherwise
 *
 * @see  #compareTo(String)
 * @see  #equalsIgnoreCase(String)
 */
public boolean equals(Object anObject) {
    if (this == anObject) {
        return true;
    }
    if (anObject instanceof String) {
        String anotherString = (String)anObject;
        int n = value.length;
        if (n == anotherString.value.length) {
            char v1[] = value;
            char v2[] = anotherString.value;
            int i = 0;
            while (n-- != 0) {
                if (v1[i] != v2[i])
                    return false;
                i  ;
            }
            return true;
        }
    }
    return false;
}

可以看出,String类对equals方法进行了重写,用来比较指向的字符串对象所存储的字符串是否相等。其他的一些类诸如Double,Date,Integer等,都对equals方法进行了重写用来比较指向的对象所存储的内容是否相等。

4 堆和栈

栈与堆都是Java用来在Ram中存放数据的地方。与C 不同,Java自动管理栈和堆,程序员不能直接地设置栈或堆。

Java的堆是一个运行时数据区,类的对象从中分配空间。这些对象通过new、newarray、anewarray和 multianewarray等指令建立,它们不需要程序代码来显式的释放。堆是由垃圾回收来负责的,堆的优势是可以动态地分配内存大小,生存期也不必事先告诉编译器,因为它是在运行时动态分配内存的,Java的垃圾收集器会自动收走这些不再使用的数据。但缺点是,由于要在运行时动态分配内存,存取速度较慢。

栈的优势是,存取速度比堆要快,仅次于寄存器,栈数据可以共享。但缺点是,存在栈中的数据大小与生存期必须是确定的,缺乏灵活性。栈中主要存放一些基本类型的变量(int, short, long, byte, float, double, boolean, char)和对象句柄。栈有一个很重要的特殊性,就是存在栈中的数据可以共享。

假设我们同时定义:

代码语言:javascript复制
int a = 3;
int b = 3;

编译器先处理int a = 3;首先它会在栈中创建一个变量为a的引用,然后查找栈中是否有3这个值,如果没找到,就将3存放进来,然后将a指向3。接着处理int b = 3;在创建完b的引用变量后,因为在栈中已经有3这个值,便将b直接指向3。这样,就出现了a与b同时均指向3的情况。这时,如果再令a=4;那么编译器会重新搜索栈中是否有4值,如果没有,则将4存放进来,并令a指向4;如果已经有了,则直接将a指向这个地址。因此a值的改变不会影响到b的值。

要注意这种数据的共享与两个对象的引用同时指向一个对象的这种共享是不同的,因为这种情况a的修改并不会影响到b, 它是由编译器完成的,它有利于节省空间。而一个对象引用变量修改了这个对象的内部状态,会影响到另一个对象引用变量。

String是一个特殊的包装类数据。

可以用:

代码语言:javascript复制
String str = new String("abc"); 
String str = "abc"; 

两种的形式来创建,第一种是用new()来新建对象的,它会在存放于堆中。每调用一次就会创建一个新的对象。 而第二种是先在栈中创建一个对String类的对象引用变量str,然后查找栈中有没有存放"abc",如果没有,则将"abc"存放进栈,并令str指 向”abc”,如果已经有”abc” 则直接令str指向“abc”。

比较类里面的数值是否相等时,用equals()方法;当测试两个包装类的引用是否指向同一个对象时,用==,下面用例子说明上面的理论。

代码语言:javascript复制
String str1 = "abc"; 
String str2 = "abc"; 
System.out.println(str1==str2); 

//输出结果
true 

可以看出str1和str2是指向同一个对象的。

代码语言:javascript复制
String str1 =new String ("abc"); 
String str2 =new String ("abc"); 
System.out.println(str1==str2); 

//输出结果
false 

用new的方式是生成不同的对象,每一次生成一个。

因此用第二种方式(String str1 =“abc”)创建多个”abc”字符串,在内存中其实只存在一个对象而已. 这种写法有利与节省内存空间. 同时它可以在一定程度上提高程序的运行速度,因为JVM会自动根据栈中数据的实际情况来决定是否有必要创建新对象。而对于String str = new String(“abc”)的代码,则一概在堆中创建新对象,而不管其字符串值是否相等,是否有必要创建新对象,从而加重了程序的负担。

另一方面, 要注意: 我们在使用诸如String str = “abc”;的格式定义类时,总是想当然地认为,创建了String类的对象str。担心陷阱!对象可能并没有被创建!而可能只是指向一个先前已经创建的 对象。只有通过new()方法才能保证每次都创建一个新的对象。

由于String类的immutable性质,当String变量需要经常变换其值时,应该考虑使用StringBuffer类,以提高程序效率。

1 人点赞