CodeGym /Java 博客 /随机的 /比较 Java 中的字符串和等于比较
John Squirrels
第 41 级
San Francisco

比较 Java 中的字符串和等于比较

已在 随机的 群组中发布
你好!今天我们要聊一个很重要也很有趣的话题,就是对象与对象的比较(Compare Strings and 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。毕竟,引用car1car2指向两个不同的对象,即指向两个不同的地址。想象一下你在比较人的情况。当然,在世界的某个地方,有一个人的名字、眼睛颜色、年龄、身高、头发颜色等与你相同。这让你们在很多方面都很相似,但你们仍然不是双胞胎——而且你们显然不是同一个人。
等于和字符串比较 - 2
当我们使用==运算 符比较两个对象时,它使用的逻辑大致相同。但是,如果您需要您的程序使用不同的逻辑怎么办?例如,假设您的程序执行 DNA 分析。它比较两个人的遗传密码,并确定他们是否是双胞胎。

public class Man {

   int geneticCode;

   public static void main(String[] args) {

       Man man1 = new Man();
       man1.geneticCode = 1111222233;

       Man man2 = new Man();
       man2.geneticCode = 1111222233;

       System.out.println(man1 == man2);
   }
}
控制台输出: false 我们得到了相同的逻辑结果(因为我们没有做太多改变),但是现在逻辑不好了!毕竟,在现实生活中,DNA 分析应该给我们 100% 的保证,我们有双胞胎站在我们面前。但是我们的程序和==运算符告诉我们相反的情况。我们如何改变这种行为并确保程序在 DNA 匹配时输出正确的结果?Java 对此有一个特殊的方法:equals()与我们之前讨论的toString()方法一样,equals()属于Object类——Java 中最重要的类,所有其他类都从该类派生。但是等于()不会单独改变我们程序的行为:

public class Man {

   String geneticCode;

   public static void main(String[] args) {

       Man man1 = new Man();
       man1.geneticCode = "111122223333";

       Man man2 = new Man();
       man2.geneticCode = "111122223333";

       System.out.println(man1.equals(man2));
   }
}
控制台输出: false 完全一样的结果,那我们要这个方法干什么?:/ 这很简单。这里的问题是我们目前正在使用这个方法,因为它是在Object类中实现的。如果我们进入Object类的代码并查看方法的实现,这就是我们将看到的:

public boolean equals(Object obj) {
   return (this == obj);
}
这就是为什么程序的行为没有改变的原因!在Object类的equals()方法中使用了完全相同的==运算符(比较引用) 。但是这个方法的诀窍是我们可以覆盖它。覆盖意味着在我们的Man类中编写您自己的equals()方法,为它提供我们需要的行为!目前,我们不喜欢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 = "CodeGym is the best website for learning Java!";
       String s2 = new String("CodeGym is the best website for learning Java!");
       System.out.println(s1 == s2);
   }
}
控制台输出: false 但为什么我们得到 false?毕竟,字符串是完全一样的,逐字逐句:/ 你可能已经猜到原因了:这是因为==运算符比较引用!显然,s1s2在内存中的地址不同。如果你想到了这一点,那么让我们重写我们的例子:

public class Main {

   public static void main(String[] args) {

       String s1 = "CodeGym is the best website for learning Java!";
       String s2 = "CodeGym is the best website for learning Java!";
       System.out.println(s1 == s2);
   }
}
现在我们又得到了两个引用,但结果却恰恰相反: 控制台输出: 无奈困惑?让我们弄清楚发生了什么。==运算符确实比较内存地址。这始终是正确的,您无需怀疑。这意味着如果s1 == s2返回 true,则这两个字符串具有相同的地址。确实这是真的!是时候给大家介绍一个专门存放字符串的内存区域了:字符串池
等于和字符串比较 - 3
字符串池是一个区域,用于存储您在程序中创建的所有字符串值。为什么创建它?正如我们之前所说,字符串在所有对象中占很大比例。任何大型程序都会创建大量字符串。创建字符串池是为了节省内存:将字符串放在那里,随后创建的字符串引用同一块内存区域——不需要每次都分配额外的内存。每次你写String = "......."程序都会检查字符串池中是否有相同的字符串。如果存在,则不会创建新字符串。并且新引用将指向字符串池中的相同地址(相同字符串所在的位置)。所以当我们写

String s1 = "CodeGym is the best website for learning Java!";
String s2 = "CodeGym is the best website for learning Java!";
s2指向与s1相同的位置。第一条语句在字符串池中创建一个新字符串。第二条语句只是引用与s1相同的内存区域。你可以再制作 500 个相同的字符串,结果不会改变。等一下。如果那是真的,那为什么这个例子以前不起作用?

