This code is used to sort a List. The list could contain in the thousands of elements but less than 10k.
protected <E> int compareFields(E o1, E o2, String fieldName){
try {
Comparable o1Data = (Comparable) o1.getClass().getMethod(fieldName).invoke(o1);
Comparable o2Data = (Comparable) o2.getClass().getMethod(fieldName).invoke(o2);
return o1Data == null ? o2Data == null ? 0 : 1 :
o2Data == null ? -1 : o1Data.compareTo(o2Data);
} catch(Exception e) {
throw new RuntimeException(e);
}
}
I was advised to
"Please don't use reflection for things like this!!
Either supply the method with a suitable Comparator, or a method to extract the relevant property (may be computed in a way not supported by the original type), or both."
An example of a better way to do this would be nice.
Context:
I've got many screens with data tables. Each one is build from a List. Each data table needs to be sortable by each of its 6 columns. The columns are either Date or String.
Using reflection here will potentially be much slower, as you are adding number of stack frames to each comparison by using getClass, getMethod and invoke rather than using the objects' native compare method.
Ideally, you would write the method to avoid the use of object in the signature. A "suitable Comparator" would at least be strongly bound to the objects' type (which you assume are the same). If you must have dynamic field comparison (as it appears), then at least the reflection could be encapsulated in that comparator.
If you are going to call this thousands of times, though, it would be best to pre-bind a Comparator to the field you are sorting by. This way, you only call getMethod once up front, rather than once for each individual comparison.
It's hard to give a good example without context, so for now here's a small list of why it's not the best idea:
The field provided has no guarantee of being Comparable (not sure why the code here needs to catch that exception and rebrand it).
What if the type of objects provided are not meant to be compared in this way? (It's an overly generic method name to know how it's supposed to be used).
It's not strongly typed. Providing the field name as a string means you'll have to change your code everywhere whenever the property name changes, and it'll be hard to track down where you need to make those changes.
Reflection is potentially slower than if implemented in a strongly typed manner.
The other answers described well why it is not advised to use reflection. I want to add an example using a more conventional solution.
Instead of specifying the field that is used to compare the two objects, you should take a Comparator instance as an argument. This way the client who uses this method can specify how to compare the two objects.
protected <E> int compareFields(E o1, E o2, Comparator<E> comparator) {
return comparator.compare(o1, o2);
}
And an example call to this function would look like this:
MyClass a = ...;
MyClass b = ...;
Comparator<MyClass> intFieldComparator = new Comparator<MyClass> {
public int compare(MyClass o1, MyClass o2) {
int field1 = o1.getIntField();
int field2 = o2.getIntField();
return field2 - field1;
}
};
compareFields(a, b, intFieldComparator);
You can define different comparators if you want to compare the objects using several fields.
Related
Want to write an efficient and client friendly way to merge multiple immutable collections into single collection of same instance type
Something like
public static <K> Collection<K> merge(Collection<K>... collection) {
// return merged collection
}
For this, along with the set of collections clients would also need to pass either the instance type of merged collection (in which case I would need to validate compatibility with the set of collections). I don't want to go this route
Is there any way I can infer the type of collection and validate all collections are of the same type and create a new instance of the same type ? Or is this not a valid use case for a general purpose merge implementation and its better off to just provide individual merge implementations for each type ?
This is not easy and requires knowing beforehand an explicit list of types you support. This gets complicated even further by the fact that the immutable types returned by e.g. java.util.List.of(a, b) aren't public types, and their fully qualified type name is not part of the spec (meaning: It could change in a next release of java and that would not be considered a backwards compatibility break; therefore if you rely on the name, your code has a high maintenance burden).
A better idea is perhaps to allow any type of collections, and even a heterogenous list of collections (toss a set, a guava ImmutableList, and a List.of() at it, and a Collections.singleton - your code doesn't care and will merge them all), and have various methods that each return a specifically desired kind of collection.
For example:
import com.google.common.collect.ImmutableList;
public static <K> ImmutableList<K> mergeToList(Collection<K>... collections) {
var out = ImmutableList.<K>builder();
for (Collection<K> c : collections) out.addAll(c);
return out.build();
}
The alternative would be something quite ugly, such as:
import com.google.common.collect.ImmutableList;
public static <K, C extends Collection<K>> C merge(C... collections) {
if (collections.length == 0) throw new IllegalArgumentException("So many problems; one of them is that it becomes impossible to merge zero collections");
Class<?> type = collections[0].getClass();
for (C c : collections) if (c.getClass() != type) throw new IllegalArgumentException("Only pass one kind of collection");
if (type == ImmutableList.class) {
// specific code to merge immutable lists.
} else if (type == Collections.singleton("dummy").getClass()) {
// specific code to merge j.u.Collections singletons.
} else if (type == List.of().getClass()) {
// this becomes virtually impossible; List.of()'s returned type depends on the number of args...
} else {
throw new IllegalArgumentException("Unsupported type: " + type);
}
}
Hopefully that makes clear why you really can't do this. A final option is to have a merge method that takes as parameter a type to merge into, but there is no way to know how to MAKE a collection type. Given com.google.common.collect.ImmutableList, without hardcoding into your source code how to do it, how would you know how to construct a new one?
This is why factories exist in java, and you need one here. collections don't ship with them, so you'd have to explicitly write them for each type you want to support.
I have one class having two variables named as x and y. In this class I have overrided the equals and hashCode methods to compare two object of this class. But our requirement is to compare two object of this class sometimes on the basis of x and sometimes on the basis of y. Is it possible dynamically in Java?
Edit:
I have one more class named as B, in this class there is two method m1 and m2 and I want to compare the above class object in such a way that when we call from m1 (for sorting) the above objects will be compared on the basis of x (means compare object by compare x variable) and when we call from m2 (for sorting) then we compare according to y.
Changing behavior based on last method to call your method is possible, but you shouldn't do it for a lot of reasons.
it violates the equals contract, thus breaking the functionality of several algorithms designed to handle collections
result of the comparison cannot be anymore known without knowing the caller, which is a hard dependency that's prone to break
However, if you insist you need it, you can do like
StackTraceElement[] stackTraceElements = Thread.currentThread().getStackTrace();
if (stackTraceElements.length < 3)
{
// do something when last method to call is not available
// probably you'll want to return something
}
String callerMethod = stackTraceElements[2].getMethodName();
if (callerMethod.equals("m1"))
{
// something
} else
{
// something else
}
This example is simplified as it assumes the method calling this method is the candidate - it can be some method further down the call stack.
As noted, this is not recommended. Rather use different kind of comparators for the purpose, and give a relevant comparator to the sort method to have different kind of sorting per context.
Depending on the complexity of the comparison, you can either do this within the class or use two seperate comparator classes.
public boolean equals(Object other){
if(condition == true){
return x==x;
}else{
return y==y;
}
}
or
public boolean equals(Object other){
if(condition == true){
return new CompareX(this, other).compare();
}else{
return new CompareY(this, other).compare();
}
}
You have to extend the comparison logic to a valid one, of course.
Oh and, the same principle applies to the hashCode.
It's not possible, to change the behaviour of equals dynamically. You have to use Comparator to provide the comparison from the outside of the class.
Since Java8 with Lambdas it is easy to use Comparators.
There is a method comparing. You can create Comparators out of Methods, which you want to compare.
// A comparator comparing on x
Comparator<A> comp1 = comparing (a -> a.x);
// A comparator comparing on the output of m1
Comparator<A> comp2 = comparing (A::m1);
// A comparator comparing on the output of m1 and when equals, comparing on x
Comparator<A> comp2 = comparing (A::m1).thenComparing (a -> a.x);
From the external point you can decide, which comparator to use.
There's a new way to sort your data in Java8, too:
List<A> data;
data.stream ().sorted (comparing (a -> a.x));
Of course you have to be allowed to use Java8 for this.
If you can add flag setting code to m1 and m2 you can modify eis answer to get rid of the kludgy stacktrace stuff.
It is still kludgy.
I am coding something like this:
List<Bean> beans = service.findBeans();
Collections.sort(beans, new BeanComparator());
return beans;
It works perfectly. What I am looking for is a shortcut to do this with just one line:
return somelibrary.Collections.sort(service.findBeans(), new BeanComparator());
Or:
return somelibrary.newList(service.findBeans(), new BeanComparator());
Note that it is required a mutable list.
This is one line:
List<Bean> beans = service.findBeans(); Collections.sort(beans, new BeanComparator()); return beans;
But more seriously, Java isn't really the right language for one-liners. Also, just because something is a one-liner doesn't mean that it is any better. For example, I was initially surprised to discover that this:
return condition ? a : b;
Creates a longer bytecode than
if( condition )
return a;
else
return b;
But that's just how the language and compiler are.
If you insist on your one-liner, Guava's Ordering can do it:
return Ordering.from( new BeanComparator() ).sortedCopy( service.findBeans() );
The returned list is modifiable, serializable, and has random access.
Efficiency-wise I think there's a bit of a waste in terms of overhead. And also you're now dependent on a 3rd-party library. You'd be essentially using very powerful tools for a very simple task. It's overkill if this is all you're using it for.
I believe the following function will yield the results you want. Just put it in the class of your choice.
public static <T> List<T> sort(List<T> list, Comparator<? super T> compare) {
Collections.sort(list, compare);
return list;
}
You could use apache CollectionUtils to collate the list with a comparator and an empty list.
CollectionUtils.collate(service.findBeans().iterator(),Collections.EMPTY_LIST.iterator(),new beanComparator())
CollectionUtils really should add a utility method that returns the sorted list...
Classic rebut to use-more-lines is LOGGING. You logging for readability purposes should not take up more than one line. Logging is static noise when you're trying to find out what code is actually doing, yet logging is fairly critical.
So logging should be compact (in one line) and quiet (should not throw exceptions/be nullsafe) and should be performant (if turned off should not introduce excess processing beyond isDebugOn() check.
Second rebut is fluent interfaces such as JOOQ which are becoming much more prevalent.
I guess if you have no duplicates and don't mind hacky code you could use:
return new ArrayList<Bean>(new TreeSet<Bean>(service.findBeans()));
I think the original question posted is valid. Because the "Collections.sort(..)" method has the intended side-effect of sorting the passed-in Collection, if you wanted to maintain your original Collection, you'd have to do the following:
List<Bean> beans = service.findBeans();
List<Bean> sortedBeans = new ArrayList<Bean>(beans);
Collections.sort(sortedBeans, new BeanComparator());
return sortedBeans;
In the case above, it's probably not that big a deal that we sort the Collection returned by a service method. But, what if the Collection we are sorting was a method parameter, and the caller did not want the Collection passed-in sorted?
I usually prefer to have methods without consequences.
Since "Collections.sort(..)" affects the list, I have to write the following code:
public void doSomethingWithBeansInOrder(List<Bean> beans) {
Collection<Bean> sortedBeans = new ArrayList<Bean>(beans);
Collections.sort(sortedBeans, ...comparator...;
for (Bean bean : sortedBeans) {
.. do something
}
}
I find the definition of "sortedBeans" ugly.
If "(Collections.sort(..)" (or something like it), returned a new Collection and did not affect the passed-in Collection, I could write:
public void doSomethingWithBeansInOrder(List<Bean> beans) {
for (Bean bean : Collections.sort(beans, ...comparator...) {
.. do something
}
}
The answer for Guava's Ordering is best, in my opinion.
To start with, Java 8 introduced the sort() metod on the List interface. So, an example of sorting the actual list would be:
List<Integer> integerList = Arrays.asList(3, 2, 1);
integerList.sort(Comparator.naturalOrder());
return integerList;
Here, I used the pre-defined naturalOrder() comparator, which in turn relies on Comparable, but a custom comparator can also be used. This still requires two statements.
However, if the desired behaviour is to create a new sorted list, and leave the orignial as it was before, I guess a stream would be the easiest way to do it:
integerList.stream().sorted(Comparator.naturalOrder()).collect(Collectors.toList());
The same thing as above applies to the comparator here.
With a TreeMap it's trivial to provide a custom Comparator, thus overriding the semantics provided by Comparable objects added to the map. HashMaps however cannot be controlled in this manner; the functions providing hash values and equality checks cannot be 'side-loaded'.
I suspect it would be both easy and useful to design an interface and to retrofit this into HashMap (or a new class)? Something like this, except with better names:
interface Hasharator<T> {
int alternativeHashCode(T t);
boolean alternativeEquals(T t1, T t2);
}
class HasharatorMap<K, V> {
HasharatorMap(Hasharator<? super K> hasharator) { ... }
}
class HasharatorSet<T> {
HasharatorSet(Hasharator<? super T> hasharator) { ... }
}
The case insensitive Map problem gets a trivial solution:
new HasharatorMap(String.CASE_INSENSITIVE_EQUALITY);
Would this be doable, or can you see any fundamental problems with this approach?
Is the approach used in any existing (non-JRE) libs? (Tried google, no luck.)
EDIT: Nice workaround presented by hazzen, but I'm afraid this is the workaround I'm trying to avoid... ;)
EDIT: Changed title to no longer mention "Comparator"; I suspect this was a bit confusing.
EDIT: Accepted answer with relation to performance; would love a more specific answer!
EDIT: There is an implementation; see the accepted answer below.
EDIT: Rephrased the first sentence to indicate more clearly that it's the side-loading I'm after (and not ordering; ordering does not belong in HashMap).
.NET has this via IEqualityComparer (for a type which can compare two objects) and IEquatable (for a type which can compare itself to another instance).
In fact, I believe it was a mistake to define equality and hashcodes in java.lang.Object or System.Object at all. Equality in particular is hard to define in a way which makes sense with inheritance. I keep meaning to blog about this...
But yes, basically the idea is sound.
A bit late for you, but for future visitors, it might be worth knowing that commons-collections has an AbstractHashedMap (in 3.2.2 and with generics in 4.0). You can override these protected methods to achieve your desired behaviour:
protected int hash(Object key) { ... }
protected boolean isEqualKey(Object key1, Object key2) { ... }
protected boolean isEqualValue(Object value1, Object value2) { ... }
protected HashEntry createEntry(
HashEntry next, int hashCode, Object key, Object value) { ... }
An example implementation of such an alternative HashedMap is commons-collections' own IdentityMap (only up to 3.2.2 as Java has its own since 1.4).
This is not as powerful as providing an external "Hasharator" to a Map instance. You have to implement a new map class for every hashing strategy (composition vs. inheritance striking back...). But it's still good to know.
HashingStrategy is the concept you're looking for. It's a strategy interface that allows you to define custom implementations of equals and hashcode.
public interface HashingStrategy<E>
{
int computeHashCode(E object);
boolean equals(E object1, E object2);
}
You can't use a HashingStrategy with the built in HashSet or HashMap. GS Collections includes a java.util.Set called UnifiedSetWithHashingStrategy and a java.util.Map called UnifiedMapWithHashingStrategy.
Let's look at an example.
public class Data
{
private final int id;
public Data(int id)
{
this.id = id;
}
public int getId()
{
return id;
}
// No equals or hashcode
}
Here's how you might set up a UnifiedSetWithHashingStrategy and use it.
java.util.Set<Data> set =
new UnifiedSetWithHashingStrategy<>(HashingStrategies.fromFunction(Data::getId));
Assert.assertTrue(set.add(new Data(1)));
// contains returns true even without hashcode and equals
Assert.assertTrue(set.contains(new Data(1)));
// Second call to add() doesn't do anything and returns false
Assert.assertFalse(set.add(new Data(1)));
Why not just use a Map? UnifiedSetWithHashingStrategy uses half the memory of a UnifiedMap, and one quarter the memory of a HashMap. And sometimes you don't have a convenient key and have to create a synthetic one, like a tuple. That can waste more memory.
How do we perform lookups? Remember that Sets have contains(), but not get(). UnifiedSetWithHashingStrategy implements Pool in addition to Set, so it also implements a form of get().
Here's a simple approach to handle case-insensitive Strings.
UnifiedSetWithHashingStrategy<String> set =
new UnifiedSetWithHashingStrategy<>(HashingStrategies.fromFunction(String::toLowerCase));
set.add("ABC");
Assert.assertTrue(set.contains("ABC"));
Assert.assertTrue(set.contains("abc"));
Assert.assertFalse(set.contains("def"));
Assert.assertEquals("ABC", set.get("aBc"));
This shows off the API, but it's not appropriate for production. The problem is that the HashingStrategy constantly delegates to String.toLowerCase() which creates a bunch of garbage Strings. Here's how you can create an efficient hashing strategy for case-insensitive Strings.
public static final HashingStrategy<String> CASE_INSENSITIVE =
new HashingStrategy<String>()
{
#Override
public int computeHashCode(String string)
{
int hashCode = 0;
for (int i = 0; i < string.length(); i++)
{
hashCode = 31 * hashCode + Character.toLowerCase(string.charAt(i));
}
return hashCode;
}
#Override
public boolean equals(String string1, String string2)
{
return string1.equalsIgnoreCase(string2);
}
};
Note: I am a developer on GS collections.
Trove4j has the feature I'm after and they call it hashing strategies.
Their map has an implementation with different limitations and thus different prerequisites, so this does not implicitly mean that an implementation for Java's "native" HashMap would be feasible.
Note: As noted in all other answers, HashMaps don't have an explicit ordering. They only recognize "equality". Getting an order out of a hash-based data structure is meaningless, as each object is turned into a hash - essentially a random number.
You can always write a hash function for a class (and often times must), as long as you do it carefully. This is a hard thing to do properly because hash-based data structures rely on a random, uniform distribution of hash values. In Effective Java, there is a large amount of text devoted to properly implementing a hash method with good behaviour.
With all that being said, if you just want your hashing to ignore the case of a String, you can write a wrapper class around String for this purpose and insert those in your data structure instead.
A simple implementation:
public class LowerStringWrapper {
public LowerStringWrapper(String s) {
this.s = s;
this.lowerString = s.toLowerString();
}
// getter methods omitted
// Rely on the hashing of String, as we know it to be good.
public int hashCode() { return lowerString.hashCode(); }
// We overrode hashCode, so we MUST also override equals. It is required
// that if a.equals(b), then a.hashCode() == b.hashCode(), so we must
// restore that invariant.
public boolean equals(Object obj) {
if (obj instanceof LowerStringWrapper) {
return lowerString.equals(((LowerStringWrapper)obj).lowerString;
} else {
return lowerString.equals(obj);
}
}
private String s;
private String lowerString;
}
good question, ask josh bloch. i submitted that concept as an RFE in java 7, but it was dropped, i believe the reason was something performance related. i agree, though, should have been done.
I suspect this has not been done because it would prevent hashCode caching?
I attempted creating a generic Map solution where all keys are silently wrapped. It turned out that the wrapper would have to hold the wrapped object, the cached hashCode and a reference to the callback interface responsible for equality-checks. This is obviously not as efficient as using a wrapper class, where you'd only have to cache the original key plus one more object (see hazzens answer).
(I also bumped into a problem related to generics; the get-method accepts Object as input, so the callback interface responsible for hashing would have to perform an additional instanceof-check. Either that, or the map class would have to know the Class of its keys.)
This is an interesting idea, but it's absolutely horrendous for performance. The reason for this is quite fundamental to the idea of a hashtable: the ordering cannot be relied upon. Hashtables are very fast (constant time) because of the way in which they index elements in the table: by computing a pseudo-unique integer hash for that element and accessing that location in an array. It's literally computing a location in memory and directly storing the element.
This contrasts with a balanced binary search tree (TreeMap) which must start at the root and work its way down to the desired node every time a lookup is required. Wikipedia has some more in-depth analysis. To summarize, the efficiency of a tree map is dependent upon a consistent ordering, thus the order of the elements is predictable and sane. However, because of the performance hit imposed by the "traverse to your destination" approach, BSTs are only able to provide O(log(n)) performance. For large maps, this can be a significant performance hit.
It is possible to impose a consistent ordering on a hashtable, but to do so involves using techniques similar to LinkedHashMap and manually maintaining the ordering. Alternatively, two separate data structures can be maintained internally: a hashtable and a tree. The table can be used for lookups, while the tree can be used for iteration. The problem of course is this uses more than double the required memory. Also, insertions are only as fast as the tree: O(log(n)). Concurrent tricks can bring this down a bit, but that isn't a reliable performance optimization.
In short, your idea sounds really good, but if you actually tried to implement it, you would see that to do so would impose massive performance limitations. The final verdict is (and has been for decades): if you need performance, use a hashtable; if you need ordering and can live with degraded performance, use a balanced binary search tree. I'm afraid there's really no efficiently combining the two structures without losing some of the guarantees of one or the other.
There's such a feature in com.google.common.collect.CustomConcurrentHashMap, unfortunately, there's currently no public way how to set the Equivalence (their Hasharator). Maybe they're not yet done with it, maybe they don't consider the feature to be useful enough. Ask at the guava mailing list.
I wonder why it haven't happened yet, as it was mentioned in this talk over two years ago.
Let's say I have this type in my application:
public class A {
public int id;
public B b;
public boolean equals(Object another) { return this.id == ((A)another).id; }
public int hashCode() { return 31 * id; //nice prime number }
}
and a Set<A> structure. Now, I have an object of type A and want to do the following:
If my A is within the set, update its field b to match my object.
Else, add it to the set.
So checking if it is in there is easy enough (contains), and adding to the set is easy too. My question is this: how do I get a handle to update the object within? Interface Set doesn't have a get method, and the best I could think of was to remove the object in the set and add mine. another, even worse, alternative is to traverse the set with an iterator to try and locate the object.
I'll gladly take better suggestions... This includes the efficient use of other data structures.
Yuval =8-)
EDIT: Thank you all for answering... Unfortunately I can't 'accept' the best answers here, those that suggest using a Map, because changing the type of the collection radically for this purpose only would be a little extreme (this collection is already mapped through Hibernate...)
Since a Set can only contain one instance of an object (as defined by its equals and hashCode methods), just remove it and then add it. If there was one already, that other one will be removed from the Set and replaced by the one you want.
I have code that does something similar - I am caching objects so that everywhere a particular object appears in a bunch of different places on the GUI, it's always the same one. In that case, instead of using a Set I'm using a Map, and then I get an update, I retrieve it from the Map and update it in place rather than creating a new instance.
You really want to use a Map<Integer,A>, not a Set<A>.
Then map the ID (even though it's also stored in A!) to the object. So storing new is this:
A a = ...;
Map<Integer,A> map = new HashMap<Integer,A>();
map.put( a.id, a );
Your complete update algorithm is:
public static void update( Map<Integer,A> map, A obj ) {
A existing = map.get( obj.id );
if ( existing == null )
map.put( obj.id, obj );
else
existing.b = obj.b;
}
However, it might be even simpler. I'm assuming you have more fields than that in A that what you gave. If this is not the case, just using a Map<Integer,B> is in fact what you want, then it collapses to nothing:
Map<Integer,B> map = new HashMap<Integer,B>();
// The insert-or-update is just this:
map.put( id, b );
I don't think you can make it any easier than using remove/add if you are using a Set.
set.remove(a);
set.add(a);
If a matching A was found it will be removed and then you add the new one, you don't even need the if (set.contains(A)) conditional.
If you have an object with an ID and an updated field and you don't really care about any other aspects of that object, just throw it out and replace it.
If you need to do anything else to the A that matches that ID then you'll have to iterate through the Set to find it or use a different Container (like the Map as Jason suggested).
No one has mentioned this yet, but basing hashCode or equals on a mutable property is one of those really, really big things that you shouldn't do. Don't muck about with object identity after you leave the constructor - doing so greatly increases your chances of having really difficult-to-figure out bugs down the road. Even if you don't get hit with bugs, the accounting work to make sure that you always properly update any and all data structures that relies on equals and hashCode being consistent will far outweigh any perceived benefits of being able to just change the id of the object as you run.
Instead, I strongly recommend that you pass id in via the constructor, and if you need to change it, create a new instance of A. This will force users of your object (including yourself) to properly interact with the collection classes (and many others) that rely on immutable behavior in equals and hashCode.
What about Map<A,A> I know it's redundant, but I believe it will get you the behavior you'd like. Really I'd love to see Set have a get(Object o) method on it.
You might want to generate a decorator called ASet and use an internal Map as the backing data structure
class ASet {
private Map<Integer, A> map;
public ASet() {
map = new HashMap<Integer, A>();
}
public A updateOrAdd(Integer id, int delta) {
A a = map.get(a);
if(a == null) {
a = new A(id);
map.put(id,a);
}
a.setX(a.getX() + delta);
}
}
You can also take a look at the Trove API. While that is better for performance and for accounting that you are working with primitive variables, it exposes this feature very nicely (e.g. map.adjustOrPutValue(key, initialValue, deltaValue).
It's a bit outside scope, but you forgot to re-implement hashCode(). When you override equals please override hashCode(), even in an example.
For example; contains() will very probably go wrong when you have a HashSet implementation of Set as the HashSet uses the hashCode of Object to locate the bucket (a number which has nothing to do with business logic), and only equals() the elements within that bucket.
public class A {
public int id;
public B b;
public int hashCode() {return id;} // simple and efficient enough for small Sets
public boolean equals(Object another) {
if (object == null || ! (object instanceOf A) ) {
return false;
}
return this.id == ((A)another).id;
}
}
public class Logic {
/**
* Replace the element in data with the same id as element, or add element
* to data when the id of element is not yet used by any A in data.
*/
public void update(Set<A> data, A element) {
data.remove(element); // Safe even if the element is not in the Set
data.add(element);
}
}
EDIT Yuvalindicated correctly that Set.add does not overwrite an existing element, but only adds if the element is not yet in the collection (with "is" implemented by equals)