Why is Mutability: equals depends on mutable field flagged sporadically?

299 views
Skip to first unread message

Bill Turner

unread,
Aug 18, 2011, 2:00:55 PM8/18/11
to equalsverifier
I have two classes: Address and Carrier. The first gets flagged for
mutability, while the second does not. The only difference I can see
between the two is that the latter (Carrier) has JPA annotations while
the former does not. Can you explain this better as it seems to me
that both should be mutable. The class bodies are below, followed by
the two corresponding tests.

===============================================================
public final class Address {

private String address1;

private String address2;

private String city;

private String state;

private String zip;

private String country;

public Address() {
super();
}

public String getAddress1() {
return address1;
}

public void setAddress1(String address1) {
this.address1 = address1;
}

public String getAddress2() {
return address2;
}

public void setAddress2(String address2) {
this.address2 = address2;
}

public String getCity() {
return city;
}

public void setCity(String city) {
this.city = city;
}

public String getState() {
return state;
}

public void setState(String state) {
this.state = state;
}

public String getZip() {
return zip;
}

public void setZip(String zip) {
if (zip != null && zip.length() > 5) {
this.zip = zip.substring(0, 5);
} else {
this.zip = zip;
}
}

public String getCountry() {
return country;
}

public void setCountry(String country) {
this.country = country;
}

@Override
public int hashCode() {
final int PRIME = 31;
int result = 1;
result = PRIME * result + ((address1 == null) ? 0 :
address1.hashCode());
result = PRIME * result + ((address2 == null) ? 0 :
address2.hashCode());
result = PRIME * result + ((city == null) ? 0 : city.hashCode());
result = PRIME * result + ((state == null) ? 0 : state.hashCode());
result = PRIME * result + ((zip == null) ? 0 : zip.hashCode());
return result;
}

@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
final Address other = (Address) obj;
if (address1 == null) {
if (other.address1 != null)
return false;
} else if (!address1.equals(other.address1))
return false;
if (address2 == null) {
if (other.address2 != null)
return false;
} else if (!address2.equals(other.address2))
return false;
if (city == null) {
if (other.city != null)
return false;
} else if (!city.equals(other.city))
return false;
if (state == null) {
if (other.state != null)
return false;
} else if (!state.equals(other.state))
return false;
if (zip == null) {
if (other.zip != null)
return false;
} else if (!zip.equals(other.zip))
return false;
return true;
}

@Override
public String toString() {
return "Address [address1=[" + this.address1 + "]"
+ ", address2=[" + this.address2 + "]"
+ ", city=[" + this.city + "]"
+ ", country=[" + this.country + "]"
+ ", state=[" + this.state + "]"
+ ", zip=[" + this.zip + "]"
+ "]";
}
}

===============================================================
@Entity
@Table(name="table_name", schema="schema_name")
public final class Carrier {

private CarrierId id;

private String carrierDescription;


private String clientName;

public Carrier() {
super();
}

@Column (name="column_name")
public String getCarrierDescription() {
return carrierDescription;
}

public void setCarrierDescription(String carrierDescription) {
this.carrierDescription = carrierDescription;
}

@Transient
public String getCarrierId() {
return id.getCarrierId();
}


@Transient
public String getClientCode() {

return id.getClientCode();
}



@Column (name="column_name")
public String getClientName() {

return clientName;

}

public void setClientName(String clientName) {
this.clientName = clientName;
}

@Id
public CarrierId getId() {
return id;
}

public void setId(CarrierId id) {
this.id = id;
}

@Override
public int hashCode() {
final int PRIME = 31;
int result = 1;
result = PRIME * result + ((carrierDescription == null) ? 0 :
carrierDescription.hashCode());
result = PRIME * result + ((clientName == null) ? 0 :
clientName.hashCode());
result = PRIME * result + ((id == null) ? 0 : id.hashCode());
return result;
}

@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
final Carrier other = (Carrier) obj;
if (carrierDescription == null) {
if (other.carrierDescription != null)
return false;
} else if (!carrierDescription.equals(other.carrierDescription))
return false;
if (clientName == null) {
if (other.clientName != null)
return false;
} else if (!clientName.equals(other.clientName))
return false;
if (id == null) {
if (other.id != null)
return false;
} else if (!id.equals(other.id))
return false;
return true;
}

@Override
public String toString() {
return "Carrier [carrierDescription=[" +
this.carrierDescription + "]"
+ ", clientName=[" + this.clientName + "]"
+ ", id=[" + this.id + "]"
+ "]";
}

}

===============================================================
@Test
public void testHashCodeAndEqualsUsingEqualsVerifier() {
EqualsVerifier.forClass(Address.class).usingGetClass().verify(); //
this fails unless suppress(Warning.NONFINAL_FIELDS) is added
}

===============================================================
@Test
public void testHashCodeAndEqualsUsingEqualsVerifier() {
EqualsVerifier.forClass(Carrier.class).usingGetClass().verify(); //
this does not fail
}

Jan Ouwens

unread,
Aug 19, 2011, 5:30:46 AM8/19/11
to equalsv...@googlegroups.com
Hi Bill,

This is actually a feature! See also
http://code.google.com/p/equalsverifier/issues/detail?id=37

The idea is that, since JPA entities are required to be mutable (as a
programmer, you don't have any choice in this, as you do with other
classes), it's not really helpful for EV to complain about it. You'll
just suppress the warning anyway, making your test look more cluttered
in the process. So for JPA entities, and JPA entities alone, EV
ignores any mutability issues it encounters.


Regards,
Jan

Reply all
Reply to author
Forward
0 new messages