谨慎重置equals方法

来源:互联网 发布:chart.js饼图显示数值 编辑:程序博客网 时间:2024/04/30 01:23

谨慎重置equals方法

Javaequals方法是一个很重要的方法,也是一个相对容易出错的地方。本片文章主要以实例来重点讲述如何实现,已经在实现中所要注意的几个问题。希望能够读者带来一些启发,减小出错的概率。

在介绍equals方法实现之前,我们有所必要了解一些基本的概念。Equals方法顾名思义,就是比较两个对象是否是等价。它必须遵守五个条件。

1)反身性(reflexive)。简单的说就是如果有一个非空对象A,那么必定存在如下关系: A.equals( A )返回一定是true

2)对称性(symmetric)。比如存在两个非空对象AB。如果A.equals( B )返回true,那么必定B.equals( A )也返回true

3)传递性(transitive)。比如存在三个非空对象ABC。如果A.equals( B )返回trueB.equals( C )返回true,那么A.equals( C )也一定是返回true

4)一致性(consistent)。对于存在的非空对象ABA.equals( B )在无论什么外在条件下,都应该一直返回true或者false

5)非空性。对于非空对象A,在任何条件下,A.equals( null )一直返回false

在正常的情况下,equals方法的实现应用了等于方法,也就是说,对于对象AB,只有并且仅仅只有AB是同一个对象才是等价。这个是默认的Object类的实现方法。在有些时候,我们要求equals方法是等价方法,而非等于方法,这时候equals方法应该被重置。在Java定义的Object对象中,其实equals方法原本的意义就是等价方法,而非等于,这点一定要记住,不要被表面的英语词汇所迷惑。个人感觉,可能equals方面这个名字起得不够好,equivalence之类的名字可能更加的容易理解。千万要记住一点:equals方面是等价方法而非等于方法!此外我们还可以改注意,在重置equals方法的时候,一定也要重置类中的hashCode方法。因为如果两个对象等价,那么它的hashCode方法返回的值必定要相同。当然没有硬性规定,不同对象的hashCode方法返回的值一定不相等。我个人建议如果两个对象不相等,hashCode返回的值最好也不相等。因为如果这样,那么当使用Hashtable的时候,效率提高是显然的。难道不是吗?!J

读者在阅多了上面相关信息后,应该对equals方面和其相关的东西有了一个基本的认识。下面我讲讲述如何来正确的时相equals方法。

1Point1D的定义

public class Point1D

{

    protected int x;

 

    public Point1D( int x )

    {

        this.x = x;

    }

 

    public boolean equals( Object o )

    {

        if ( !(o instanceof Point1D) )

            return false;

 

        return x == ((Point1D) o).x;

    }

 

    public int hashCode()

    {

        return super.hashCode() * 31 + x;

    }

}

2Point2D的定义

public class Point2D extends Point1D

{

    protected int y;

 

    public Point2D( int x, int y )

    {

        super( x );

        this.y = y;

    }

 

    public boolean equals( Object o )

    {

        if ( !(o instanceof Point2D) )

            return false;

 

        Point2D p2d = (Point2D) o;

 

        if ( this == p2d )

            return true;

 

        return y == p2d.y && super.equals( p2d );

    }

 

    public int hashCode()

    {

        return super.hashCode() * 31 + y;

    }

}

你觉得有问题吗?嗯,看上去好像没有问题,定义的挺好的,真的吗?!让我们测试一下。

public class Test

{

    public static void main( String[] args )

    {

        Point1D p1d  = new Point1D( 10 );

        Point2D p2d  = new Point2D( 10, 20 );

 

        if ( p1d.equals( p2d ) )

            System.out.println( "P1D is equal to P2D" );

        else

            System.out.println( "P1D is unequal to P2D" );

 

        if ( p2d.equals( p1d ) )

            System.out.println( "P2D is equal to P1D" );

        else

            System.out.println( "P2D is unequal to P1D" );

    }

}

