UPDATE: The answer is actually in the documentation:
Note: Great care must be exercised if mutable objects are used as set
elements. The behavior of a set is not specified if the value of an
object is changed in a manner that affects equals comparisons while
the object is an element in the set.
Case closed, thanks everyone!
Edit: The referenced topic about duplicates in a hash sets does have the same point, however it does not answer my question: Why is the documentation not saying anything about that a set is only guaraneteed to work with immutable objects?
edit2: I do understand what happens. The set of course cannot know when the hashcode of the entities change after they have been added. But the point is that imo the documentation should clearly state that sets only work properly with immutable objects.
I've been working with Java for more than 5 years now, and don't laugh, but only now I realized something about the Sets. I thought I understood what a set is, namely what the doc says:
A collection that contains no duplicate elements. More formally, sets
* contain no pair of elements e1 and e2 such that * e1.equals(e2), and at most one null element.
But, this is totally not true?! See here:
public static void main(String[] args) {
Set<Entity> entitySet = new HashSet<>();
Entity e1 = new Entity("One");
Entity e2 = new Entity("Two");
entitySet.add(e1);
entitySet.add(e2);
e2.name = "One"; // !
System.out.println("Objects equal:" + e1.equals(e2));
Iterator<Entity> iterator = entitySet.iterator();
while (iterator.hasNext()) {
System.out.println(iterator.next());
}
}
static class Entity {
String name;
Entity(String name) {
this.name = name;
}
#Override
public boolean equals(Object obj) {
if (!(obj instanceof Entity)) {
return false;
}
return name.equals(((Entity) obj).name);
}
#Override
public int hashCode() {
return name.hashCode();
}
#Override
public String toString() {
return "Entity[name=" + name + "]";
}
Output:
Objects equal:true
Entity[name=One]
Entity[name=One]
So, I guess the thing about sets not containing duplicates is only true when we deal with immutable entries? But why is the doc not saying anything about it? I was never really aware of this. The problem with this of course is that the entites could contain any number of further fields that are not part of the equality definition; and they might be different in those fields. I'm thinking about something like this:
public static void main(String[] args) {
Set<Entity> entitySet = new HashSet<>();
Entity e1 = new Entity("Public", true);
Entity e2 = new Entity("Secret", false);
entitySet.add(e1);
entitySet.add(e2);
e2.name = "Public";
Iterator<Entity> iterator = entitySet.iterator();
// print only public entity (e1)
while (iterator.hasNext()) {
Entity e = iterator.next();
if (e.equals(e1)) {
System.out.println(e);
}
}
}
static class Entity {
String name;
boolean mayBeDisplayedToUser;
Entity(String name, boolean mayBeDisplayedToUser) {
this.name = name;
this.mayBeDisplayedToUser = mayBeDisplayedToUser;
}
#Override
public boolean equals(Object obj) {
if (!(obj instanceof Entity)) {
return false;
}
return name.equals(((Entity) obj).name);
}
#Override
public int hashCode() {
return name.hashCode();
}
#Override
public String toString() {
return "Entity[name=" + name + ", may be displayed:" + mayBeDisplayedToUser + "]";
}
}
Output:
Entity[name=Public, may be displayed:false] Entity[name=Public, may be
displayed:true]
So.. I'm quite puzzled right now. Am I the only one that was not aware of this?
You are adding the items to the HashSet while they are unique and then mutating the items after the fact. The containing HashSet has no idea that you broke the set contract by changing obj.name.
Related
I have a data class:
public class MyData {
final Integer alpha;
final Double beta;
final Integer foo;
final Double bar;
}
I need .equals and .hashCode to have the conventional definition involving all four fields. But I have another important requirement:
Given a large number of MyData objects, I need to rapidly check whether a new MyData object matches any of the existing ones on the .alpha and .beta fields only.
Three approaches I don't want to take:
Composite object:
public class MyData {
final MyDataKey alphaAndBeta;
final Integer foo;
final Double bar;
}
public class MyDataKey {
final Integer alpha;
final Double beta;
}
While I could then do lookups against a HashSet<MyDataKey>, it's inelegant because all other uses of the object will need to refer to dataObj.alphaAndBeta.alpha instead of dataObj.alpha.
Comparator:
public class OnlyAlphaAndBeta implements Comparator<MyData> {
int compare(MyData a, MyData b) {...}
}
This would then let a new TreeSet<MyData>(new OnlyAlphaAndBeta()) do the lookups I want; but with O(log(N)) instead of O(1).
Multi-level lookup:
public class MyDataLookup {
Map<Integer, Set<Double>> existingAlphaBeta;
boolean contains(MyData query) {
Set<Double> betas = this.existingAlphaBeta.get(query.alpha);
if (betas == null) {
return false;
}
return betas.contains(query.beta);
}
boolean add(MyData toInsert) {...};
}
This does the job, in O(1), but what if the key was more than just 2 fields? I could keep nesting Map<A, Map<B, Map<C, ...>>> for each field in the key, but this doesn't seem right. Surely I'd rather compute just one hash and look it up in one table.
I think what I'm looking for is something like HashSet, but which can be specialized to use something other than the .equals and .hashCode methods, analogous to how Comparator redefines ordering for SortedSet. Such a collection wouldn't fulfill the Set contract anymore, but it would be "set-like".
Does something like this exist in any of the big, well-maintained Java utility libraries? Alternately, am I overlooking some obvious way of accomplishing my goals?
Using a Map is the right approach, but you can encapsulate it in a Set implementation having precisely the intended behavior of “a Set with custom equals”.
public class CustomSet<E> extends AbstractSet<E> {
private final Function<E, Object> theKeyFunction;
private final HashMap<Object, E> backend = new HashMap<>();
public CustomSet(Function<E,Object> keyFunction) {
theKeyFunction = Objects.requireNonNull(keyFunction);
}
#Override
public int size() {
return backend.size();
}
#Override
public boolean add(E e) {
Objects.requireNonNull(e);
return backend.putIfAbsent(theKeyFunction.apply(e), e) == null;
}
#Override
public boolean contains(Object o) {
if(o == null) return false;
#SuppressWarnings("unchecked") E e = (E)o;
Object key;
try { key = theKeyFunction.apply(e); }
catch(ClassCastException ex) { return false; }
return backend.containsKey(key);
}
#Override
public boolean remove(Object o) {
if(o == null) return false;
#SuppressWarnings("unchecked") E e = (E)o;
Object key;
try { key = theKeyFunction.apply(e); }
catch(ClassCastException ex) { return false; }
return backend.remove(key) != null;
}
#Override
public void clear() {
backend.clear();
}
#Override
public boolean retainAll(Collection<?> c) {
return backend.values().retainAll(c);
}
#Override
public boolean removeIf(Predicate<? super E> filter) {
return backend.values().removeIf(filter);
}
#Override
public void forEach(Consumer<? super E> action) {
backend.values().forEach(action);
}
#Override
public Iterator<E> iterator() {
return backend.values().iterator();
}
#Override
public Spliterator<E> spliterator() {
return backend.values().spliterator();
}
#Override
public Object[] toArray() {
return backend.values().toArray();
}
#Override
public <T> T[] toArray(T[] a) {
return backend.values().toArray(a);
}
}
To keep it simple, this Set does not support null.
This class overrides some methods it doesn’t have to, to provide better performance when iterating or streaming over it. Besides that, it’s rather simple. If you think, “but a Set that internally uses a Map is quite inefficient”, look at the source code of HashSet or TreeSet…
This set implementation can be tested like
record Person(String name, int age) {}
Set<Person> nameSet = new CustomSet<>(Person::name);
Set<Person> ageSet = new CustomSet<>(Person::age);
for(String name: List.of("John", "Paul", "George", "Ringo")) {
for(int age: new int[] { 20, 24, 27, 31 }) {
Person p = new Person(name, age);
if(nameSet.add(p)) System.out.println("added " + p + " to nameSet");
if(ageSet.add(p)) System.out.println("added " + p + " to ageSet");
}
}
System.out.println();
System.out.println("nameSet: " + nameSet);
System.out.println("ageSet: " + ageSet);
System.out.println();
Person p = new Person("Paul", 100);
System.out.println("nameSet contains " + p + "? " + nameSet.contains(p));
System.out.println("ageSet contains " + p + "? " + ageSet.contains(p));
p = new Person("Bob", 27);
System.out.println("nameSet contains " + p + "? " + nameSet.contains(p));
System.out.println("ageSet contains " + p + "? " + ageSet.contains(p));
added Person[name=John, age=20] to nameSet
added Person[name=John, age=20] to ageSet
added Person[name=John, age=24] to ageSet
added Person[name=John, age=27] to ageSet
added Person[name=John, age=31] to ageSet
added Person[name=Paul, age=20] to nameSet
added Person[name=George, age=20] to nameSet
added Person[name=Ringo, age=20] to nameSet
nameSet: [Person[name=George, age=20], Person[name=John, age=20], Person[name=Ringo, age=20], Person[name=Paul, age=20]]
ageSet: [Person[name=John, age=20], Person[name=John, age=24], Person[name=John, age=27], Person[name=John, age=31]]
nameSet contains Person[name=Paul, age=100]?true
ageSet contains Person[name=Paul, age=100]?false
nameSet contains Person[name=Bob, age=27]?false
ageSet contains Person[name=Bob, age=27]?true
demonstrating the different understanding of equality of the two sets, which leads to the same warning as applies to TreeSet with comparators not consistent with equals. Mixing sets with different key functions can lead to the same weird behavior as mixing sorted sets with different comparators or mixing such sets with an ordinary hash set.
If the key consists of multiple properties, a dedicated key object is the way to go, but that doesn’t mean that the application domain object has to be a composed object:
record MyData(int alpha, double beta, int foo, double bar) {}
Set<MyData> set = new CustomSet<>(d -> {
record Key(int alpha, double beta) {}
return new Key(d.alpha(), d.beta());
});
set.add(new MyData(1, 1.0, 100, 1.23));
System.out.println(set.contains(new MyData(1, 1.0, -1, Double.NaN))); // true
Solutions for older Java versions without record are a bit more verbose, but the principle stays the same. If you don’t need maximum performance, you can also use List keys as they have a working equals and hashCode implementation:
// Java 8 compatible
Set<MyData> set = new CustomSet<>(d -> Arrays.asList(d.alpha(), d.beta()));
I promise that I did try searching for an answer before writing this question. But 24 hours later I had a much more productive search, and found several viable answers:
Guava Equivalence
Eclipse Collections HashingStrategy
commons-collections AbstractHashedMap
Trove4j HashingStrategy
Keep a Map of the MyData values where the map key is defined uniquely by alpha and beta. In this example I'm concatenating them as Strings:
class MyDataLookup {
Map<String, MyData> map = new HashMap<>();
public MyData put(MyData value) {
return map.put(getKey(value), value);
}
public boolean contains(MyData value) {
return map.containsKey(getKey(value));
}
private static String getKey(MyData value) {
return value.alpha +"_"+ value.beta;
}
}
From this you can easily change the definition of containment by modifying MyDataLookup#getKey (say to include foo as well).
I'm new to programming and I've got a task to make a swing/GUI crud where you are able to put in people/animal/movies into an array etc (I'm currently doing people) and it will generate mail, username etc. I've done all that but I'm missing one thing.
Now - I want to give each element in the array an unique ID of some sort where if 1 person has ID 25 for instance, so there can't be another element with the same ID 25 unless i remove that specific element.
I use a Jframe and then a java public class where i have get's and set's for my Jframe.
Sorry but I'm new - thank you.
Java.lang.Object has methods called hasCode() and equals(). These methods play a significant role in the real time application. However its use is not always common to all applications.
hashCode()
As you know this method provides the has code of an object. Basically the default implementation of hashCode() provided by Object is derived by mapping the memory address to an integer value. If look into the source of Object class , you will find the following code for the hashCode. public native int hashCode(); It indicates that hashCode is the native implementation which provides the memory address to a certain extent. However it is possible to override the hashCode method in your implementation class.
equals()
This particular method is used to make equal comparison between two objects. There are two types of comparisons in Java. One is using “= =” operator and another is “equals()”. I hope that you know the difference between this two. More specifically the “.equals()” refers to equivalence relations. So in broad sense you say that two objects are equivalent they satisfy the “equals()” condition. If you look into the source code of Object class you will find the following code for the equals() method.
So, lets create a class Person overriding these methods:
public class Person {
private Integer personId;
private String fullName;
public Integer getPersonId() {
return personId;
}
public void setPersonId(Integer personId) {
this.personId = personId;
}
public String getFullName() {
return fullName;
}
public void setFullName(String fullName) {
this.fullName = fullName;
}
#Override
public String toString() {
return "Person [personId=" + personId + ", fullName="
+ fullName + "]";
}
#Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((fullName == null) ? 0 : fullName.hashCode());
result = prime * result + ((personId == null) ? 0 : personId.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;
Person other = (Person) obj;
if (fullName == null) {
if (other.fullName != null)
return false;
} else if (!fullName.equals(other.fullName))
return false;
if (personId == null) {
if (other.personId != null)
return false;
} else if (!personId.equals(other.personId))
return false;
return true;
}
}
and now our main class to create and manage duplicate objects of class Person:
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
public class UniqueArrayExample {
//To remove duplicates objects we use here HashSet util collection
private static Set<Person> personSet = new HashSet<Person>();
public static void main(String[] args) {
//creating four objects of Person Class
Person person1 = new Person();
Person person2 = new Person();
Person person3Dupl = new Person();
Person person4 = new Person();
//third object person3Dup1 is duplicate of Object person1
person1.setPersonId(12341);
person2.setPersonId(12342);
person3Dupl.setPersonId(12341);
person4.setPersonId(12344);
person1.setFullName("Suresh Kumar");
person2.setFullName("Mahesh Singh");
person3Dupl.setFullName("Suresh Kumar");
person4.setFullName("Rajkumar Singh");
//Add those Person class Object to Set<Person> collection
personSet.add(person1);
personSet.add(person2);
personSet.add(person3Dupl);
personSet.add(person4);
//So here after getting all Objects to Iterator and by
//printing those will not give you the person1 Object duplicate.
Iterator<Person> iterator = personSet.iterator();
while(iterator.hasNext()) {
System.out.println(iterator.next().toString());
}
}
}
here output generated as above class:
Person [personId=12342, fullName=Mahesh Singh]
Person [personId=12341, fullName=Suresh Kumar]
Person [personId=12344, fullName=Rajkumar Singh]
the duplicate object possibles to remove by use of HashSet, because of overriding hashCode() and equals() methods inside class Person, if you remove those methods from the class Person, Set collection will add all Objects of Person and will remove the redundancy.
Hope this would help you.
I have an ArrayList of Objects. I want to see if that ArrayList contains an Object with a particular field.
TeamBuilder.java
public class TeamBuilder {
public static void main(String[] args) {
Team team = new Team();
team.addMember(new TeamMember("myID"));
System.out.println(team.containsMember("myID")); //false
}
}
TeamMember.java
public class TeamMember {
private String id;
public TeamMember(String id) {
this.id = id;
}
public String getID() {
return this.id;
}
#Override
public boolean equals(Object o) {
if (o instanceof TeamMember) {
o = ((TeamMember) o).getID();
}
return o.equals(this.getID());
}
}
Team.java
import java.util.ArrayList;
public class Team {
private ArrayList<TeamMember> members = new ArrayList<>();
public boolean addMember(TeamMember teamMember) {
if (members.contains(teamMember)) {
return false;
}
members.add(teamMember);
return true;
}
public boolean containsMember(String eid) {
System.out.println(members.get(0).equals(eid)); //true
System.out.println(members.contains(eid)); //false
if (members.contains(eid)) {
return true;
}
return false;
}
}
I do not want to use a loop and I do not want to overwrite arrayList.contains().
I was expecting .contains() to iterate through my list of TeamMember's and return true when it found one that was equal to the Object passed. The two Objects are equal, but the .contains() method is returning false.
How can I elegantly check to see if a Team contains a TeamMember with the specified ID? I was under the impression I could avoid a for loop because of Java method: Finding object in array list given a known attribute value, but I am not able to get it to work.
The most elegant solution is to
First and foremost fix your equals method so that it fulfills equals contract --- meaning if a.equals(b) then b.equals(a) must be true.
You should never have a TeamMember object be equal to a String. That will result in the possibility of hard to debug side effects and bugs, that you really don't want.
The equals method should never throw an exception. He should first check for reference equality, then class sameness, then field equality.
Your TeamMember class should also override hashCode() and it should use the same fields as the equals method does.
then set up a HashMap<String, TeamMember> that matches ID Strings TeamMember objects.
Be sure that your ID's are immutable.
Fill your Map with String-TeamMember pairs
Call get(String key) when you need a TeamMember that matches an ID.
We are parsing XML configuration files with JAXB into Java objects. The XML files are versioned and after loading version 1.0 and 2.0 into objects we would like to compare the two objects of the same but unknown type (there are many different configurations for all kinds of things) recursively and their field values and print out the differences.
An object might look as follows.
#XmlRootElement(name = "HelloWorld")
public class HelloWorldConfiguration {
private List<HelloWorldObject> helloWorldObjects = new ArrayList<HelloWorldObject>();
public HelloWorldConfiguration() {
HelloWorldObject o = new HelloWorldObject();
helloWorldObjects.add(o);
helloWorldObjects.add(o);
helloWorldObjects.add(o);
helloWorldObjects.add(o);
helloWorldObjects.add(o);
}
#XmlElement(name = "helloWorldObject")
public List<HelloWorldObject> getHelloWorldObjects() {
return helloWorldObjects;
}
public void setHelloWorldObjects(List<HelloWorldObject> helloWorldObjects) {
this.helloWorldObjects = helloWorldObjects;
}
}
public class HelloWorldObject {
private Stage firstName = new Stage("Tony");
private Stage secondName = new Stage("Stark");
public Stage getFirstName() {
return firstName;
}
public void setFirstName(Stage firstName) {
this.firstName = firstName;
}
public Stage getSecondName() {
return secondName;
}
public void setSecondName(Stage secondName) {
this.secondName = secondName;
}
}
For example we would like to be informed about following changes about the above HelloWorldConfiguration object?
there is additional "HelloWorldObject" item in the list (the item with its attributes must be printed on screen)
the "HelloWorldObject" at the position n has a new "firstName" value (the name of the field or XML element that changed and its value should be printed)
the new "HelloWorldObject" list is shorter by 2 following elements (the missing elements must be printed with all attributes and values)
My questions are as follows.
Would you solve this with reflection on the Java object level or compare the two different XML files?
Are there any libraries out there that already do something like that for me? On XML or Java object level?
Any examples?
Disclaimer. I am the author of the JAXB2 Basics plugin package which includes the JAXB2 Equals plugin.
If you generate your classes from an XML Schema, the JAXB2 Equals plugin might be of use for you in this use case.
The JAXB2 Equals plugin is capable of generating equals methods which do deep structure-traversing value comparison of JAXB class instances:
public boolean equals(Object object) {
final EqualsStrategy strategy = JAXBEqualsStrategy.INSTANCE;
return equals(null, null, object, strategy);
}
public boolean equals(ObjectLocator thisLocator, ObjectLocator thatLocator, Object object, EqualsStrategy strategy) {
if (!(object instanceof PurchaseOrderType)) {
return false;
}
if (this == object) {
return true;
}
final PurchaseOrderType that = ((PurchaseOrderType) object);
{
USAddress lhsShipTo;
lhsShipTo = this.getShipTo();
USAddress rhsShipTo;
rhsShipTo = that.getShipTo();
if (!strategy.equals(LocatorUtils.property(thisLocator, "shipTo", lhsShipTo), LocatorUtils.property(thatLocator, "shipTo", rhsShipTo), lhsShipTo, rhsShipTo)) {
return false;
}
}
{
USAddress lhsBillTo;
lhsBillTo = this.getBillTo();
USAddress rhsBillTo;
rhsBillTo = that.getBillTo();
if (!strategy.equals(LocatorUtils.property(thisLocator, "billTo", lhsBillTo), LocatorUtils.property(thatLocator, "billTo", rhsBillTo), lhsBillTo, rhsBillTo)) {
return false;
}
}
// ...
return true;
}
I hope you've got the idea. You can provide a "locator" which would track the location of things being compared and a strategy which will do the comparison of individual values.
As the result you can:
Do an in-depth comparison of schema-derived JAXB class instances.
Know what is different (exact values).
Know where are the differences (exact location in the object structure).
And the whole thing is reflection-free and therefore quite fast.
Below is a snippet from another project. This is from one of the tests where I compare object "before" and "after" and log the differences.
final EqualsStrategy strategy = new org.jvnet.hyperjaxb3.lang.builder.ExtendedJAXBEqualsStrategy() {
#Override
public boolean equals(ObjectLocator leftLocator,
ObjectLocator rightLocator, Object lhs, Object rhs) {
if (!super.equals(leftLocator, rightLocator, lhs, rhs)) {
logger.debug("Objects are not equal.");
super.equals(leftLocator, rightLocator, lhs, rhs);
logger.debug("Left: "
+ (lhs == null ? "null" : lhs.toString()));
if (leftLocator != null) {
logger.debug("At [" + leftLocator.getPathAsString()
+ "].");
}
logger.debug("Right: "
+ (rhs == null ? "null" : rhs.toString()));
if (rightLocator != null) {
logger.debug("At [" + rightLocator.getPathAsString()
+ "].");
}
return false;
} else
{
return true;
}
}
};
From the other hand, this approach is not a real "diff" as you may know it from VCS. It only says that something is different, but does not calculate any "shortest edit distance".
Is there such a collection implemention can let us know what objects are newly added, modified or deleted comparing to a specific point?
I want to use such a collection to hold objects loaded from database, and bind it to user interface ,so user can add new object to it, delete items in it or modify some ones. when user click a save button , I need to persist changes to database, so I need to know the changed objects.
Here is my own solution, but I am not sure about whether it is very bad, please give me some advice.
interface :
import java.util.Collection;
/**
* #author ggfan#amarsoft
*
*/
public interface DataObjectsMonitor {
/**
* take a snapshot for comparing
*/
public void snapshot();
/**
*
* #return Objects that are modified comparing to those ones before {#link #snapshot()} last called
*/
public Collection<?> getmodifiedObjects();
/**
*
* #return Objects that are deleted comparing to those ones before {#link #snapshot()} last called
*/
public Collection<?> getDeletedObjects();
/**
*
* #return Objects that are added comparing to those ones before {#link #snapshot()} last called
*/
public Collection<?> getAddedObjects();
}
Model Class must be extended from such a abstract class :
public abstract class DataObject {
public abstract int dataHashCode();
}
implemention class :
public class DataObjectListMonitor<T extends DataObject> extends ArrayList<T> implements DataObjectsMonitor {
private static final long serialVersionUID = 1L;
private Map<T, Integer> oldVersion = new HashMap<T, Integer>();
public void snapshot() {
oldVersion.clear();
for(T t : this){
oldVersion.put(t, new Integer(t.dataHashCode()));
}
}
public Collection<T> getmodifiedObjects() {
ArrayList<T> modified = new ArrayList<T>();
for(T t : oldVersion.keySet()){
if(this.contains(t) && t.dataHashCode() != oldVersion.get(t)){
modified.add(t);
}
}
return modified;
}
public Collection<T> getDeletedObjects() {
ArrayList<T> deleted = new ArrayList<T>();
for(T t : oldVersion.keySet()){
if(!this.contains(t)){
deleted.add(t);
}
}
return deleted;
}
public Collection<T> getAddedObjects() {
ArrayList<T> added = new ArrayList<T>();
for(T t : this){
if(!oldVersion.keySet().contains(t)){
added.add(t);
}
}
return added;
}
}
test :
public class Model extends DataObject {
private String id;
private String name;
public String toString() {
return "Model [id=" + id + ", name=" + name + "]";
}
public Model(String id, String name) {
super();
this.id = id;
this.name = name;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int dataHashCode() {
int dataHashCode = 0;
if(id != null){
dataHashCode += id.hashCode();
}
if(name != null){
dataHashCode += name.hashCode();
}
return dataHashCode;
}
public static void main(String[] args){
DataObjectListMonitor<Model> data = new DataObjectListMonitor<Model>();
Model m1 = new Model("m1", "model 1");
Model m2 = new Model("m2", "model 2");
Model m3 = new Model("m3", "model 3");
Model m4 = new Model("m4", "model 4");
Model m5 = new Model("m5", "model 5");
data.add(m1);
data.add(m2);
data.add(m3);
data.add(m4);
data.add(m5);
data.snapshot();
Model m6 = new Model("m6", "model 6");
data.add(m6);
m3.setName("model 3 changed");
m3.setName("model 3");
data.remove(m5);
m1.setName("model 1 chaned");
for(Model m : data.getAddedObjects()){
System.out.println("added : " + m);
}
for(Model m : data.getDeletedObjects()){
System.out.println("deleted : " + m);
}
for(Model m : data.getmodifiedObjects()){
System.out.println("modified : " + m);
}
}
}
output :
added : Model [id=m6, name=model 6]
deleted : Model [id=m5, name=model 5]
modified : Model [id=m1, name=model 1 chaned]
edit: using hashCode is totally wrong, but maybe we can use MD5 or CRC32.
You can implement a custom collection that can easily track and log add, replace and delete operations but tracking and logging modifications on collection items is quite impossible..
The general idea: implement the List interface, add a delegate (a real list) and delegate all method calls to the internal list (or set, map, queue, ...):
public class LoggingList implements List {
private List delegate = new ArrayList();
private List<ChangeEvent> history = new ArrayList<ChangeEvent>();
// ...
#Override
public boolean add(Object item) {
boolean success = delegate.add(item);
if (success) {
history.add(new ChangeEvent(Type.ADD, item));
}
return success;
}
}
But - the list can't track if someone uses a reference to item to modify its state after it has been added.
One way is to create your own collection implementing the Map interface and add a dirty flag to it. So that when you add or remove elements from the collection make the isDirty flag true and do the operation according to it.
if the existing object is changed then you need to have some different logic.
Tracking modifications means there has to be some ID that you can attach to each item, which also means your better choice is a Map instead of a Collection.
One example to do this is to subclass HashMap and intercept the put (for add/modify operations) and remove (for delete operations).
This is the general idea -- bottom line is you are most likely on your own with the implementation, unless somebody else can recommend a third-party API for this:
Map<K, V> items = new HashMap<K, V>() {
public V put(K key, V value) {
if (containsKey(key))
modified.put(key, value);
else
created.put(key, value);
return super.put(key, value);
}
public V remove(Object key) {
if (containsKey(key))
deleted.put((K) key, get(key));
return super.remove(key);
}
};
In my opinion you should not track changes in the collection but in the changed object itself. So have a changed flag as an instance field inside your object and set it to true when something changed and set it to false after you've written it to the database.
The problem in tracking inside the collection is that it is hard to keep track of modifications to objects already in the collection. Then you need something like a wrapper around each and every object that "informs" your collection when changes to that object happen. Too much in my opinion when this can be solved by tracking inside the objects itself.
This objects' states can afterwards be used to filter the collection or whatever...