如何在Java中覆盖equals方法


108

我试图覆盖Java中的equals方法。我有一堂课People,基本上有2个数据字段nameage。现在,我想重写equals方法,以便可以在2个People对象之间进行检查。

我的代码如下

public boolean equals(People other){
    boolean result;
    if((other == null) || (getClass() != other.getClass())){
        result = false;
    } // end if
    else{
        People otherPeople = (People)other;
        result = name.equals(other.name) &&  age.equals(other.age);
    } // end else

    return result;
} // end equals

但是当我写age.equals(other.age)它给我错误时,因为equals方法只能比较String并且age是Integer。

==按照建议使用运算符,问题解决了。


3
嘿this.age == other.age怎么样?:)
denis.solonenko

1
年龄的数据类型是什么?整数还是整数?另外,您正在使用哪个版本的JDK?
Manish

2
“ as equals方法只能比较字符串”-谁告诉您equals方法只能比较字符串?equals方法属于Object类,默认情况下,任何创建的类都将具有equals实现。您可以在任何Java类上调用equals
Manish

Answers:


127
//Written by K@stackoverflow
public class Main {

    /**
     * @param args the command line arguments
     */
    public static void main(String[] args) {
        // TODO code application logic here
        ArrayList<Person> people = new ArrayList<Person>();
        people.add(new Person("Subash Adhikari", 28));
        people.add(new Person("K", 28));
        people.add(new Person("StackOverflow", 4));
        people.add(new Person("Subash Adhikari", 28));

        for (int i = 0; i < people.size() - 1; i++) {
            for (int y = i + 1; y <= people.size() - 1; y++) {
                boolean check = people.get(i).equals(people.get(y));

                System.out.println("-- " + people.get(i).getName() + " - VS - " + people.get(y).getName());
                System.out.println(check);
            }
        }
    }
}

//written by K@stackoverflow
    public class Person {
        private String name;
        private int age;

        public Person(String name, int age){
            this.name = name;
            this.age = age;
        }

        @Override
        public boolean equals(Object obj) {
            if (obj == null) {
                return false;
            }

            
if (obj.getClass() != this.getClass()) {
                return false;
            }



            final Person other = (Person) obj;
            if ((this.name == null) ? (other.name != null) : !this.name.equals(other.name)) {
                return false;
            }

            if (this.age != other.age) {
                return false;
            }

            return true;
        }

        @Override
        public int hashCode() {
            int hash = 3;
            hash = 53 * hash + (this.name != null ? this.name.hashCode() : 0);
            hash = 53 * hash + this.age;
            return hash;
        }

        public int getAge() {
            return age;
        }

        public void setAge(int age) {
            this.age = age;
        }

        public String getName() {
            return name;
        }

        public void setName(String name) {
            this.name = name;
        }
    }

输出:

跑:

-Subash Adhikari-VS-K假

-Subash Adhikari-VS-StackOverflow否

-Subash Adhikari-VS-Subash Adhikari是

-K-VS-StackOverflow错误

-K-VS-Subash Adhikari错误

-StackOverflow-VS-Subash Adhikari错误

-建立成功(总时间:0秒)


7
什么是hash = 53 * hash为什么你正在使用?
kittu 2015年

2
getClass()如果该类被子类化并与超类的对象进行比较,则使用会引起问题。
Tuxdude

1
可能是bcoz 53质数,请看此答案stackoverflow.com/a/27609/3425489,他在选择“ hashCode()
Shantaram Tupe”中的

1
这个问题的成功答案很好地解释了为什么您覆盖hashCode()stackoverflow.com/a/27609/1992108
Pegasaurus

7
考虑使用if(getClass()!= obj.getClass())...而不是使用instanceof运算符或isAssignableFrom。这将需要精确的类型匹配,而不是子类型匹配。-对称要求。也String可以使用比较其他对象类型Objects.equals(this.name,other.name)
YoYo

22

引入一种可以更改参数类型的新方法签名称为重载

