Set remove() function does not work - java

while working with my application I've encountered a problem while trying to remove object from the java collection ( Set pulled from database with EclipseLink ).
The object which I want to remove in an entity class which has overriden equals method.
I've even checked whether any of the objects in the collection is eqauls to the one I want to remove with the following code:
for(AlbumEntity entity : deleteGroup.getAlbums()){
System.out.println("VAL: " + deleteAlbum.equals(entity));
}
In this case, one of the values returned is true. However, if I do:
boolean result = deleteGroup.getAlbums().remove(deleteAlbum);
the value of result is false and the size of collection stays the same.
Thanks for your help in advance
edit:
#Override
public int hashCode() {
int hash = 0;
hash += (id != null ? id.hashCode() : 0);
return hash;
}
#Override
public boolean equals(Object object) {
if (!(object instanceof AlbumEntity)) {
return false;
}
AlbumEntity other = (AlbumEntity) object;
if ((this.id == null && other.id != null) || (this.id != null && !this.id.equals(other.id))) {
return false;
}
return true;
}

A few possibilities:
1) There is a problem with the implementation of id's equals or hashCode methods. In this case, you could have id1.equals(id2) but id1.hashCode() != id2.hashCode(). This would cause inconsistency between equals and hashCode() for the album objects and could cause the symptoms you're seeing.
2) The id for one or more albums changes at some point after the for loop that checks deleteAlbum.equals(entity) for each album in the Set. If an id changes for an album, the remove() method may not be able to find it. An id could change from null to some non null number if got saved to the database - EclipseLink might do this for you without you explicitly asking it to.
3) Because of EclipseLink's meddling, deleteGroup might not actually be a HashSet when you run your code. The docs for EclipseLink suggest it will give you an "indirection object" instead of the java.util.Set (or java.util.HashSet I presume) declared in your class, depending on how it is configured. In that case, the contains and remove methods might not do what you expect them to.
See Overriding equals and hashCode in Java for more details on these and other possible problems involving equals and hashCode, which can cause bizarre behavior with Sets.

Okay let's try a bit of testing:
1:
Iterator<AlbumEntity> it = deleteGroup.getAlbums().iterator();
while(it.hasNext()){
AlbumEntity entity = it.next();
Assert.assertTrue(deleteGroup.getAlbums().contains(entity))
}
Does this test run successfully?

Related

Overridden equals is not called