Result

P1D is equal to P2D

P2D is unequal to P1D

WOW,为什么,竟然是这样!很明显原实现没有对子父类进行有效的检查,而仅仅对其他类做了相关的检测,一个大bug!其实这个是所有面向对象语言中都存在的问题,我们可以参考请谨慎实现operator==操作符函数。它用了一个所有面向对象中对通用的方法解决了它,那么是否我们这里也要这么做呢?嗯,我觉得完全可以,不过在Java中我们是否可以发现更好的方法呢?噢,我想到了,为什么不检查类的class来完成对应的检查工作呢?它很好的完成了我们要得工作,难道不是吗?!让我们动手吧J!

3Piont1D的重定义

public class Point1D

{

    protected int x;

 

    public Point1D( int x )

    {

        this.x = x;

    }

 

    public boolean equals( Object o )

    {

        if ( o == null )

            return false;

 

        if ( !getClass().equals( o.getClass() ) )

            return false;

 

        return x == ((Point1D) o).x;

    }

 

    public int hashCode()

    {

        return super.hashCode() * 31 + x;

    }

}

4Point2D的重定义

public class Point2D extends Point1D

{

    protected int y;

 

    public Point2D( int x, int y )

    {

        super( x );

        this.y = y;

    }

 

    public boolean equals( Object o )

    {

        if ( o == null )

            return false;

 

        if ( !getClass().equals( o.getClass() ) )

            return false;

 

        Point2D p2d = (Point2D) o;

 

        if ( this == p2d )

            return true;

 

        return y == p2d.y && super.equals( p2d );

    }

 

    public int hashCode()

    {

        return super.hashCode() * 31 + y;

    }

}

现在他们能够正确工作了吗?让我们再试试看。

public class Test

{

    public static void main( String[] args )

    {

        Point1D p1d  = new Point1D( 10 );

        Point1D p2d  = new Point2D( 10, 20 );

        Point1D p2d2 = new Point2D( 10, 20 );

        Point1D p2d3 = new Point2D( 10, 20 );

        Point1D p2d4 = new Point2D( 10, 30 );

        Point1D p2d5 = new Point2D( 10, 40 );

        Point1D p2d6 = new Point2D( 10, 50 );

 

        // Check reflexive

        if ( p1d.equals( p1d ) )

            System.out.println( "Reflexive/t/t-/tpassed!" );

        else

            System.out.println( "Reflexive/t/t-/tfailed!" );

 

        // Check symmetric

        if ( ((p1d.equals( p2d ) ? 1 : 0) ^ (p2d.equals( p1d ) ? 1 : 0)) == 0 )

            System.out.println( "Symmetric/t/t1/tpassed!" );

        else

            System.out.println( "Symmetric/t/t1/tfailed!" );

       

        if ( ((p2d.equals( p2d2 ) ? 1 : 0) ^ (p2d2.equals( p2d ) ? 1 : 0)) == 0 )

            System.out.println( "Symmetric/t/t2/tpassed!" );

        else

            System.out.println( "Symmetric/t/t2/tfailed!" );

 

        // Check transitive

        if ( p2d.equals( p2d2 ) && p2d2.equals( p2d3 ) )

        {

            if ( p2d.equals( p2d3 ) )

                System.out.println( "Transitive/t/t1/tpassed!" );

            else

                System.out.println( "Transitive/t/t1/tfailed!" );

        }

 

        if ( !p2d4.equals( p2d5 ) && !p2d5.equals( p2d6 ) )

        {

            if ( !p2d4.equals( p2d6 ) )

                System.out.println( "Transitive/t/t2/tpassed!" );

            else

                System.out.println( "Transitive/t/t2/tfailed!" );

        }

       

        // Check null

        if ( !p1d.equals( null ) && !p2d.equals( null ) )

            System.out.println( "Null/t/t/t-/tpassed!" );

        else

            System.out.println( "Null/t/t/t-/tfailed!" );

    }

}

