你好!今天我们要讲一个非常重要且有趣的话题,即Java中对象之间的比较equals() 。事实上,在 Java 中什么情况下对象A等于对象B?让我们尝试写一个例子:
public class Car {
String model;
int maxSpeed;
public static void main(String[] args) {
Car car1 = new Car();
car1.model = "Ferrari";
car1.maxSpeed = 300;
Car car2 = new Car();
car2.model = "Ferrari";
car2.maxSpeed = 300;
System.out.println(car1 == car2);
}
}
控制台输出:
false
好吧,停下来。事实上,为什么这两辆车不相等?我们给了它们相同的属性,但比较的结果是错误的。答案很简单。该运算符==
比较的不是对象的属性,而是链接。即使两个对象有 500 个相同的属性,比较的结果仍然是 false。毕竟,链接car1
指向car2
两个不同的对象、两个不同的地址。想象一下比较人的情况。世界上很可能有一个人和你有相同的名字、眼睛颜色、年龄、身高、头发颜色等。也就是说,你们在很多方面都很相似,但仍然不是双胞胎,尤其不是同一个人。 当我们使用它来比较两个对象时,该运算符应用大致相同的逻辑。==
但是如果您的程序需要不同的逻辑怎么办?例如,如果您的程序模拟 DNA 分析。她必须比较两个人的 DNA 代码并确定他们是双胞胎。
public class Man {
int dnaCode;
public static void main(String[] args) {
Man man1 = new Man();
man1.dnaCode = 1111222233;
Man man2 = new Man();
man2.dnaCode = 1111222233;
System.out.println(man1 == man2);
}
}
控制台输出:
false
按逻辑,结果是一样的(毕竟我们没有改变任何东西),但现在我们对此不满意!确实,在现实生活中,DNA分析百分百保证我们面对的是双胞胎。但我们的程序和操作员==
告诉我们事实并非如此。我们怎样才能改变这种行为并确保如果 DNA 测试匹配,程序就会产生正确的结果?为此,Java 中创建了一个特殊的方法 - equals()。
Java 中的 equals() 方法
与我们前面讨论的方法一样toString()
,equals()属于类,Object
Java 中最重要的类,所有其他类都派生于该类。然而,equals() 本身不会以任何方式改变我们程序的行为:
public class Man {
String dnaCode;
public static void main(String[] args) {
Man man1 = new Man();
man1.dnaCode = "111122223333";
Man man2 = new Man();
man2.dnaCode = "111122223333";
System.out.println(man1.equals(man2));
}
}
控制台输出:
false
结果完全一样,那么为什么需要这个方法呢?:/ 这很简单。事实上,现在我们使用了这个方法,因为它是在类本身中实现的Object
。如果我们进入类代码Object
并查看该方法是如何在其中实现以及它的作用,我们将看到:
public boolean equals(Object obj) {
return (this == obj);
}
这就是我们程序的行为没有改变的原因!在类的 equals() 方法中Object
存在相同的引用比较==
. 但这个方法的技巧是我们可以重写它。覆盖意味着在我们的类中编写您自己的 equals() 方法Man
并使其按照我们想要的方式运行!现在我们不满意该检查man1.equals(man2)
本质上与 执行相同的操作man1 == man2
。在这种情况下我们会这样做:
public class Man {
int dnaCode;
public boolean equals(Man man) {
return this.dnaCode == man.dnaCode;
}
public static void main(String[] args) {
Man man1 = new Man();
man1.dnaCode = 1111222233;
Man man2 = new Man();
man2.dnaCode = 1111222233;
System.out.println(man1.equals(man2));
}
}
控制台输出:
true
完全不同的结果!通过编写我们自己的 equals() 方法而不是标准方法,我们实现了正确的行为:现在如果两个人具有相同的 DNA 代码,程序会告诉我们:“DNA 分析表明他们是双胞胎”并返回 true!通过重写类中的 equals() 方法,您可以轻松创建必要的对象比较逻辑。我们只是笼统地谈到了物体的比较。我们接下来还会有一个关于这个主题的 单独的大型讲座(如果您感兴趣,您现在可以快速阅读)。
Java 中的字符串比较 - 字符串比较
为什么我们将字符串比较与其他所有内容分开处理?嗯,事实上,编程中的台词是一个完全不同的故事。首先,如果你拿人类编写的所有Java程序来说,其中大约25%的对象是由它们组成的。因此,这个话题非常重要。其次,比较字符串的过程确实与其他对象有很大不同。让我们看一个简单的例子:public class Main {
public static void main(String[] args) {
String s1 = "JavaRush is the best site to learn Java!";
String s2 = new String("JavaRush is the best site to learn Java!");
System.out.println(s1 == s2);
}
}
控制台输出:
false
但为什么是假的呢?这些行是完全相同的,逐字逐句:/您可以假设:这是因为运算符 ==
比较引用!毕竟,s1
它们s2
在内存中具有不同的地址。如果你有这个想法,那么让我们重做我们的例子:
public class Main {
public static void main(String[] args) {
String s1 = "JavaRush is the best site to learn Java!";
String s2 = "JavaRush is the best site to learn Java!";
System.out.println(s1 == s2);
}
}
现在我们也有两个链接,但结果却变成了相反: 控制台输出:
true
完全困惑吗?:) 让我们弄清楚吧。该运算符==
实际上比较内存中的地址。这个规则总是有效的,没有必要怀疑它。这意味着如果s1 == s2
返回 true,则这两个字符串在内存中具有相同的地址。确实如此!现在是时候熟悉一个用于存储字符串的特殊内存区域了 - 字符串池 ( String pool
) 字符串池是一个用于存储您在程序中创建的所有字符串值的区域。它是为了什么而创建的?正如前面提到的,字符串占据了所有对象的很大一部分。在任何大型程序中,都会创建很多行。为了节省内存,这就是所需要的String Pool
- 一行您需要的文本放置在那里,以后新创建的链接引用相同的内存区域,无需每次分配额外的内存。每次写入时String = “........”
,程序都会检查字符串池中是否存在包含此类文本的行。如果有,则不会创建新的。并且新的链接将指向字符串池中存储该字符串的相同地址。因此,当我们在程序中写入
String s1 = "JavaRush is the best site to learn Java!";
String s2 = "JavaRush is the best site to learn Java!";
该链接s2
指向与 完全相同的位置s1
。第一个命令在字符串池中创建了一个新行,其中包含我们需要的文本,而当执行第二个命令时,它只是引用与 相同的内存区域s1
。您可以用相同的文本再制作至少 500 行,结果不会改变。停止。但为什么这个例子之前对我们不起作用呢?
public class Main {
public static void main(String[] args) {
String s1 = "JavaRush is the best site to learn Java!";
String s2 = new String("JavaRush is the best site to learn Java!");
System.out.println(s1 == s2);
}
}
我认为,凭直觉,您已经猜到原因是什么了:) 在进一步阅读之前尝试猜测。您可以看到这两行的创建方式不同。一是有操作员的帮助new
,二是没有操作员的帮助。正是这个原因。new运算符在创建对象时,会强制为其在内存中分配一块新的区域。并且用 , 创建的行new
不会以 结尾String Pool
:它成为一个单独的对象,即使它的文本与 'a. 中的同一行完全相同String Pool
。也就是说,如果我们编写以下代码:
public class Main {
public static void main(String[] args) {
String s1 = "JavaRush is the best site to learn Java!";
String s2 = "JavaRush is the best site to learn Java!";
String s3 = new String("JavaRush is the best site to learn Java!");
}
}
在内存中它看起来像这样: 每次创建一个新对象时,new
都会在内存中分配一个新区域,即使新行内的文本是相同的!我们似乎已经解决了运算符==
,但是我们的新朋友 - equals() 方法呢?
public class Main {
public static void main(String[] args) {
String s1 = "JavaRush is the best site to learn Java!";
String s2 = new String("JavaRush is the best site to learn Java!");
System.out.println(s1.equals(s2));
}
}
控制台输出:
true
有趣的。我们确切地知道什么s1
并s2
指向内存中的不同区域。但是,尽管如此,equals() 方法表明它们是相等的。为什么?还记得吗,上面我们说过可以在类中重写 equals() 方法,以便它按照您需要的方式比较对象?这就是他们在课堂上所做的String
。它有一个重写的 equals() 方法。它比较的不是链接,而是字符串中的字符序列。如果字符串中的文本相同,那么它们的创建方式和存储位置并不重要:在字符串池中,还是在单独的内存区域中。比较的结果将是真实的。顺便说一下,Java 允许您以不区分大小写的方式正确比较字符串。正常情况下,如果你写其中一行,例如大写,比较的结果将为 false:
public class Main {
public static void main(String[] args) {
String s1 = "JavaRush is the best site to learn Java!";
String s2 = new String("JAVARUSH - ЛУЧШИЙ САЙТ ДЛЯ ИЗУЧЕНИЯ JAVA!");
System.out.println(s1.equals(s2));
}
}
控制台输出:
false
对于这种情况,该类String
有一个方法equalsIgnoreCase()
。如果比较的主要内容是特定字符的序列,而不是大小写,则可以使用它。例如,这在比较两个电子邮件地址时非常有用:
public class Main {
public static void main(String[] args) {
String address1 = "Moscow, Academician Korolev street, 12";
String address2 = new String("Г. МОСКВА, УЛ. АКАДЕМИКА КОРОЛЕВА, ДОМ 12");
System.out.println(address1.equalsIgnoreCase(address2));
}
}
在这种情况下,很明显我们正在谈论相同的地址,因此使用该方法equalsIgnoreCase()
将是正确的决定。
String.intern() 方法
该类String
还有另一个棘手的方法 - intern()
; 该方法intern()
直接与String Pool
'om. intern()
如果您在字符串上 调用方法,它会:
- 查看字符串池中是否存在带有此文本的字符串
- 如果存在,则返回池中指向它的链接
- 如果没有,它会在字符串池中放置一行包含此文本的行,并返回指向该文本的链接。
intern()
通过 new 创建的字符串引用,我们可以将其与通过String Pool
'a 的字符串引用进行比较==
。
public class Main {
public static void main(String[] args) {
String s1 = "JavaRush is the best site to learn Java!";
String s2 = new String("JavaRush is the best site to learn Java!");
System.out.println(s1 == s2.intern());
}
}
控制台输出:
true
以前,当我们在没有 的情况下比较它们时intern()
,结果是错误的。现在,该方法intern()
检查是否存在包含文本“JavaRush - 学习 Java 的最佳网站!”的行。在字符串池中。当然它就在那里:我们在编写时创建了它
String s1 = "JavaRush is the best site to learn Java!";
经过检查,引用s1
和方法返回的引用s2.intern()
是否指向内存中的同一区域,当然,它们确实如此:) 总结一下,记住并使用主要规则:要比较字符串,始终使用 equals()方法!比较字符串时,几乎总是指比较它们的文本,而不是引用、内存区域等。equals() 方法正是您所需要的。以下是一些链接供大家自行学习:
GO TO FULL VERSION