谨慎重置equals方法

    技术2022-05-11  69

    谨慎重置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的替代函数完成了灵活的类型操作。这个应该算是比较全面的实现了,你说呢?!

          

    最新回复(0)