Comparing properties of objects that equal through an id - java

Lets assume there is a data service that returns instances of the class cat:
class Cat {
int id;
String name;
int iq;
}
I want to hold the instances inside a Set<Cat> that must not hold two cats with the same id. So I need to override the equals and hashcode method to only check for the id.
My question is how can I detect if a cat inside my set requires an update when I receive a new instance from the service with the same id, but different values for name and/or iq? I cannot add the properties to equals or hashcode since then it would be possible that the Set holds instances of the same id.
Do I have to compare all the fields manually or is there another Java-typical solution for this?
Edit for clarification:
Just updating the Set with the new instance would not be enough because there is code triggered on an update. So what I want to do is:
if (set.contains(newCat)) {
Cat current = set.get(newCat);
if (!current.equals(newCat)) { //obviously this is not enough
set.add(current);
//notify EventBusses and such
}
}
Solutions that came into my mind are:
current.requiresUpdate(newCat) //basically copy of equals() with properties
current.updateWith(newCat) //same as above but keeping the old instance
if (!current.name.euqals(newCat.name)) //for each property
only get the objects from the service that have changed. Optimal, but out of scope for me.
All of which would require somewhat redundant code which is why I was hoping there is a pattern or collection that does the work for me.
The solution can include Guava classes.

I think you have two distinct problems:
Comparing Cat objects: If two Cat objects are equal only if id, name and iq are equal, than implement the equals method accordingly.
Maintaining a collection of Cat objects: To maintain a collection of Cat objects in which there are no two objects with the same id, use a Map<Integer, Cat> as already suggested.
Your code may then look something like:
if (mapOfCats.contains(newCat.id)) {
Cat current = mapOfCats.get(newCat.id);
if (!current.equals(newCat)) {
mapOfCats.put(newCat.id, newCat);
// notify EventBusses and such
}
}

You just need to insert the value again.
If id will be match from previous cat object, it will be overwritten in set.
A HashSet internally maintains a HashMap to identify duplicates.

The simpler solution is: if found equal - remove it and add again, with updated values. The code can be somewhat like this :
yourSet.remove(cat);
yourSet.add(newCatObejectWithSameID);
newCatObejectWithSameID will have different name and iq.

Your requirements sound slightly strange - if the ID field is supposed to uniquely identify a Cat, then there should surely only ever be a single instance of a Cat with that ID. Or, at least, the corresponding fields on the multiple Cat instances should be equal.
However, assuming there can be multiple logically different Cat instances with the same ID, the easiest way to handle this is to use a Map<Integer, Cat>:
Map<Integer, Cat> cats = new HashMap<>();
for (Cat cat : getAllCats()) {
if (!cats.containsKey(cat.id)) {
cats.put(cat.id, cat);
} else {
// Do whatever - ignore, log a message, throw an exception?
}
}
Collection<Cat> catsWithUniqueIds = cats.values();

Related

How to override addAll function in Java?