public boolean equals(People other){

People与有所不同Object

当一个方法签名与其父类的签名相同时,它被称为覆盖(overriding),并且@Override注释有助于在编译时将二者区分开:

@Override
public boolean equals(Object other){

没有看到的实际声明age,很难说出错误出现的原因。


18

我不确定细节,因为您还没有发布整个代码,但是:

  • 记得覆盖hashCode(),以及
  • equals方法应具有Object,而不应People作为其参数类型。目前,您正在重载而不是覆盖equals方法,这可能不是您想要的,尤其是考虑到以后要检查其类型的情况。
  • 您可以使用instanceof它来检查它是一个People对象,例如if (!(other instanceof People)) { result = false;}
  • equals用于所有对象,但不用于基元。我认为您的意思是年龄是int(原始)年龄,在这种情况下,请使用==。请注意,整数(以大写的“ I”表示)是一个对象,应与等值进行比较。

请参阅在Java中重写equals和hashCode时应考虑哪些问题?更多细节。


12
@Override
public boolean equals(Object that){
  if(this == that) return true;//if both of them points the same address in memory

  if(!(that instanceof People)) return false; // if "that" is not a People or a childclass

  People thatPeople = (People)that; // than we can cast it to People safely

  return this.name.equals(thatPeople.name) && this.age == thatPeople.age;// if they have the same name and same age, then the 2 objects are equal unless they're pointing to different memory adresses
}

12

条款10:在重载等于时遵守总合同

根据Effective Java的说法,重写该equals方法似乎很简单,但是有很多方法可以弄错它,并且后​​果可能非常严重。避免问题的最简单方法是不重写该equals方法,在这种情况下,该类的每个实例仅等于其自身。如果满足以下任一条件,这是正确的做法:

  • 该类的每个实例本质上都是唯一的。对于表示活动实体而不是值的类(例如Thread),这是正确的。Object提供的equals实现对于这些类具有完全正确的行为。

  • 该类无需提供“逻辑相等”测试。例如,java.util.regex.Pattern可以重写等于以检查两个Pattern实例是否表示完全相同的正则表达式,但是设​​计人员认为客户端不需要或不需要此功能。在这种情况下,从Object继承的equals实现是理想的。

  • 一个超类已经覆盖了equals,并且超类行为适合于该类。例如,大多数Set实现从AbstractSet继承其equals实现,从AbstractList继承List的实现,从AbstractMap继承Map的实现。

  • 该类是private或package-private,并且您可以肯定不会调用其equals方法。如果您极力规避风险,则可以覆盖equals方法以确保它不会被意外调用:

equals方法实现了等价关系。它具有以下属性:

  • 自反:对于任何非null的参考值xx.equals(x)必须返回true。

  • 对称的:对于任何非空引用值xyx.equals(y)当且仅当y.equals(x)返回true时,才必须返回true。

  • 传递性:对于任何非空的参考值xyz,如果x.equals(y)回报率truey.equals(z)回报率true,那么x.equals(z)必须返回true

  • 一致:对于任何非空引用值xyx.equals(y)必须多次返回true或一致返回false,前提是未修改equals比较中使用的信息。

  • 对于任何非null的参考值xx.equals(null)必须返回false

这是高质量equals方法的秘诀:

  1. 使用==运算符检查自变量是否为此对象的引用。如果是这样,则返回true。这只是一项性能优化,但是如果比较可能会很昂贵,那么就值得这样做。

  2. 使用instanceof运算符检查参数是否具有正确的类型。如果不是,则返回false。通常,正确的类型是方法所在的类。有时,它是此类所实现的一些接口。如果该类实现的接口细化了equals约定,以允许在实现该接口的类之间进行比较,请使用接口。集合接口(例如Set,List,Map和Map.Entry)具有此属性。

  3. 将参数强制转换为正确的类型。因为此强制转换之前是一个instanceof测试,所以可以保证成功。

  4. 对于类中的每个“重要”字段,请检查参数的该字段是否与该对象的相应字段匹配。如果所有这些测试都成功,则返回true;否则,返回true。否则,返回false。如果步骤2中的类型是接口,则必须通过接口方法访问参数的字段;否则,必须为0。如果类型是类,则可以根据其可访问性直接访问这些字段。

  5. 对于类型不是floator的原始字段double,请使用==运算符进行比较;对于对象引用字段,请equals递归调用该方法;对于float字段,请使用静态Float.compare(float, float)方法;对于double字段,请使用Double.compare(double, double)。浮点和双精度字段的特殊处理是由存在的作了必要的Float.NaN-0.0f并且类似的双值; 虽然您可以使用静态方法和来比较floatdouble字段,但这将需要在每次比较时进行自动装箱,这会导致性能下降。对于字段,请将这些准则应用于每个元素。如果数组字段中的每个元素都很重要,请使用其中一种方法。Float.equalsDouble.equalsarrayArrays.equals

  6. 某些对象引用字段可能合法包含null。为了避免出现a的可能性NullPointerException,请使用静态方法检查此类字段的相等性Objects.equals(Object, Object)

    // Class with a typical equals method
    
    public final class PhoneNumber {
    
        private final short areaCode, prefix, lineNum;
    
        public PhoneNumber(int areaCode, int prefix, int lineNum) {
    
            this.areaCode = rangeCheck(areaCode,  999, "area code");
    
            this.prefix   = rangeCheck(prefix,    999, "prefix");
    
            this.lineNum  = rangeCheck(lineNum,  9999, "line num");
    
        }
    
        private static short rangeCheck(int val, int max, String arg) {
    
            if (val < 0 || val > max)
    
               throw new IllegalArgumentException(arg + ": " + val);
    
            return (short) val;
    
        }
    
        @Override public boolean equals(Object o) {
            if (o == this)
                return true;
            if (!(o instanceof PhoneNumber))
                return false;
            PhoneNumber pn = (PhoneNumber)o;
            return pn.lineNum == lineNum && pn.prefix == prefix
                    && pn.areaCode == areaCode;
        }
        ... // Remainder omitted
    
    }

1
不要忘记提及您也必须重写hashCode()。还要注意,因为Java7文字equals()hashCode()方法已经使用变得更加容易Objects.equals()Arrays.equals()并且Objects.hashCode()Arrays.hashCode()
阿诺德·施里弗

3
考虑使用if (getClass() != obj.getClass()) ...而不是使用instanceof运算符。这将需要精确的类型匹配,而不是子类型匹配。-对称要求。
YoYo '18年

@YoYo是正确的...使用instanceof可能会使对称属性失败。如果o是PhoneNumber的子类(例如PhoneNumberWithExtension),并且通过使用instanceof以相同的方式覆盖equals,则o.equals(this)将使instanceof测试失败,而PhoneNumber.equals将通过它并返回true(假设所有其他PhoneNumber字段相等)。
ldkronos

5

由于我猜测age是类型int

public boolean equals(Object other){
    boolean result;
    if((other == null) || (getClass() != other.getClass())){
        result = false;
    } // end if
    else{
        People otherPeople = (People)other;
        result = name.equals(otherPeople.name) &&  age == otherPeople.age;
    } // end else

    return result;
} // end equals

这将导致NullPointerExceptionif nameis null
orien 2011年

@orien没什么大不了的,也许是合同中name从来没有分配null价值...
fortran

@fortran所以...也许这没什么大不了;)
orien

5

在Java中比较对象时,可以进行语义检查,将对象的类型和标识状态比较为:

  • 本身(相同实例)
  • 本身(克隆或重建的副本)
  • 不同类型的其他对象
  • 其他相同类型的对象
  • null

规则:

  • 对称性a.equals(b) == b.equals(a)
  • equals()总会让步truefalse,但从来没有一个NullpointerExceptionClassCastException或任何其他抛出

比较:

  • 类型检查:两个实例必须具有相同的类型,这意味着您必须比较实际的类是否相等。当开发人员instanceof用于类型比较时,这种方法通常无法正确实现(仅在没有子类的情况下才起作用,而在时违反对称规则)A extends B -> a instanceof b != b instanceof a)
  • 标识状态的语义检查:确保您了解实例被标识为哪种状态。可以通过社会保险号来识别人员,但是不能通过头发的颜色(可以染成色),姓名(可以改变)或年龄(一直在改变)来识别。仅应与值对象比较完整状态(所有非瞬态字段),否则仅检查标识该实例的内容。

对于您的Person班级:

public boolean equals(Object obj) {

    // same instance
    if (obj == this) {
        return true;
    }
    // null
    if (obj == null) {
        return false;
    }
    // type
    if (!getClass().equals(obj.getClass())) {
        return false;
    }
    // cast and compare state
    Person other = (Person) obj;
    return Objects.equals(name, other.name) && Objects.equals(age, other.age);
}

可重用的通用实用程序类:

public final class Equals {

    private Equals() {
        // private constructor, no instances allowed
    }

    /**
     * Convenience equals implementation, does the object equality, null and type checking, and comparison of the identifying state
     *
     * @param instance       object instance (where the equals() is implemented)
     * @param other          other instance to compare to
     * @param stateAccessors stateAccessors for state to compare, optional
     * @param <T>            instance type
     * @return true when equals, false otherwise
     */
    public static <T> boolean as(T instance, Object other, Function<? super T, Object>... stateAccessors) {
        if (instance == null) {
            return other == null;
        }
        if (instance == other) {
            return true;
        }
        if (other == null) {
            return false;
        }
        if (!instance.getClass().equals(other.getClass())) {
            return false;
        }
        if (stateAccessors == null) {
            return true;
        }
        return Stream.of(stateAccessors).allMatch(s -> Objects.equals(s.apply(instance), s.apply((T) other)));
    }
}

对于您的Person班级,请使用以下实用程序类:

public boolean equals(Object obj) {
    return Equals.as(this, obj, t -> t.name, t -> t.age);
}

1

如果age是int,则应使用==(如果它是Integer对象),则可以使用equals()。如果您覆盖equals,则还需要实现hashcode方法。合同的详细信息可以在Object的javadoc中找到,也可以在Web的各个页面上找到。


0

这是我最近使用的解决方案:

public class Test {
    public String a;
    public long b;
    public Date c;
    public String d;
    
    @Override
    public boolean equals(Object obj) {
        if (this == obj) {
            return true;
        }
        if (!(obj instanceof Test)) {
            return false;
        }
        Test testOther = (Test) obj;
        return (a != null ? a.equals(testOther.a) : testOther.a == null)
                && (b == testOther.b)
                && (c != null ? c.equals(testOther.c) : testOther.c == null)
                && (d != null ? d.equals(testOther.d) : testOther.d == null);
    }

}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.