在Java中比较字符、整数和类似类型:使用equals还是==?

11
我想确认一下Java中的某些内容: 如果我有一个Character、Integer或Long等类型的对象,我应该使用equals方法还是只使用==运算符就足够了?
我知道对于字符串来说,没有保证每个唯一字符串都只有一个实例,但我不确定其他包装类型是否也是这样。
我的直觉是使用equals方法,但我想确保不会浪费性能。

永远不要假设性能 - 进行测试。也许“==”和“.equals”具有大致相同的性能,在这种情况下,问题就毫无意义了。 - TofuBeer
6
关于性能,唯一需要假设的是:如果代码有误但更快,并不重要。 :-) - Darron
这就是我想要确保的。 - Uri
1
关于“我知道对于字符串来说,不能保证每个唯一的字符串只有一个实例”:如果出于某种原因你需要这样做,请参考String#intern() - Arjan
9个回答

31

编辑:规范保证了一些装箱转换的情况。第5.1.7节

如果正在装箱的值 p 为 true、false、位于 \u0000 到 \u007f 范围内的 byte、char 类型或在 -128 到 127 之间的 int 或 short 数字,则令 r1 和 r2 分别为 p 的任意两个装箱转换的结果。恒有 r1 == r2。

当然,实现可以使用更大的池。

我真的建议避免编写依赖此功能的代码。不是因为它可能会失败,而是因为它不明显 - 很少有人会那么熟悉规范。(我先前认为这取决于实现.)

应该使用 equals 或比较基础值,即:

if (foo.equals(bar))
或者
if (foo.intValue() == bar.intValue())
请注意,即使自动装箱保证使用固定的值,其他调用者仍然可以创建单独的实例。

据我所知,JLS规定byte、short和int的取值范围至少为-128到127,char的等效范围相同,但long、float或short没有。据我所知,6u14及以前的性能版本允许通过系统属性配置池大小。 - Tom Hawtin - tackline
哇,你说得对。我本来以为它可以选择使用池或不使用池。我会进行编辑的。 - Jon Skeet
@Tom 你提到了short两次。实际上,short和long有相同大小的池子。你可以说Boolean总是被池化了。 - Peter Lawrey

7
如果你想比较任意两个对象是否相等,使用.equals()
即使这些对象是原始包装类型Byte, Character, Short, Integer, Long, Float, DoubleBoolean,也要这样做(尤其是)。
对于对象,“==”仅比较对象的标识,这很少是你想要的。在实际上永远不会使用原始包装器。
只在以下两种情况之一中使用 ==
  1. 涉及比较的所有值都是原始类型(最好不是浮点数)
  2. 您真的想知道两个引用是否引用同一个对象(这包括比较enum,因为该值绑定到对象标识)

场景3:您正在比较枚举实例或其他管理其实例的不可变类型,以使“==”是安全的。 - Eddie
你的第三种情况与第二种情况相同。 - TofuBeer
@TofuBeer:从技术上讲,是的,但很多人不会这么想。 - Eddie

5

Java语言规范5.1.7

如果要装箱的值 p 是 true、false、一个字节、Unicode 编码在 \u0000 到 \u007f 范围内的字符,或者一个介于 -128 和 127 之间的整数或短整型数,则让 r1 和 r2 分别是 p 进行两次装箱转换的结果。那么 r1 == r2 总是成立。

并且:

讨论

理想情况下,对给定的基本类型值 p 进行装箱总是产生相同的引用。但实际上,使用现有的实现技术可能无法做到这一点。上述规则是一种实际妥协方案。上面的最后一条子句要求将某些常见的值始终装箱为不可区分的对象。实现可以懒惰地或即时地缓存它们。

对于其他值,此公式禁止程序员在包装值的标识上做出任何假设。这允许(但不强制)共享某些或所有这些引用。

这确保在大多数常见情况下,行为将是所需的行为,而不会对小型设备产生过度的性能损失。内存限制较少的实现可能会缓存所有字符和短整型数,以及范围在 -32K 到 +32K 范围内的整数和长整型数。

因此,在某些情况下,"==" 运算符可以正常工作,但在许多其他情况下,它无法正常工作。始终使用 .equals 是安全的,因为您通常无法保证实例是如何创建的。

如果速度很重要(大多数 .equals 方法都以 "==" 运算符比较,或者至少应该这样做),而且您可以保证它们是如何分配的并且它们符合以上范围,则 "==" 是安全的。

一些虚拟机可能会增加这个范围的大小,但除非您确实需要这样做,否则最好假设语言规范指定的最小大小比依赖于特定的虚拟机行为更为安全。


4
//Quick test
public class Test {
  public static void main(String[] args) {
    System.out.println("Are they equal? "+ (new Long(5) == new Long(5)));
  }
}

输出:

"它们相等吗?0"

答案:

不,它们不相等。您必须使用.equals方法或比较它们的基本值。


Long.valueOf(5) == Long.valueOf(5) 怎么样? - Maurice Perry
new Long(...) 每次都会给你一个 不同 的引用。这并不令人惊讶。但是 Long.valueOf(...) 会池化引用。就像 Maurice 所说,你的测试在那种情况下不正确。 - Varkhan
由于您可能不知道Long是如何获得的,使用==几乎永远不安全。 - TofuBeer
确实,TofuBeer是正确的。特别是他在回答中提到的方法,肯定有办法获得两个完全相同的Longs。但是依赖这些方法仍然是一个糟糕的想法,因为如果您稍后更改了这些Longs,您不会考虑如何比较它们的相等性也发生了变化。 - Brandon Yarbrough
CaptainAwesomePants只需要展示一个==失败的实例,就可以证明你不能普遍依赖==,即使有一些情况下它能够工作。 - Kip

2

在实现equals(Object o)方法时,几乎总是以以下方式开始:

if(this == o) return true;

因此,即使使用 == 返回 true,使用 equals 也不会对性能造成太大的影响。

我建议始终对对象使用 equals 方法。

* 当然,在极少数情况下,您不应遵循此建议。


1

一般来说,保证对于相同的数值,您得到的Long对象是相同的(即使您限制自己使用Long.valueOf())。

然而,通过首先尝试测试引用的相等性(使用==),然后如果失败,则尝试equals(),可能会获得性能提升。这完全取决于额外的==测试和方法调用的比较成本...您的结果可能会有所不同,但值得尝试一个简单的循环测试以查看哪个更好。


1
值得注意的是,如果可用,自动装箱的值将使用池化对象。这就是为什么在Java 6u13中(Integer)0 ==(Integer)0,但(Integer)128!=(Integer)128的原因。

1
我喜欢通过视觉看到结果:

  public static void main(String[] args)
  {
        Integer a = 126; //no boxed up conversion, new object ref
        Integer b = 126; //no boxed up conversion, re-use memory address
        System.out.println("Are they equal? " + (a == b)); // true
        Integer a1 = 140; //boxed up conversion, new object
        Integer b1 = 140; //boxed up conversion, new object
        System.out.println("Are they equal? " + (a1 == b1)); // false
        System.out.println("Are they equal? " + (new Long(5) == new Long(5))); // false
  }

0

== 比较对象引用,而 equals(Object obj) 比较对象相等性。如果有可能存在多个相等的对象实例,那么你必须使用 equals 进行相等性比较。

示例:

Integer i1 = new Integer(12345);
Integer i2 = new Integer(12345);

这些是不同的对象实例,但根据 Integer 的相等性它们是相等的,因此您必须使用 equals(Object obj)

public enum Gender {
    MALE, FEMALE;
}

在这种情况下,只会存在一个FEMALE实例,因此使用==是安全的。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接