For instance I have two Arraylists with different data types.
ArrayList<Integer> intValues = new ArrayList<Integer>();
intValues.add(1);
intValues.add(2);
intValues.add(3);
ArrayList<String> strValues = new ArrayList<String>();
strValues.add("4");
strValues.add("5");
strValues.add("6");
If both of these lists contained the same data type objects, I would easily call addAll function;
intValues.addAll(intValues2);
But of course if I try to call addAll function with these different type lists, compiler warns me with incompatible types: ArrayList cannot be converted to Collection<? extends Integer> warning.
So I have to create a bad solution like;
for(String s: strValues)
{
intValues.add(Integer.parseInt(s));
}
Is there a better way to do this, I mean, creating a class which implements List, overriding addAll function etc. so I will be able to call;
intValues.addAll(strValues);
And intValues list will contain 1,2,3,4,5,6.
Edit: I really don't want to store String values in an Integer array, I have to deal with some creepy old code at the moment and I need a Collection to hold some differend kinds of classes, trying to create a Constructor for those objects, this integer-string scenario is just a simple way to introduce my problem.
Let me tell you about my current situation with another integer-string like scenario:
Creepy class A is car, it holds car's weight, price, color, engine type.
Creepy class B is watch, it holds watch's still type, movement type, price, lug size etc.
I am trying to create a holder class, so it will hold those classes and adding a few functions (for example, overriding compare method makes the holder class to compare prices of different classes).
Now I think I have to create a HolderHolder class which implements List so I can call holderHolder.addAll(carsList) and holderHolder.addAll(watchesList), and it will hold these as Holder objects and yes, this does not look pretty.
You act as if what you want is self-evident and logical. It really isn't. "4" and 4 are entirely unrelated, and expecting that your list of integers now has a value 4 when you call addAll with "4" is, as a consequence, as bizarre as expecting your list of movies to gain 'Forrest Gump' when you call .addAll(colorsOfTheRainbow) on that, because in your mind, 'green' is so incredibly similar to 'Forrest Gump', that you might as well assume that. (Here, 'green' is "4" and 'Forrest Gump' is 4).
So let's do some work and make this more sensible:
That 'assumption' (that "4" is so similar to 4, that you want .add("4") to just mean that 4 shows up in your list) needs to encoded, explicitly, in your code. Now it makes sense, and now you can write a function that maps Green to Forrest Gump and use it for that example just the same - we've generalized the principle.
What you're really talking about is a mapping function that maps an element of your List<String> (so.. a String) to a type that your target list is of (Integer), and you then want the operation: Take this list. Map every value in it with my mapping function. Then, add all the mapped values to this other list.
That makes perfect sense.
So, write that.
List<Integer> intValues = ...;
strValues.map(Integer::valueOf).forEachOrdered(intValues::add);
Looks like bad smell.
One bad Solution can be an own implementation of an List with Type Object. But than you have to cast and work with the Classes of the primitive types.
I think i every case you have to parse or cast. That cost to much of performance just for easy call of addAll.
I would think about the incoming data and why they have to be the same but in different types?
Edit:
If i get to know it correct. It is a little bit hard to understand without more detailed infos.
But maybe you can write an mapper class to map thoose old creepy classes in one new class an then you can put these new class in an collection and can compare all by overriding equals.
public class CreepyClassMapper
{
public CreepyClassMapper(Car aCar, Watch aWatch)
{
}
#override
private boolean equals(Object obj)
{
// maybe add an instance check
CreepyClassMapper other = (CreepyClassMapper) object;
// do your compare stuff
return true;
}
}
if i were you, i will create a function like this in util class
public void append(ArrayList<Integer> intValues, ArrayList<String> strValues){
}

Check all values of object in list are unique [duplicate]