I have a class Reminder that has both hashcode and equals overridden like this:
#Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((cronExpression == null) ? 0 : cronExpression.hashCode());
result = prime * result + ((subject == null) ? 0 : subject.hashCode());
result = prime * result + timeout;
result = prime * result + ((type == null) ? 0 : type.hashCode());
return result;
}
#Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (!(obj instanceof Reminder))
return false;
Reminder other = (Reminder) obj;
if (cronExpression == null) {
if (other.cronExpression != null)
return false;
} else if (!cronExpression.equals(other.cronExpression))
return false;
if (subject == null) {
if (other.subject != null)
return false;
} else if (!subject.equals(other.subject))
return false;
if (timeout != other.timeout)
return false;
if (type == null) {
if (other.type != null)
return false;
} else if (!type.equals(other.type))
return false;
return true;
}
Both overrides were automatically generated using Eclipse. I'm using the Reminder in a HashSet instantiated like this: private Set<Reminder> localReminders = new HashSet<Reminder>();
When updating this set, I'm using localreminders.contains(anotherReminder) and for some reason that I've been trying to figure out for a while now, it does not call the overridden equals method. Even though cronExpression, subject, timeout and type of the reminders compared are the same, contains returns false.
So far I've only come across answers where equalsand/or hashcode were implemented incorrectly or not at all. Any help would be very much appreciated!
Let me know if you need more information like additional code for this!
EDIT: the properties used in hashcodeand equals are all String, except for timeout which is int.
EDIT2: while debugging, I currently have these two reminders in my HashSet:
Reminder [cronExpression=0 10 10 ? * *, subject=, type=OTHER_TYPE, audioPath=/other_type_reminder.mp3, muted=false, future=DelegatingErrorHandlingRunnable for Task#af94b0, timeout=35940]
Reminder [cronExpression=50 53 10 ? * *, subject=sub, type=TYPE, audioPath=/type_reminder.mp3, muted=false, future=DelegatingErrorHandlingRunnable for ReminderTask#f1f373, timeout=35940]
The one that I am checking whether it is contained in my set looks like this:
Reminder [cronExpression=50 53 10 ? * *, subject=sub, type=TYPE, audioPath=/type_reminder.mp3, muted=false, future=null, timeout=35940]
The only difference I can spot here is that in one, the future is null while it is actually set in the other. But since the future property is not included in either hashcode or ´equals`, this should not matter.
As you can see in the implementation of the equals method you call cronExpression.equals(other.cronExpression) and subject.equals(other.subject) and type.equals(other.type). If only one of this is not implemented right then you get wrong result. Please check if all of the properties that you use in this method has correct implementation of equals.
By the way also check the implementation of the methods cronExpression.hashCode(), subject.hashCode() and type.hashCode(). They are used in your hashCode method.
Edit: If as you said cronExpression, subject and type are Strings then it should be easy for you to make main method populate two objects from class Reminder with the same info and test the methods. To be sure where is the problem you can call if(firstReminder.equals(secondReminder)).
From my experiance you can have problems with the strings. For example if one of the string has space at the end is different then the other or similar kind of issue.
Edit 2: Ok, from your input It seems this objects to have the same strings.
Is it possible Reminder class to be extended and you to compare child class object with Reminder object? If this happen in the child class equals and hashcode can be implemented and then the result can be wrong.
Also just be sure can you log the size of each string? This is very strange.
Maybe it is possible you to have hidden character. See this for more information: Is there an invisible character that is not regarded as whitespace?
Good luck!
The Problem may be with your hashcode() method. It should generate a unique code. There are some guidelines to overridde hashcode().Hashcode Best Practice
If hashcode of objects are different then equals() will not called even if they are equal.
Because HashSet first check hashcodes of both objects and if hashcodes are equal then only it will call equals() to check whether both objects are really equal or not.
Read Oracle Javadoc to override hashcode override contract
You need to provide us the import of the Reminder class if you want us to be able to help you.
For your culture and curiosity : java.util.HashSet.contains(Object o), reading the code it points to :
public boolean containsKey(Object key) {
return getNode(hash(key), key) != null;
}
which itself points to :
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
As you can see, the important part of your implementation is Reminder.hashCode().
Regarding your specific issue : As you are probably using quartz for org.quartz.CronExpression, you can see that org.quartz.CronExpression.hashCode() method is not implemented, so it calls it's parent hashCode(), which is Object.hashCode().
From the documentation (JRE 7), you can read :
As much as is reasonably practical, the hashCode method defined by class Object does return distinct integers for distinct objects. (This is typically implemented by converting the internal address of the object into an integer, but this implementation technique is not required by the JavaTM programming language.)
So both of similar item with different instance of org.quartz.CronExpression will have different hashCode() result.

Hibernate proxy as a key in HashMap

Today I spent some time debugging an issue with hibernate, simplified example would look like:
Map<Cat, Owner> catsMap = new HashMap();
List<Owner> owners = ownerRepo.getOwners();
for (Owner owner : owners) {
// cat is Lazy, according to its nature :)
catsMap.put(owner.getCat(), owner);
}
Cat cat = catRepo.findOne("meow");
Owner meowOwner = catsMap.get(cat);
at this moment meowOwner is null because it is not found in catsMap keyset. It took sometime to figure out why because in debug window I see that the Cat with name 'meow' exists in keyset of catsMap, moreover, if I write an expression
catsMap.keySet().iterator().next().equals(cat)
it returns true, hashcodes are the same, same values, though
catsMap.get(cat)
still returns null in the same expressions window.
At last I called
catsMap.keySet().iterator().next().getClass()
and finally found out that it is long.path.to.package.Cat_$$_jvstaea_41, so it is a proxy and equals fails on the step when it checks class equality.
The solution is, of course, obvious, but the question is why do I have
catsMap.keySet().iterator().next().equals(cat)
returning true? I tried also reversed case
cat.equals(catsMap.keySet().iterator().next())
and this one returns false, which is breaking the equals() convention of transitivity.
PS: in all the examples I assume that currently there is only one cat and one owner in DB
Cat cat = catRepo.findOne("meow") should return the same instance, unless your Map is outside of the initial transaction. If you want to store an Entity outside of a transaction, make sure to unproxy it before storing it.
public T unproxy(T proxied)
{
T entity = proxied;
if (entity instanceof HibernateProxy) {
Hibernate.initialize(entity);
entity = (T) ((HibernateProxy) entity)
.getHibernateLazyInitializer()
.getImplementation();
}
return entity;
}
You have to make sure you use the right syntax for your equals and hashCode overrides. This is an example of implementation:
#Override
public boolean equals(Object obj) {
if (obj == this)
return true;
if (!(obj instanceof MyEntityClass))
return false;
MyEntityClass other = (MyEntityClass) obj;
return Objects.equals(getId(), other.getId());
}
#Override
public int hashCode() {
return Objects.hash(getId());
}
Note: Do not use the fields directly this.id, prefer the getter to allow for Hibernate's proxy to resolve the entity when necessary. Also, prefer instanceof to getClass() != obj.getClass(), the first will handle implementations and extends correctly, not the second.

Comparing raw object with a Stack/Queue - Overriding equals() method

I've created a class which can be inherited to create both a Stack and a Queue using LinkedLists, I've passed all the JUnit tests except the equals one, I still have no idea why it doesn't work.
#Override public boolean equals(Object o) {
if( o == null) return false;
if(o == this) return true;
if(!(o instanceof PushPop)) return false;
PushPop test1= this;
PushPop test = (PushPop)o;
while(!test.isEmpty() && !test1.isEmpty()){
if(test1.pop() != test.pop()) return false;
}
return true;
}
The test sends out an assertion error whenever it's comparing the values, specifically whenever a stack/queue has an extra value than the second one.
OK, I have located your problem:
In your test, you do the following [taken from comment]
stack.push(i);
Assert.assertFalse(stack.equals(stack2));
stack2.push(i);
Assert.assertTrue(stack.equals(stack2));
This seems reasonable. However, equals clears both stacks so when you push i onto stack2, it is no longer equal to the now-empty stack.
Hence your error.
The solution: Don't Modify Your Object In An equals Method.
I'd suggest cloning or comparing whatever your underlying data structure is (e.g., if you're using Nodes, having Node implement equals).

How to convert Java 8 map.remove to Java 1.6?

I have the following:
fruitMap.remove(fruitId, fruitProperties);
The fruitMap is:
private Map<FruitId, FruitProperties> fruitMap = new HashMap<FruitId, FruitProperties>();
When I attempt to build my code I get a:
ERROR
The method remove(Object) in the type Map<MyImplementation.FruitId, FruitProperties>
is not applicable for the arguments (Map<MyImplementation.FruitId, FruitProperties>)
What is the issue?
Note that thiis call is inside of a method "removeFruit()" inside my "FruitImplementation" class.
From the Javadocs:
The default implementation is equivalent to, for this map:
if (map.containsKey(key) && Objects.equals(map.get(key), value)) {
map.remove(key);
return true;
} else
return false;
The default implementation makes no guarantees about synchronization or atomicity properties of this method. Any implementation providing atomicity guarantees must override this method and document its concurrency properties.
So you could use that default implementation. Put it in a static helper method maybe.
But if this is supposed to be thread-safe, you may need to add some synchronization code (or consider using a ConcurrentMap, which by the way already has the remove method since Java 5).
The remove(key, value) method removes the entry for key if it is currently mapped to value. The method was added in Java 1.8. The Javadoc for the Map interface mentions the following default implementation:
if (map.containsKey(key) && Objects.equals(map.get(key), value)) {
map.put(key, newValue);
return true;
} else
return false;
Since the Objects class was only added in Java 1.7, for Java 1.6 you have to write the equality test yourself. So, if you don't need the return value of the method, you can replace map.remove(key, value) with:
if (map.containsKey(key) {
Object storedValue = map.get(key);
if (storedValue == null ? value == null : storedValue.equals(value)) {
map.remove(key);
}
}
Note that this is not thread-safe. If you access the map from multiple threads, you will have to add a synchronized block.
You'll have to test the value yourself:
if(fruitProperties.equals(fruitMap.get(fruitId)) {
fruitMap.remove(fruitId);
}
Note, my implementation here assumes you are testing a non-null fruitProperties object.
You need to do the following assuming your values cannot be null
if (fruitProperties.equals(fruitMap.get(fruitId))
fruitMap.remove(fruitId);
Note: for this to be thread safe you would need to wrap this in a synchronized block.
Here is complete solution, handling synchronization and specific cases like null values.
synchronized (fruitMap)
{
if ((fruitMap.containsKey(fruitId) // The key is present
&& (
(fruitProperties == null && fruitMap.get(fruitId) == null) // fruitProperties is null, so is the stored value
|| (fruitProperties != null && fruitProperties.equals(fruitMap.get(fruitId)))
)
)
{
fruitMap.remove(fruitId);
}
}
It works in Java 6, it's an equivalent to :
fruitMap.remove(fruitId, fruitProperties);
Objects.equals has an implementation like this :
public static boolean equals(Object a, Object b) {
return (a == b) || (a != null && a.equals(b));
}
Therefore, the default implementation of remove :
if (map.containsKey(key) && Objects.equals(map.get(key), value)) {
map.remove(key);
return true;
} else
return false;
Can be written in Java 6 as :
if (map.containsKey(key) && ((map.get(key) == value) || (map.get(key) != null && map.get(key).equals(value)))) {
map.remove(key);
return true;
} else
return false;
As per the Java Doc, remove(Object key, Object value)
Removes the entry for the specified key only if it is currently mapped
to the specified value.
If your equals() is properly defined, you can do something like this
FruitProperties valueFromMap = map.get(key);
if(valueFromMap != null){
if( valueFromMap == originalValue || valueFromMap.equals(originalValue)){
map.remove(key);
}
}
Now that you're using simple HashMap, I assume that you'll take care of thread-safety by either synchronizing or change it to ConcurrentHashMap :)

Best practice to validate null and empty collection in Java

I want to verify whether a collection is empty and null. Could anyone please let me know the best practice.
Currently, I am checking as below:
if (null == sampleMap || sampleMap.isEmpty()) {
// do something
}
else {
// do something else
}
If you use the Apache Commons Collections library in your project, you may use the CollectionUtils.isEmpty(...) and MapUtils.isEmpty(...) methods which respectively check if a collection or a map is empty or null (i.e. they are "null-safe").
The code behind these methods is more or less what user #icza has written in his answer.
Regardless of what you do, remember that the less code you write, the less code you need to test as the complexity of your code decreases.
That is the best way to check it. You could write a helper method to do it:
public static boolean isNullOrEmpty( final Collection< ? > c ) {
return c == null || c.isEmpty();
}
public static boolean isNullOrEmpty( final Map< ?, ? > m ) {
return m == null || m.isEmpty();
}
If you use Spring frameworks, then you can use CollectionUtils to check against both Collections (List, Array) and Map etc.
if(CollectionUtils.isEmpty(...)) {...}
When you use spring then you can use
boolean isNullOrEmpty = org.springframework.util.ObjectUtils.isEmpty(obj);
where obj is any [map,collection,array,aything...]
otherwise: the code is:
public static boolean isEmpty(Object[] array) {
return (array == null || array.length == 0);
}
public static boolean isEmpty(Object obj) {
if (obj == null) {
return true;
}
if (obj.getClass().isArray()) {
return Array.getLength(obj) == 0;
}
if (obj instanceof CharSequence) {
return ((CharSequence) obj).length() == 0;
}
if (obj instanceof Collection) {
return ((Collection) obj).isEmpty();
}
if (obj instanceof Map) {
return ((Map) obj).isEmpty();
}
// else
return false;
}
for String best is:
boolean isNullOrEmpty = (str==null || str.trim().isEmpty());
Personally, I prefer to use empty collections instead of null and have the algorithms work in a way that for the algorithm it does not matter if the collection is empty or not.
We'll check a Collection object is empty, null or not. these all methods which are given below, are present in org.apache.commons.collections4.CollectionUtils package.
Check on List or set type of collection Objects.
CollectionUtils.isEmpty(listObject);
CollectionUtils.isNotEmpty(listObject);
Check on Map type of Objects.
MapUtils.isEmpty(mapObject);
MapUtils.isNotEmpty(mapObject);
The return type of all methods is boolean.
You can use org.apache.commons.lang.Validate's "notEmpty" method:
Validate.notEmpty(myCollection) -> Validate that the specified argument collection is neither null nor a size of zero (no elements); otherwise throwing an exception.
If you need to check for null, that is the way. However, if you have control on this, just return empty collection, whenever you can, and check only for empty later on.
This thread is about the same thing with C#, but the principles applies equally well to java. Like mentioned there, null should be returned only if
null might mean something more specific;
your API (contract) might force you to return null.
For all the collections including map use: isEmpty method which is there on these collection objects. But you have to do a null check before:
Map<String, String> map;
........
if(map!=null && !map.isEmpty())
......

Categories