See the following 2 classes, DTO and DTOWithOrdering:
public class DTO {
private final String key;
private final long recordVersionNumber;
public DTO(String key) {
this.key = key;
this.recordVersionNumber = 0;
}
public DTO(String key, long recordVersionNumber) {
this.key = key;
this.recordVersionNumber = recordVersionNumber;
}
public String getKey() {
return key;
}
public long getRecordVersionNumber() {
return recordVersionNumber;
}
#Override
public String toString() {
return "Key: " + this.key + " Record Version Number: " + this.recordVersionNumber;
}
#Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
DTO that = (DTO) o;
return Objects.equal(this.key, that.key) &&
Objects.equal(this.recordVersionNumber, that.recordVersionNumber);
}
#Override
public int hashCode() {
return Objects.hashCode(key, recordVersionNumber);
}
public class DTOWithOrdering extends DTO implements Comparable<DTOWithOrdering> {
public DTOWithOrdering(String key, long recordVersionNumber) {
super(key, recordVersionNumber);
}
#Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
DTOWithOrdering other = (DTOWithOrdering) o;
if(this.getKey().equals(other.getKey())) {
if(this.getRecordVersionNumber() == other.getRecordVersionNumber()) {
return true;
} else if(this.getRecordVersionNumber() <= other.getRecordVersionNumber()) {
return true;
} else {
return false;
}
} {
return false;
}
}
#Override
public int compareTo(DTOWithOrdering other) {
if(this.getKey().equals(other.getKey())) {
if(this.getRecordVersionNumber() == other.getRecordVersionNumber()) {
return 0;
} else if(this.getRecordVersionNumber() <= other.getRecordVersionNumber()) {
return 0;
} else {
return -1;
}
} {
return -1;
}
}
}
DTOWIthOrdering extends from DTO and overrides the equals and compareTo methods.
The problem arises with the following code snippet when I create a TreeSet<DTOWIthOrdering> and invoke contains on this
TreeSet<DTOWithOrdering> treeSet = new TreeSet<DTOWithOrdering>(keyAndVersionList);
List<DTO> results = new ArrayList<DTO>();
for (DTO diff : diffs) {
if (treeSet.contains(new DTOWithOrdering(diff.getKey(), diff.getRecordVersionNumber())) == false) {
results.add(diff);
}
}
When I run this within my program I can see that treeSet contains 2700+ entities, one of which has a key of 0b3ae620-bbcf-347d-a9b4-87e6fd765cd7 and recordVersionNumber of 4
However, one of the diff entities contains the same key with a recordVersionNumber of 0.
When the code invokes the contains method, the set returns a value of false.
Strangely, for other examples, where the keys are equal and the record version number is greater in the TreeSet it returns true!
Is there something wrong here with my logic?
Here is a quote from JavaDoc for Comparable interface:
int compareTo(T o)
Compares this object with the specified object for order. Returns a
negative integer, zero, or a positive integer as this object is less
than, equal to, or greater than the specified object. The implementor
must ensure sgn(x.compareTo(y)) == -sgn(y.compareTo(x)) for all x and
y. (This implies that x.compareTo(y) must throw an exception if
y.compareTo(x) throws an exception.)
If you return -1 but never return 1, the property
sgn(x.compareTo(y)) == -sgn(y.compareTo(x)) cannot hold true. So your implementation of this method does not conform to the specification and TreeSet can work improperly.
Related
I have use TreeMap to store key value.
For key using custom object.
But once I have faced very strange issue, I am not able to get value which I have set earlier(with same key).
below is my code
public final class TestOptions implements Cloneable {
private Map<StorageSystemOptionKey, Object> options = new TreeMap<StorageSystemOptionKey, Object>();
private static final class StorageSystemOptionKey implements Comparable<StorageSystemOptionKey> {
/** Constant used to create hashcode */
private static final int HASH = 31;
private final Class<? extends StorageRepository> storageRepositoryClass;
/** The option name */
private final String name;
private StorageSystemOptionKey(Class<? extends StorageRepository> storageRepositoryClass, String name) {
this.storageRepositoryClass = storageRepositoryClass;
this.name = name;
}
public int compareTo(StorageSystemOptionKey o) {
int ret = storageRepositoryClass.getName().compareTo(o.storageRepositoryClass.getName());
if (ret != 0) {
return ret;
}
return name.compareTo(o.name);
}
#Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
final StorageSystemOptionKey that = (StorageSystemOptionKey) o;
if (!storageRepositoryClass.equals(that.storageRepositoryClass)) {
return false;
}
if (!name.equals(that.name)) {
return false;
}
return true;
}
#Override
public int hashCode() {
int result;
result = storageRepositoryClass.hashCode();
result = HASH * result + name.hashCode();
return result;
}
}
void setOption(Class<? extends StorageRepository> fileSystemClass, String name, Object value) {
options.put(new StorageSystemOptionKey(fileSystemClass, name), value);
}
Object getOption(Class<? extends StorageRepository> fileSystemClass, String name) {
StorageSystemOptionKey key = new StorageSystemOptionKey(fileSystemClass, name);
return options.get(key);
}
boolean hasOption(Class<? extends StorageRepository> fileSystemClass, String name) {
StorageSystemOptionKey key = new StorageSystemOptionKey(fileSystemClass, name);
return options.containsKey(key);
}
public int compareTo(TestOptions other) {
if (this == other) {
return 0;
}
int propsSz = options == null ? 0 : options.size();
int propsFkSz = other.options == null ? 0 : other.options.size();
if (propsSz < propsFkSz) {
return -1;
}
if (propsSz > propsFkSz) {
return 1;
}
if (propsSz == 0) {
return 0;
}
int hash = options.hashCode();
int hashFk = other.options.hashCode();
if (hash < hashFk) {
return -1;
}
if (hash > hashFk) {
return 1;
}
return 0;
}
#Override
public Object clone() {
TestOptions clone = new TestOptions();
clone.options = new TreeMap<StorageSystemOptionKey, Object>(options);
return clone;
}
}
calling method to set and get like
public abstract Class<? extends StorageRepository> getStorageRepositoryClass();
public Class<? extends StorageRepository> getStorageRepositoryClass() {
return MyImpl.class;
}
TestOptions opt =new TestOptions(); // shared accross all Threads
Object getProperty(String name) {
return opt.getOption(getStorageRepositoryClass(), name);
}
void setProperty(String name, Object value) {
opt.setOption(getStorageRepositoryClass(), name, value);
}
Using set and get method in multi-threaded application.
queries:
I am calling set/get in multiple time then also I was not able to get value which was set earlier(same key)
Is this due to Treeset implementation is not synchronized
or problem with hashCode, equals or compareTo method implementation?
On a quick glance your compareTo(), equals() and hashCode() look fine. Note that TreeMap will mostly use compareTo() to find elements so that method needs to be correct (your's looks technically correct).
However, TreeMap and TreeSet (as well as other basic collections and maps) are not thread-safe and thus concurrent modifications can cause all kinds of unexpected behavior. We once had a case where 2 threads were trying to add a single element to a hashmap and the threads ended up in an endless loop because the internal list to resolve clashes produced a cycle (due to the concurrent put).
So either use the ConcurrentXxxx maps and collections or synchronize access to yours.
TreeSet is not synchronized. I belive ConcurrentSkipListMap might be better.
Check also your hashCode, equals implementation
My intention was to make a caching service for a database results, that I can paginate differently based on client's requests.
So, upon the (search) request I am making a key that is composed of parameters, which are in form of two Map<String, String[]> and a:
public class DocMaintainer {
public Manipulator creator;
public Manipulator lastChange;
#Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
DocMaintainer that = (DocMaintainer) o;
return Objects.equals(creator, that.creator) &&
Objects.equals(lastChange, that.lastChange);
}
#Override
public int hashCode() {
return Objects.hash(creator, lastChange);
}
}
public class Manipulator {
public Date fromDate;
public Date toDate;
public String userId;
public String system;
public Manipulator() {
this.userId = "";
this.system = "";
this._fromJoda = new DateTime(Long.MIN_VALUE);
this._toJoda = new DateTime(Long.MAX_VALUE - DateTimeConstants.MILLIS_PER_WEEK);
}
private DateTime _fromJoda;
private DateTime _toJoda;
public DateTime get_fromJoda() {
_fromJoda = fromDate != null ? new DateTime(fromDate) : _fromJoda;
return _fromJoda;
}
public DateTime get_toJoda() {
_toJoda = toDate != null ? new DateTime(toDate) : _toJoda;
try {
_toJoda = _toJoda.plusDays(1);
} catch (Exception e) {
System.out.println(e);
}
return _toJoda;
}
#Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Manipulator that = (Manipulator) o;
return Objects.equals(fromDate, that.fromDate) &&
Objects.equals(toDate, that.toDate) &&
Objects.equals(userId, that.userId) &&
Objects.equals(system, that.system);
}
#Override
public int hashCode() {
return Objects.hash(fromDate, toDate, userId, system);
}
}
As you can see I intended to use hashing to create a "key":
public class SearchKey {
public int conjunctionHash;
public int disjunctionHash;
public int maintainerHash;
public SearchKey(int conjunctionHash, int disjunctionHash, int maintainerHash) {
this.conjunctionHash = conjunctionHash;
this.disjunctionHash = disjunctionHash;
this.maintainerHash = maintainerHash;
}
#Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
SearchKey searchKey = (SearchKey) o;
return conjunctionHash == searchKey.conjunctionHash &&
disjunctionHash == searchKey.disjunctionHash &&
maintainerHash == searchKey.maintainerHash;
}
#Override
public int hashCode() {
return Objects.hash(conjunctionHash, disjunctionHash, maintainerHash);
}
}
a key-object is used directly as a caching key in a singleton service:
#Named
#Singleton
public class SearchCacheSrv {
private Map<SearchKey, ValidMainteinersList<FindDTO>> cache = new HashMap<>();
public ValidMainteinersList<FindDTO> getCached(SearchKey searchKey) {
if (cache.containsKey(searchKey))
return cache.get(searchKey);
else
return new ValidMainteinersList<FindDTO>();
}
public SearchKey makeAkey(Map<String, String[]> conjunction,
Map<String, String[]> disjunction,
DocMaintainer maintainer) {
return new SearchKey(conjunction.hashCode(), disjunction.hashCode(), maintainer.hashCode());
}
public ValidMainteinersList<FindDTO> cache(SearchKey searchKey, ValidMainteinersList<FindDTO> findDTOS) {
return cache.put(searchKey, findDTOS);
}
public void clearCache() {
cache.clear();
}
}
Unfortunately this is not behaving the way I expected and I'm getting different hashes/keys generated for the same parameters.
Naturally question is why?
The problem here is that the hashCode of an array does not depend on the contents, but on the reference. That means that if you have two conjunction / disjunction keys that are equal, but the contained arrays are not the same objects, then the hashcode of the keys will be different.
The solution that probably takes the least effort is replacing the arrays with ArrayLists, which do base their hashCode on the content.
I actually don't see the point of passing conjunction.hashCode(), ... to your SearchKey constructor; I never had to do it this way, but it could be my mistake.
Try passing actual values to your SearchKey class, not hashCodes, so the hashCode method always returns a consistent value.
I'm trying to compare two fields (string and integer) using only the Comparable interface. It was my first time using this and I've no idea where to put the second field to compare the values.
public int compareTo(Object o) throws ClassCastException
{
int count = 0;
int compareName = this.lastName.compareTo(((SalePerson) o).getLastName());
int compareSales = Integer.compare(this.totalSales, ((SalePerson) o).getTotalSales());
if(!(o instanceof SalePerson))
{
throw new ClassCastException("A SalePerson object expected.");
}
if((this.totalSales < ((SalePerson) o).getTotalSales()))
{
count = -1;
}
else if((this.totalSales > ((SalePerson) o).getTotalSales()))
{
count = 1;
}
return count;
}
If you want to implement Comparable interface, it is unecassary to throw ClassCastException since o has to be SalePerson, otherwise you will get a compile error.
You can do it this way:
public class SalePerson implements Comparable<SalePerson>{
#Override
public int compareTo(SalePerson o) {
int totalSalesCompare = Integer.compare(this.totalSales, o.getTotalSales());
return totalSalesCompare == 0 ? this.lastName.compareTo(o.getLastName())
: totalSalesCompare;
}
}
Also, the compareTo is suggested to work with equals and hashCode:
#Override
public boolean equals(Object o) {
if (o == null) {
return false;
}
if (!(o instanceof SalePerson)) {
return false;
}
return Integer.compare(Integer.compare(this.totalSales, o.getTotalSales())) == 0
&& this.lastName.equals(o.getLastName());
}
#Override
public int hashCode() {
return this.lastName.hashCode() * 31 + this.totalSales;
}
Following code is a wrong implementation of java equals method. It violated the symmetric rule. But I do not know how it violated this rule. Please point it out where in this method it violated the symmetric rule.
public class WrongEquals {
private final String variable;
public WrongEquals(String variable) {
this.variable = variable;
}
#override public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o instanceof String) {
return variable.equals((String) o);
}
if (o instanceof WrongEquals) {
return variable.equals(((WrongEquals) o).variable);
}
return false;
}
#override
public int hashCode() {
return (variable == null ? 0 : variable.hashCode());
}
}
Because your WrongEquals instance can be equal to some String, but no String will be equal to any instance of WrongEquals
Look at the String implementation of equals() (as of JDK 7)
public boolean equals(Object anObject) {
if (this == anObject) {
return true;
}
if (anObject instanceof String) {
String anotherString = (String) anObject;
int n = value.length;
if (n == anotherString.value.length) {
char v1[] = value;
char v2[] = anotherString.value;
int i = 0;
while (n-- != 0) {
if (v1[i] != v2[i])
return false;
i++;
}
return true;
}
}
return false;
}
Obviously this implementation ensures that any equality will only exist between another String instance.
(This is just an extension and illustration of Kon's prior answer)
Try adding this main program:
public static void main(String[] args){
WrongEquals a = new WrongEquals("xyzzy");
String b = "xyzzy";
System.out.println(a.equals(b));
System.out.println(b.equals(a));
}
It will display:
true
false
Can anyone let me know what goes wrong in this piece of code? I'm pulling my hair out!
There isn't any problem if I use HashMap instead of ConcurrentHashMap. The code is compiled with JDK 5.0
public class MapTest {
public Map<DummyKey, DummyValue> testMap = new ConcurrentHashMap<DummyKey, DummyValue>();
public MapTest() {
DummyKey k1 = new DummyKey("A");
DummyValue v1 = new DummyValue("1");
DummyKey k2 = new DummyKey("B");
DummyValue v2 = new DummyValue("2");
testMap.put(k1, v1);
testMap.put(k2, v2);
}
public void printMap() {
for(DummyKey key : testMap.keySet()){
System.out.println(key.getKeyName());
DummyValue val = testMap.get(key);
System.out.println(val.getValue());
}
}
public static void main(String[] args){
MapTest main = new MapTest();
main.printMap();
}
private static class DummyKey {
private String keyName = "";
public DummyKey(String keyName){
this.keyName = keyName;
}
public String getKeyName() {
return keyName;
}
#Override
public int hashCode() {
return keyName.hashCode();
}
#Override
public boolean equals(Object o) {
return keyName.equals(o);
}
}
private static class DummyValue {
private String value = "";
public DummyValue(String value){
this.value = value;
}
public String getValue() {
return value;
}
}
}
This is the output:
B
Exception in thread "main" java.lang.NullPointerException
at test.MapTest.printMap(MapTest.java:27)
at test.MapTest.main(MapTest.java:34)
DummyKey.equals method implementation is incorrect, due to that testMap.get(key) always returns null. Try this
public boolean equals(Object o) {
if (o instanceof DummyKey) {
DummyKey other = (DummyKey) o;
return keyName == null ? other.keyName == null : keyName.equals(other.keyName);
}
return false;
}
hashCode also needs a little change to be consistent with equals
public int hashCode() {
return keyName == null ? 0 : keyName.hashCode();
}
The problem comes from your equals in DummyKey.
When you call DummyValue val = testMap.get(key);, the hashcode function finds a match (both keyname of k1 and key are the same and so are their hashcode). Yet equals returns false because k1.keyname is equal to "A" which is not equal to key itself, which is actually of type DummyValue: you are not comparing properly!
Therefore, you need to modify your equals function:
#Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
DummyKey other = (DummyKey) obj;
if (keyName == null) {
if (other.keyName != null)
return false;
} else if (!keyName.equals(other.keyName))
return false;
return true;
}
Please note that if you change hashCode(), then you must change equals() as well. Otherwise, you will run into problems. If equals() returns true for two items, then their hashCode() value must be equal! The opposite is not required but preferable for better hashing performance. Here is an implementation of equals() and hashCode().
HINT: if you are using eclipse, you can utilize its source generation capability to create the correct hashCode() and equals() method for you. The only thing you need to do is to pick the instance variables that identify the object. To do so in eclipse, while your source code is open, go to the tabs in the top and choose "source", then choose "Generate hashCode() and equals()..."
#Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((keyName == null) ? 0 : keyName.hashCode());
return result;
}
Override
public boolean equals(Object other) {
if(this == other) return true; //for optimization
if(! other instanceof this) return false; //also covers for when other == null
return this.keyName == null ? other.keyName == null : this.keyName.equals(other.keyName);
}
As others have pointed, the problem lies in the way you override hashcode and equals.
Two options : 1) Just remove the hashcode and equals and it works fine
2) I let eclipse generate the source for hashcode and equals and it works fine. This is what my eclipse belted out for me :
#Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result
+ ((keyName == null) ? 0 : keyName.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;
DummyKey other = (DummyKey) obj;
if (keyName == null) {
if (other.keyName != null)
return false;
} else if (!keyName.equals(other.keyName))
return false;
return true;
}