I want to remove duplicates from a list but what I am doing is not working:
List<Customer> listCustomer = new ArrayList<Customer>();
for (Customer customer: tmpListCustomer)
{
if (!listCustomer.contains(customer))
{
listCustomer.add(customer);
}
}
Assuming you want to keep the current order and don't want a Set, perhaps the easiest is:
List<Customer> depdupeCustomers =
new ArrayList<>(new LinkedHashSet<>(customers));
If you want to change the original list:
Set<Customer> depdupeCustomers = new LinkedHashSet<>(customers);
customers.clear();
customers.addAll(dedupeCustomers);
If the code in your question doesn't work, you probably have not implemented equals(Object) on the Customer class appropriately.
Presumably there is some key (let us call it customerId) that uniquely identifies a customer; e.g.
class Customer {
private String customerId;
...
An appropriate definition of equals(Object) would look like this:
public boolean equals(Object obj) {
if (obj == this) {
return true;
}
if (!(obj instanceof Customer)) {
return false;
}
Customer other = (Customer) obj;
return this.customerId.equals(other.customerId);
}
For completeness, you should also implement hashCode so that two Customer objects that are equal will return the same hash value. A matching hashCode for the above definition of equals would be:
public int hashCode() {
return customerId.hashCode();
}
It is also worth noting that this is not an efficient way to remove duplicates if the list is large. (For a list with N customers, you will need to perform N*(N-1)/2 comparisons in the worst case; i.e. when there are no duplicates.) For a more efficient solution you could use a HashSet to do the duplicate checking. Another option would be to use a LinkedHashSet as explained in Tom Hawtin's answer.
java 8 update
you can use stream of array as below:
Arrays.stream(yourArray).distinct()
.collect(Collectors.toList());
Does Customer implement the equals() contract?
If it doesn't implement equals() and hashCode(), then listCustomer.contains(customer) will check to see if the exact same instance already exists in the list (By instance I mean the exact same object--memory address, etc). If what you are looking for is to test whether or not the same Customer( perhaps it's the same customer if they have the same customer name, or customer number) is in the list already, then you would need to override equals() to ensure that it checks whether or not the relevant fields(e.g. customer names) match.
Note: Don't forget to override hashCode() if you are going to override equals()! Otherwise, you might get trouble with your HashMaps and other data structures. For a good coverage of why this is and what pitfalls to avoid, consider having a look at Josh Bloch's Effective Java chapters on equals() and hashCode() (The link only contains iformation about why you must implement hashCode() when you implement equals(), but there is good coverage about how to override equals() too).
By the way, is there an ordering restriction on your set? If there isn't, a slightly easier way to solve this problem is use a Set<Customer> like so:
Set<Customer> noDups = new HashSet<Customer>();
noDups.addAll(tmpListCustomer);
return new ArrayList<Customer>(noDups);
Which will nicely remove duplicates for you, since Sets don't allow duplicates. However, this will lose any ordering that was applied to tmpListCustomer, since HashSet has no explicit ordering (You can get around that by using a TreeSet, but that's not exactly related to your question). This can simplify your code a little bit.
List → Set → List (distinct)
Just add all your elements to a Set: it does not allow it's elements to be repeated. If you need a list afterwards, use new ArrayList(theSet) constructor afterwards (where theSet is your resulting set).
I suspect you might not have Customer.equals() implemented properly (or at all).
List.contains() uses equals() to verify whether any of its elements is identical to the object passed as parameter. However, the default implementation of equals tests for physical identity, not value identity. So if you have not overwritten it in Customer, it will return false for two distinct Customer objects having identical state.
Here are the nitty-gritty details of how to implement equals (and hashCode, which is its pair - you must practically always implement both if you need to implement either of them). Since you haven't shown us the Customer class, it is difficult to give more concrete advice.
As others have noted, you are better off using a Set rather than doing the job by hand, but even for that, you still need to implement those methods.
private void removeTheDuplicates(List<Customer>myList) {
for(ListIterator<Customer>iterator = myList.listIterator(); iterator.hasNext();) {
Customer customer = iterator.next();
if(Collections.frequency(myList, customer) > 1) {
iterator.remove();
}
}
System.out.println(myList.toString());
}
The "contains" method searched for whether the list contains an entry that returns true from Customer.equals(Object o). If you have not overridden equals(Object) in Customer or one of its parents then it will only search for an existing occurrence of the same object. It may be this was what you wanted, in which case your code should work. But if you were looking for not having two objects both representing the same customer, then you need to override equals(Object) to return true when that is the case.
It is also true that using one of the implementations of Set instead of List would give you duplicate removal automatically, and faster (for anything other than very small Lists). You will still need to provide code for equals.
You should also override hashCode() when you override equals().
Nearly all of the above answers are right but what I suggest is to use a Map or Set while creating the related list, not after to gain performance. Because converting a list to a Set or Map and then reconverting it to a List again is a trivial work.
Sample Code:
Set<String> stringsSet = new LinkedHashSet<String>();//A Linked hash set
//prevents the adding order of the elements
for (String string: stringsList) {
stringsSet.add(string);
}
return new ArrayList<String>(stringsSet);
Two suggestions:
Use a HashSet instead of an ArrayList. This will speed up the contains() checks considerably if you have a long list
Make sure Customer.equals() and Customer.hashCode() are implemented properly, i.e. they should be based on the combined values of the underlying fields in the customer object.
As others have mentioned, you are probably not implementing equals() correctly.
However, you should also note that this code is considered quite inefficient, since the runtime could be the number of elements squared.
You might want to consider using a Set structure instead of a List instead, or building a Set first and then turning it into a list.
The cleanest way is:
List<XXX> lstConsultada = dao.findByPropertyList(YYY);
List<XXX> lstFinal = new ArrayList<XXX>(new LinkedHashSet<GrupoOrigen>(XXX));
and override hascode and equals over the Id's properties of each entity
IMHO best way how to do it these days:
Suppose you have a Collection "dups" and you want to create another Collection containing the same elements but with all duplicates eliminated. The following one-liner does the trick.
Collection<collectionType> noDups = new HashSet<collectionType>(dups);
It works by creating a Set which, by definition, cannot contain duplicates.
Based on oracle doc.
The correct answer for Java is use a Set. If you already have a List<Customer> and want to de duplicate it
Set<Customer> s = new HashSet<Customer>(listCustomer);
Otherise just use a Set implemenation HashSet, TreeSet directly and skip the List construction phase.
You will need to override hashCode() and equals() on your domain classes that are put in the Set as well to make sure that the behavior you want actually what you get. equals() can be as simple as comparing unique ids of the objects to as complex as comparing every field. hashCode() can be as simple as returning the hashCode() of the unique id' String representation or the hashCode().
Using java 8 stream api.
List<String> list = new ArrayList<>();
list.add("one");
list.add("one");
list.add("two");
System.out.println(list);
Collection<String> c = list.stream().collect(Collectors.toSet());
System.out.println(c);
Output:
Before values : [one, one, two]
After Values : [one, two]