Result:

Reflexive               -       passed!

Symmetric               1       passed!

Symmetric               2       passed!

Transitive              1       passed!

Transitive              2       passed!

Null                     -       passed!

Great! 都很好,都通过了!比较请谨慎实现operator==操作符函数中的方法,我们是否真的已经到了很满意的程度了呢?!好像还有一些瑕疵要修补吧。比如说,我们要对Point2D实例个数进行统计,所以就添加了两个静态的属性和一个静态方法。

public class StatPoint2D extends Point2D

{

    protected static Object lock     = new Object();

    protected static long p2dCounter = 0;

 

    static synchronized long getCounter()

    {

        return p2dCounter;

    }

 

    public StatPoint2D( int x, int y )

    {

        super( x, y );

        synchronized ( lock )

        {

            ++ p2dCounter;

        }

    }

 

    public static void main( String[] args )

    {

        Point2D p2d  = new Point2D( 10, 20 );

        Point2D p2d2 = new StatPoint2D( 10, 20 );

 

        if ( p2d.equals( p2d2 ) )

            System.out.println( "P2D is equal to P2D2" );

        else

            System.out.println( "P2D2 is unequal to P2D" );

    }

}

Result:

P2D2 is unequal to P2D

从等价概念而言,P2DP2D2是等价的,但是我们运行的结果和我们的预料出现的偏移。如何解决呢?问题的关键是我们使用了getClass来作为判断依据,它用的并非是真正的等价依据。所以我们最好的方法是使用等价Class来替换掉它(因为getClass是final修饰的,我们不能够通过重置来完成它)。我们在Point1D中加入如下方法。

    protected Object getEquivalentClass()

    {

        return getClass();

    }

5Point1D再次重新定义

public class Point1D

{

    protected int x;

 

    protected Object getEquivalentClass()

    {

        return getClass();

    }

   

    public Point1D( int x )

    {

        this.x = x;

    }

 

    public boolean equals( Object o )

    {

        if ( !(o instanceof Point1D) )

            return false;

 

        Point1D p1d = (Point1D) o;

 

        if ( !getEquivalentClass().equals( p1d.getEquivalentClass() ) )

            return false;

 

        return x == p1d.x;

    }

 

    public int hashCode()

    {

        return super.hashCode() * 31 + x;

    }

}

6Point2D再次重新定义

public class Point2D extends Point1D

{

    protected int y;

 

    public Point2D( int x, int y )

    {

        super( x );

        this.y = y;

    }

 

    public boolean equals( Object o )

    {

        if ( o instanceof Point2D )

        {

            Point2D p2d = (Point2D) o;

 

            if ( this == p2d )

                return true;

 

            if ( y != p2d.y )

                return false;

        }

       

        return super.equals( o );

    }

 

    public int hashCode()

    {

        return super.hashCode() * 31 + y;

    }

}

7StatPoint2D再次重新定义

public class StatPoint2D extends Point2D

{

    protected static Object lock     = new Object();

    protected static long p2dCounter = 0;

 

    static synchronized long getCounter()

    {

        return p2dCounter;

    }

 

    protected Object getEquivalentClass()

    {

        return Point2D.class;

    }

   

    public StatPoint2D( int x, int y )

    {

        super( x, y );

        synchronized ( lock )

        {

            ++ p2dCounter;

        }

    }

 

    public static void main( String[] args )

    {

        Point2D p2d  = new Point2D( 10, 20 );

        Point2D p2d2 = new StatPoint2D( 10, 20 );

 

        if ( p2d.equals( p2d2 ) )

            System.out.println( "P2D is equal to P2D2" );

        else

            System.out.println( "P2D2 is unequal to P2D" );

    }

}

Result:

P2D is equal to P2D2

非常酷,用一个简单getEquivalentClass的替代函数完成了灵活的类型操作。这个应该算是比较全面的实现了,你说呢?!