Re: [EqualsVerifier] Ineritance issue.

366 views
Skip to first unread message

Jan Ouwens

unread,
Mar 8, 2013, 6:02:17 AM3/8/13
to equalsv...@googlegroups.com
Hi Steve,

Thanks for the kind words!

What you should do in this case, kind of depends on what your intention is. I noticed that Sventon1 basically has the same implementation of equals as AbstractSventon: no new fields are added into the mix. If it's indeed your intention to only add behaviour to Sventon, and not data, you could simply remove the equals override in Sventon1 (and make it final in AbstractSventon). That completely satisfies the equals contract, and it also satisfies EqualsVerifier (which is more strict than the contract). It does mean, however, that any subclass of AbstractSventon could be equal to any other subclass. This also satisfies the Liskov substitution principle, that says that any class (in this case, Sventon1, Sventon2 and SventonX) should be able to transparently stand in for its superclass (AbstractSventon).

If you don't mean to add new fields, but you don't want Sventon1 to be able to be equal to Sventon2, you could write your equals using getClass() instead of instanceof:

        @Override
        public boolean equals(Object o) {
            if (this == o) {
                return true;
            }
            if (o == null || getClass() != o.getClass()) {
                return false;
            }
            AbstractSventon that = (AbstractSventon) o;
            return new EqualsBuilder().append(url, that.url)
                    .append(repositoryInstance, that.repositoryInstance)
                    .isEquals();
        }

Then add .usingGetClass() to your call to EqualsVerifier, and you're set. The downside to this is, you can't make trivial subclasses of Sventon1 anymore (for example, if you just want to quickly override one method in an anonymous subclass). Hibernate does this a lot, too. Your equals method will appear to be broken, because these trivial (and often nameless) subclasses suddenly are no longer equal to other instances of Sventon1. If you run into this problem, you should add a canEqual method to AbstractSventon. How to do this, is excellently explained in this article: http://www.artima.com/lejava/articles/equality.html

Hope this helps! If you have any more questions, please let me know.

Jan



On 7 March 2013 17:27, Steve Christou <schri...@gmail.com> wrote:
Hello,
I just started using equalsverifier for everything and love it! However I'm currently running into an issue where I can't get this test to work. Here is how the 2 classes look like:

public abstract class AbstractSventon extends SubversionRepository {
.....
    @Override
    public boolean equals(Object o) {
        if (this == o) {return true;}
        if (!(o instanceof AbstractSventon)) {return false;}
        AbstractSventon that = (AbstractSventon) o;
        return new EqualsBuilder()
            .append(url, that.url)
            .append(repositoryInstance, that.repositoryInstance)
            .isEquals();
    }
}

public class Sventon1 extends AbstractSventon {
    @Override
    public final boolean equals(Object o) {
        if (this == o) {return true;}
    if (!(o instanceof Sventon)) {return false;}
        Sventon that = (Sventon) o;
        return new EqualsBuilder()
             .appendSuper(super.equals(that))
            .append(url, that.url)
            .append(repositoryInstance, that.repositoryInstance)
            .isEquals();
    }
}

The EqualsBuilder method comes from org.apache.commons.lang3.builder.EqualsBuilder. This is currently how my test looks like:
@Test
public void testEquals() {
EqualsVerifier.forClass(AbstractSventon.class)
 .withPrefabValues(URL.class, new URL("http://test"), new URL("https://test"))
 .suppress(Warning.STRICT_INHERITANCE)
 .verify();
EqualsVerifier.forClass(Sventon.class)
.withPrefabValues(URL.class, new URL("http://test"), new URL("https://test"))
.verify();
}

The first test with AbstractSventon part passes, however the second part fails. I get the following exception:
java.lang.AssertionError: Symmetry:
  hudson.scm.browsers.Sventon@ef039a1
does not equal superclass instance
  hudson.scm.browsers.AbstractSventon$$EnhancerByCGLIB$$2ab42b4c@ef039a1

I added .withRedefinedSuperclass(), but now I'm receiving this exception:
java.lang.AssertionError: Redefined superclass:
  hudson.scm.browsers.Sventon@ef039a1
should not equal superclass instance
  hudson.scm.browsers.AbstractSventon$$EnhancerByCGLIB$$aefce7fa@ef039a1
but it does.

I was wondering how my test code should look like to do this verification, or how the equals method should look like?

Thanks,
Steve.

--
You received this message because you are subscribed to the Google Groups "equalsverifier" group.
To unsubscribe from this group and stop receiving emails from it, send an email to equalsverifie...@googlegroups.com.
For more options, visit https://groups.google.com/groups/opt_out.
 
 

Steve Christou

unread,
Mar 8, 2013, 2:37:03 PM3/8/13
to equalsv...@googlegroups.com
You are the man! :) Now my tests pass, thank you!

Jan Ouwens

unread,
Mar 8, 2013, 5:31:23 PM3/8/13
to equalsv...@googlegroups.com
You're very welcome! Glad I could help :).

Luiz Felipe Silva Ramos

unread,
Aug 9, 2013, 11:46:27 PM8/9/13
to equalsv...@googlegroups.com
Hello, sorry for my basic question but even after the answer above I can't solve my problem. I'm getting the  java.lang.AssertionError: Redefined superclass exception.

I'm using a really simple example:

public abstract  class Person {
    private String name;
    private int age;

    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;
    }


    @Override
    public final boolean equals(Object o) {
        if (this == o) return true;
        if (!(o instanceof Person)) return false;

        Person that = (Person) o;

        return new EqualsBuilder()
                .append(name, that.name)
                .isEquals();
    }

    @Override
    public final int hashCode() {
        return name != null ? name.hashCode() : 0;
    }
}

public class Student extends Person {
    private long identification;
    public long getIdentification() {
        return identification;
    }

    public void setIdentification(long identification) {
        this.identification = identification;
    }
}

@Test
public void testEquals() {
EqualsVerifier equalsVerifier = EqualsVerifier.forClass(Student.class).suppress(Warning.NONFINAL_FIELDS)
.withRedefinedSuperclass();
equalsVerifier.verify();
}

Please, could you help me?

Thanks for the great job with equalsverifier!

Jan Ouwens

unread,
Aug 10, 2013, 7:56:46 AM8/10/13
to equalsv...@googlegroups.com
Hi Luiz,

You can simply remove the call to withRedefinedSuperclass. You only
need to use it when a class redefines equals, and one of its
superclasses does, too. Since your Student example doesn't override
equals, you don't need to use withRedefinedSuperclass.

By the way, the intended way to call EqualsVerifier is like this:

EqualsVerifier.forClass(Student.class)
.suppress(Warning.NONFINAL_FIELDS)
.verify();


Much less code that way :).


Regards,
Jan

Luiz Felipe Silva Ramos

unread,
Aug 10, 2013, 1:20:53 PM8/10/13
to equalsv...@googlegroups.com
Thanks for your time Jan!
Reply all
Reply to author
Forward
0 new messages