Adding unique object instances into an ArrayList

Really basic OO comprehension issue I am running into, any help is greatly appreciated.
I'm trying to add instances of "Thing" to an arraylist every-time I press a button, I can't wrap my head around how to create unique instances to add to the list. A different button press should remove the most recent object from the list.
ArrayList myList = new ArrayList<Thing>();
if(input.isKeyPressed(Input.KEY_A)){
Thing myThing = new Thing();
myThing.setNumber(myList.size());
myList.add(myThing);
}
if(input.isKeyPressed(Input.KEY_R)){
if(myList.size()>0){
myList.remove(myList.size()-1);
}
}
If I plan on making lots of "things" and I don't care about what they are called (nor do I want to keep track of unique thing-object names). How can I create a unique 'thing' object on each button press with minimal pain.
UPDATE:
Thanks for the comments, please let me try to articulate my question better...
When I create an ArrayList full of 'Thing', each instance of which is called "myThing", every instance has the same instance variables values.
If I wanted some of the 'Thing''s to have boolean isVisable = true, and other's to have boolean isVisable = false. I get stuck because each element of the list has the same name.
Make sure that Thing implements equals and hashCode correctly and then store the instances in a Set collection (i.e. HashSet). With the implementation of hashCode() and equals() it will be completely up to you which two instances of Thing are the same and hence you will be able to enforce uniqueness any way you need.
Now the trick here is that implementing hashCode() and equals() is not entirely trivial, but you need to know how to do it if you plan to use Java. So read the appropriate chapter of Effective JAva (or better yet read the entire book).
try this:
$ cat Thing.java
import java.util.*;
public class Thing{
UUID id;
Thing () {
id = UUID.randomUUID();
}
public String toString(){
return id.toString();
}
public static void main(String[] argv) {
Thing t = new Thing();
System.out.println(t);
}
}
$ javac Thing.java && java Thing
08bb3702-84d3-4bc3-b8ab-bb52b90b8f78

Get a Boolean value for whether an Object partially matches (Java)