public class Main {

   public static void main(String[] args) {

       String s1 = "CodeGym is the best website for learning Java!";
       String s2 = new String("CodeGym is the best website for learning Java!");
       System.out.println(s1 == s2);
   }
}
我认为您的直觉已经告诉您原因 =) 在进一步阅读之前尝试猜测。您可以看到这两个字符串是以不同的方式声明的。一个有新运算符,另一个没有。原因就在这里。当使用new操作符创建一个对象时,它会强制为该对象分配一块新的内存区域。使用new创建的字符串不会在字符串池中结束——它成为一个单独的对象,即使它的文本与字符串池中的字符串完全匹配。也就是说,如果我们编写如下代码:

public class Main {

   public static void main(String[] args) {

       String s1 = "CodeGym is the best website for learning Java!";
       String s2 = "CodeGym is the best website for learning Java!";
       String s3 = new String("CodeGym is the best website for learning Java!");
   }
}
在记忆中,它看起来像这样:
等于和字符串比较 - 4
每次使用new创建一个新对象时,都会分配一个新的内存区域,即使新字符串中的文本是相同的!看来我们已经找到了==运算符。但是我们的新朋友equals()方法呢?

public class Main {

   public static void main(String[] args) {

       String s1 = "CodeGym is the best website for learning Java!";
       String s2 = new String("CodeGym is the best website for learning Java!");
       System.out.println(s1.equals(s2));
   }
}
控制台输出: 有趣。我们确定s1s2指向内存中的不同区域。但是equals()方法仍然告诉我们它们是相等的。为什么?还记得我们之前说过可以重写equals()方法来比较我们想要的对象吗?这正是他们对String类所做的。它覆盖了equals()方法。它不是比较引用,而是比较字符串中的字符序列。如果文本相同,那么它们是如何创建的或存储在何处都无关紧要:无论是在字符串池中还是在单独的内存区域中。比较的结果将为真。顺便说一句,Java 允许您执行不区分大小写的字符串比较。通常,如果其中一个字符串全部为大写字母,则比较结果将为假:

public class Main {

   public static void main(String[] args) {

       String s1 = "CodeGym is the best website for learning Java!";
       String s2 = new String("CODEGYM IS THE BEST WEBSITE FOR LEARNING JAVA!");
       System.out.println(s1.equals(s2));
   }
}
控制台输出: false 对于不区分大小写的比较,String类具有equalsIgnoreCase()方法。如果您只关心比较特定字符的序列而不是字母大小写,则可以使用它。例如,这在比较两个地址时可能会有所帮助:

public class Main {

   public static void main(String[] args) {

       String address1 = "2311 Broadway Street, San Francisco";
       String address2 = new String("2311 BROADWAY STREET, SAN FRANCISCO");
       System.out.println(address1.equalsIgnoreCase(address2));
   }
}
在这种情况下,我们显然是在谈论相同的地址,因此使用equalsIgnoreCase()方法是有意义的。

String.intern() 方法

String还有一个更棘手的方法:intern();intern ()方法直接与字符串池一起使用。如果您在某个字符串上 调用intern()方法:
  • 它检查字符串池中是否有匹配的字符串
  • 如果存在,则返回对池中字符串的引用
  • 如果不是,它将字符串添加到字符串池并返回对它的引用。
在对使用new获得的字符串引用使用intern()方法后,我们可以使用==运算符将其与字符串池中的字符串引用进行比较。

public class Main {

   public static void main(String[] args) {

       String s1 = "CodeGym is the best website for learning Java!";
       String s2 = new String("CodeGym is the best website for learning Java!");
       System.out.println(s1 == s2.intern());
   }
}
控制台输出: true当我们之前在没有intern() 的 情况下比较这些字符串时,结果为 false。现在intern()方法检查字符串“CodeGym is the best site for learning Java!” 在字符串池中。当然,它是:我们用

String s1 = "CodeGym is the best website for learning Java!";
我们检查s1和s2.intern()返回的引用是否指向相同的内存区域。当然,他们确实这样做了 :) 总之,记住并应用这条重要规则:始终使用 equals ()方法来比较字符串!比较字符串时,我们几乎总是要比较它们的字符,而不是比较引用、内存区域或其他任何东西。equals ()方法正是您所需要的。 为了巩固您所学的知识,我们建议您观看我们的 Java 课程中的视频课程

更多阅读:

评论
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION