比较深拷贝时,Java 深度比较返回 false

Java Deep Comparison Returns False when Comparing a Deep Copy

提问人:DantesExile 提问时间:3/5/2021 更新时间:3/5/2021 访问量:624

问:

我创建了一个抽象类 Fruit,它覆盖了 equals() 方法。然后我创建了一个子类 Orange,它覆盖了 copy() 和 equals() 方法。TestFruit.java,在我的测试文件中,我正在创建一个橙子数组并测试它们的方法。我正在尝试创建橙色的深度副本,并在父橙色和副本之间进行深度比较。但是,在我的输出中,比较始终返回 false。我检查了父级和副本的属性,它们似乎确实相同。任何指示将不胜感激。我对 Java 和复制很陌生。我在下面附上了我的代码。

Fruit.java:

package juicer;

import copy.Copyable;

public abstract class Fruit implements Copyable, Cloneable
{
    private double mass;
    private boolean isJuicedRemoved;

    protected Fruit(double theMass)
            throws IllegalMassException
    {
        {
            if (theMass <= 0)
            {
                throw new IllegalMassException(theMass);
            }
            else
            {
                this.mass = theMass;
                this.isJuicedRemoved = false;
            }
        }
    }

    protected Fruit(Fruit fruit)
    {
        this.mass = fruit.mass;
        this.isJuicedRemoved = fruit.isJuicedRemoved;
    }

    public double getMass()
    {
        return mass;
    }

    public boolean getIsJuicedExtracted()
    {
        return isJuicedRemoved;
    }

    protected void setMass(double value)
    {
        this.mass = value;
    }

    protected abstract double juiceRatio();

    public double extractJuice()
    {
        double liquidMass = amountJuice();

        if (!isJuicedRemoved)
        {
            isJuicedRemoved = true;
            mass -= liquidMass;
        }

        return liquidMass;
    }

    public double amountJuice()
    {
        if (isJuicedRemoved) return 0.0;

        return mass * juiceRatio();
    }

    @Override
    public boolean equals(Object obj)
    {
        // Steps to override the equals() method():

        // Step 1: Test if obj is an instance of Fruit.
        //         If it is not, then return false.
        if (!(obj instanceof Fruit)) return false;

        // Step 2: Cast obj to an Fruit.
        Fruit rhs = (Fruit)obj;

        // Step 3: Test if the data fields of the invoking object are
        //         equal to the ones in rhs using a deep comparison
        //         and return this result.
        return super.equals(obj) && // test for equality in the super class
                mass == rhs.mass &&
                isJuicedRemoved == rhs.isJuicedRemoved;
    }

    @Override
    public int hashCode()
    {
        int result = super.hashCode();
        result = 31*result + Double.hashCode(mass);
        result = 31*result + Boolean.hashCode(isJuicedRemoved);
        return result;
    }

    @Override
    public Object clone() throws CloneNotSupportedException
    {
        Fruit objectClone = (Fruit)super.clone();

        objectClone.mass = mass;
        objectClone.isJuicedRemoved = isJuicedRemoved;

        return objectClone;
    }

    @Override
    public String toString()
    {
        return "\tmass = " + mass +
                "\n\tisJuiceExtracted = " + isJuicedRemoved + "\n";
    }
}

Orange.java:

package juicer;

public class Orange extends Fruit
{
    public Orange(double mass)
    {
        super(mass);
    }

    // copy constructor
    public Orange(Orange other)
    {
        super(other);
    }

    @Override
    protected double juiceRatio()
    {
        return 0.87;
    }

    @Override
    public boolean equals(Object obj)
    {
        // Steps to override the equals() method():

        // Step 1: Test if obj is an instance of Orange.
        //         If it is not, then return false.
        if (!(obj instanceof Orange)) return false;

        // Step 2: Cast obj to an Orange.
        // This step is not needed since the only data fields this
        // class has are the ones it inherits.

        // Step 3: Test if the data fields of the invoking object are
        //         equal to the ones in rhs using a deep comparison
        //         and return this result.
        return super.equals(obj);
    }

    @Override
    public Object copy()
    {
        return new Orange(this);
    }

    @Override
    public String toString()
    {
        return "Orange:\n" + super.toString();
    }
}

TestFruit.java:

package test;

import juicer.*;
import java.util.Random;

public class TestFruit
{
    public static void main(String[] args)
    {
        Orange[] oranges = new Orange[1];

        //Random double generator for mass
        Random rd = new Random();

        //create oranges
        for (int i = 0; i <= oranges.length - 1; i++ )
        {
            oranges[i] = new Orange(rd.nextDouble());
        }

        for (Orange orange : oranges)
        {
            Orange orangeCopy = new Orange(orange);

            if (orange == orangeCopy)
            {
                System.out.print("The comparison is true!");
            }
            else
            {
                System.out.print("Does not match.");
            }
        }

    }
}
Java Compare 覆盖 copy-constructor deep-copy

评论


答:

2赞 rb612 3/5/2021 #1

Java 中常见的误解之一是使用 vs 。当您在 Java 中比较两个对象时,它在内部比较其内存地址。 实际上并不调用 .==.equals()====.equals()

在这种情况下,您有两个不同的橙色对象,因此比较将始终返回 false。

如果你使用 ,那么它实际上会调用你实现的方法。a.equals(b)equals

正如@Andreas在评论中指出的那样,还有另一个问题。调用 将调用 的超类实现,而 的超类是 。 行为与(即也检查引用相等性)相同。重写并非易事,因此让 IDE 为您生成重写通常是一件好事。super.equals(obj)FruitequalsFruitObjectObject.equals()==.equals()

与C++等语言相比,Java没有运算符重载。这意味着您不能为 定义不同的实现。这就是为什么在比较任何非基元类型时始终调用的最佳做法(除非您显式检查引用相等性,这种情况很少见)。==.equals()

评论

0赞 Andreas 3/5/2021
当然,由于实现错误地调用了 ,结果会和使用 一样。equals()Object.equals()==
0赞 rb612 3/5/2021
@Andreas,啊,好点子——是的,在超类中将调用,它也会根据对象身份进行比较。super.equals(obj)FruitObject.equals()