I think this is an easy question, if I could figure out search terms to describe it. It's similar to Finding all objects that have a given property inside a collection except I just want a Boolean "is it there" result.
Say I have a sorted TreeSet of Cats, each of which has a name, age, food, etc. I have something complicated to do for each potential cat name, but I want to skip it if there already a cat in my TreeSet with that name. I don't care if any of the other attributes match. I obviously can't do if (!AlltheCats.contains(candidateName))... because then I'll have a type mismatch between the string candidateName and the object Cat. But I don't think I can create an object to search for an identical match to, because I don't care about the values for age, food, etc.
What would be an efficient/elegant way to do this?
Create a HashSet of Strings containing names, every time you invoke your method on a cat, check first if it is already in the set, and if it is, skip this cat. Modify this set as you keep going.
(*)This answer assumes you want to invoke the method for one cat [and not 0] with identical name.
Should look something like that:
Set<String> names = new HashSet<String>();
for (Cat c : set) {
if (names.contains(c.getName())) {
continue;
}
names.add(c.getName());
c.foo(); //your method here
}

Java List with Objects - find and replace (delete) entry if Object with certain attribute already exists

I've been working all day and I somehow can't get this probably easy task figured out - probably a lack of coffee...
I have a synchronizedList where some Objects are being stored. Those objects have a field which is something like an ID. These objects carry information about a user and his current state (simplified).
The point is, that I only want one object for each user. So when the state of this user changes, I'd like to remove the "old" entry and store a new one in the List.
protected static class Objects{
...
long time;
Object ID;
...
}
...
if (Objects.contains(ID)) {
Objects.remove(ID);
Objects.add(newObject);
} else {
Objects.add(newObject);
}
Obviously this is not the way to go but should illustrate what I'm looking for...
Maybe the data structure is not the best for this purpose but any help is welcome!
EDIT:
Added some information...
A Set does not really seem to fit my purpose. The Objects store some other fields besides the ID which change all the time. The purpose is, that the list will somehow represent the latest activities of a user. I only need to track the last state and only keep that object which describes this situation.
I think I will try out re-arranging my code with a Map and see if that works...
You could use a HashMap (or LinkedHashMap/TreeMap if order is important) with a key of ID and a value of Objects. With generics that would be HashMap<Object, Objects>();
Then you can use
if (map.containsKey(ID)) {
map.remove(ID);
}
map.put(newID, newObject);
Alternatively, you could continue to use a List, but we can't just modify the collection while iterating, so instead we can use an iterator to remove the existing item, and then add the new item outside the loop (now that you're sure the old item is gone):
List<Objects> syncList = ...
for (Iterator<Objects> iterator = syncList.iterator(); iterator.hasNext();) {
Objects current = iterator.next();
if (current.getID().equals(ID)) {
iterator.remove();
}
}
syncList.add(newObject);
And you can't use a Set to have only the first one stored ?
because it basically is precisely what you require.
You could use a HashSet to store the objects and then override the hashCode method in the class that the HashSet will contain to return the hashcode of your identifying field.
A Map is easiest, but a Set reflects your logic better. In that case I'd advice a Set.
There are 2 ways to use a set, depending on the equals and hashCode of your data object.
If YourObject already uses the ID object to determine equals (and hashCode obeys the contract) you can use any Set you want, a HashSet is probably best then.
If YourObjects business logic requires a different equals, taking into account multiple fields beside the ID field, then a custom comparator should be used. A TreeSet is a Set which can use such a Comparator.
An example:
Comparator<MyObject> comp = new Comparator<MyObject>{
public int compare(MyObject o1, MyObject o2) {
// NOTE this compare is not very good as it obeys the contract but
// is not consistent with equals. compare() == 0 -> equals() != true here
// Better to use some more fields
return o1.getId().hashCode() < o2.getId().hashCode();
}
public boolean equals(Object other) {
return 01.getId().equals(o2.getId());
}
}
Set<MyObject> myObjects = new TreeSet(comp);
EDIT
I have updated the code above to reflect that id is not an int, as suggested by the question.
My first option would be a HashSet, this would require that you override the hashCode and equals methods (don't forget: if you override one, override consistently the other !) so that objects with the same ID field are considered equal.
But this might break something if this assumption is NOT to be made in other parts of your application. In that case you might opt for using a HashMap (with the ID as key) or implement your own MyHashSet class (backed by such a HashMap).

Categories