We did a big code change changing a node id, which used to be represented by an int, to be now represented by a NodeId object. The challenging task now is to identify all the places that are using object == to change them to .equals(). The same for the != operator.
Is there any script or anything that exists or can be written that can identify the places more accurately than a manual eyeballing?
Your help is appreciated!
Thanks a lot.
Unfortunately, I think you are out of luck. NetBeans does not support "Find Usages" for binary operators, and a quick web search does not reveal any other tools which might. Ironically, if you wanted to replace NodeId.equals() with ==, "Find Usages" would be the right tool.
Have you considered making NodeId immutable and using a Factory Pattern to make every equivalent instance of NodeId unique? Then you would not need to replace == and !=. This is also faster than using equals(), but makes object creation take longer. This may seem like an overly complicated solution to a simple problem, but consider the trouble that a single missed == or != could be to track down later on. In contrast, you can probably write the factory code in less time than it will take you to manually search for == and != and it should be easy to unit test.
Here is a simple example where the equivalence of different instances of NodeId depends on an int. Modify the HashMap key to fit your use case. If you don't need multiple factories, you can use a static factory method with a static map rather than a class.
public class NodeId {
private final int id;
private NodeId(int id) {
this.id = id;
}
#Override
public boolean equals(Object obj) {
if (obj instanceof NodeId) return id == ((NodeId)obj).id;
else return false;
}
#Override
public int hashCode() {
int hash = 3;
hash = 29 * hash + this.id;
return hash;
}
public static class Factory {
Map<Integer, NodeId> assigned = new HashMap<>();
public NodeId getInstance(int id) {
NodeId nodeId = assigned.get(id);
if (nodeId == null) {
nodeId = new NodeId(id);
assigned.put(id, nodeId);
}
return nodeId;
}
}
}
A simple test class demonstrates usage.
public class Test {
public static void main(String[] args) {
NodeId.Factory factory = new NodeId.Factory();
NodeId a = factory.getInstance(42);
NodeId b = factory.getInstance(42);
NodeId c = factory.getInstance(3);
System.out.println(a == b); // Should print "true"
System.out.println(a == c); // Should print "false"
}
}
Related
I Used Java HashSet for storing unique elements but now I want to retrieve element but HashSet does not has something like that, Here is what I want for my problem:
for my usecase LinkInfo hashCode() and equals() methods do not use LinkInfo.id field I want to get linkinfo instance from set and update all of its' fields except id field that should be from old instance
Set<LinkInfo> fooSet = new HashSet<>()
public void updateFoo(LinkInfo linkInfo) {
LinkInfo temp = fooSet.get(linkInfo);
linkInfo.setId(temp.getId());
// now update set
fooSet.remove(linkInfo)
fooSet.add(linkInfo)
}
Rather than
LinkInfo temp = fooSet.get(linkInfo);
the below logic is the same as what you seem to want
if (fooSet.contains(linkInfo)) {
temp = linkInfo;
}
I don't know why you're doing this, but to answer the question, yes you can do this, but you should use a map, like so:
import java.util.HashMap;
import java.util.Map;
public class Main {
static class Test {
public int a,b;
public Test(int a, int b) {
this.a = a;
this.b = b;
}
#Override
public boolean equals(Object obj) {
Test that = (Test)obj;
return this.a == that.a && this.b == that.b;
}
#Override
public int hashCode() {
return a ^ b;
}
}
public static void main(String[] args) {
Map<Test,Test> map = new HashMap<>();
Test t = new Test(1,2);
System.out.println(System.identityHashCode(t));
map.put(t, t);
Test t2 = new Test(1,2);
System.out.println(System.identityHashCode(t2));
System.out.println(System.identityHashCode(map.get(t2)));
}
}
Now you are able to retrieve the instance that you put into that map initially through an instance that is equal to it.
The program printed:
475266352
1355531311
475266352
on my computer. You can use a HashSet and iterate through it achieving the same result, but it won't be O(1).
You have a bit of a logical problem here. Why should an API that depends on equals() provide a method getElementEqualTo(e)? In order to use such a method, you need to present an object that, for the API's purposes, is equivalent to the desired result. What would be the point of that?
But that doesn't mean you're out of luck. I think you're saying that your LinkInfo class provides hashCode() and equals() methods suitable for identifying the object you want by means of a different object that you can obtain. In that case, it sounds like a HashMap could serve your purpose. Just map each key to itself.
HashMap<LinkInfo, LinkInfo> infos;
public void updateInfo(LinkInfo linkInfo) {
LinkInfo temp = infos.remove(linkInfo);
if (temp != null) {
linkInfo.setId(temp.getId());
}
infos.put(linkInfo, linkInfo);
}
I do agree with Scary answer above, but in case you want the exact reference that is stored in the Set instead of an similar equal object, you may use below code:
public void updateFoo(LinkInfo linkInfo) {
LinkInfo temp = null;
for(LinkInfo curLinkInfo:fooSet) if (curLinkInfo.equals(linkInfo))temp = curLinkInfo;
if(temp!=null)
linkInfo.setId(temp.getId());
// now update set
fooSet.remove(linkInfo)
fooSet.add(linkInfo)
}
The following code is not giving me the result I'm expecting:
public static void main (String[] args) {
Set<Pair> objPair = new LinkedHashSet<Pair>();
objPair.add(new Pair(1, 0));
System.out.println("Does the pair (1, 0) exists already? "+objPair.contains(new Pair(1, 0)));
}
private static class Pair {
private int source;
private int target;
public Pair(int source, int target) {
this.source = source;
this.target = target;
}
}
The result will be:
Does the pair (1, 0) exists already? false
I can't understand why it's not working.
Or maybe I'm using the "contains" method wrong (or for the wrong reasons).
There is also another issue,
if I add the same value twice, it will be accepted, even being a set
objPair.add(new Pair(1, 0));
objPair.add(new Pair(1, 0));
It won't accept/recognize the class Pair I've created?
Thanks in Advance.
You need to override your hashCode and equals methods in your Pair class. LinkedHashSet (and other Java objects that use hash codes) will use them to locate and find your Pair objects.
Without your own hashCode() implementation, Java considers two Pair objects equal only if they are the exact same object and new, by definition, always creates a 'new' object. In your case, you want Pair objects to be consider equal if they have the same values for source and target -- to do this, you need to tell Java how it should test Pair objects for equality. (and to make hash maps work the way you expect, you also need to generate a hash code that is consistent with equals -- loosely speaking, that means equal objects must generate the same hashCode, and unequal objects should generate different hash codes.
Most IDEs will generate decent hashcode() and equals() methods for you. Mine generated this:
#Override
public int hashCode() {
int hash = 3;
hash = 47 * hash + this.source;
hash = 47 * hash + this.target;
return hash;
}
#Override
public boolean equals(Object obj) {
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
final Pair other = (Pair) obj;
if (this.source != other.source) {
return false;
}
if (this.target != other.target) {
return false;
}
return true;
}
I am getting a warning that watchStore.contains(s) is a suspicious call to java.util.Collection#contains. How can I fix it? I want to use contains() to find a particular object with the matching serial number.
public Watch findWatchBySerialNumber(long srch) {
long s = srch;
Watch watch = null;
for(int i = 0; i < watchStore.size(); i++) {
watch = watchStore.get(i);
if(watchStore.contains(s)) {
System.out.print("item found");
return watch;
}
}
System.out.print("item not found");
return null; // watch is not found.
}
Presuming that Watch is the class, watchStore is a List<Watch>, and that a field serialNo exists on Watch...
public Optional<Watch> findWatchBySerialNumber(long serial) {
return watchStore.stream()
.filter(w -> w.getSerialNo() == serial)
.findFirst();
}
If you're not using Java 8, the code is close, but a bit more dangerous since you have the chance to return null. If you can use Guava's Optional, that'd be a better choice here.
public Watch findWatchBySerialNumber(long serial) {
for(Watch w : watchStore) {
if(w.getSerialNo() == serial) {
return w;
}
}
return null;
}
Your contains isn't going to work since your list doesn't contain Longs, it contains Watchs. This is also why the compiler sees it as dubious; contains accepts an Object but it will return false if what you're looking for doesn't have a comparable equals for what's in your list.
You have to iterate over the entirety of your collection to find it in this scenario, especially since you're looking for a specific property on those objects as opposed to a specific, easy-to-provide value.
please how can I fix that. I want to use the contain() to find a
particular object with the matching serial number.
In that case override Watch's equals() to use serialNumber field for comparison.
Then add constructor that accepts serialNumber.
public class Watch {
private final long serialNumber;
public Watch(long serialNumber) {
this.serialNumber = serialNumber;
}
#Override
public boolean equals(Object obj) {
return obj == this ||
(obj instanceof Watch && ((Watch)obj).serialNumber == serialNumber);
}
#Override
public int hashCode() {
return (int)serialNumber;
}
}
Replace if(watchStore.contains(s)){ with if(watchStore.contains(watchToFind)){ where Watch watchToFind = new Watch(s);
you can use contains method from org.apache.commons.lang.ArrayUtils package.
Checks if the value is in the given array.
The method returns false if a null array is passed in.
Parameters:
array the array to search through
valueToFind the value to find
Returns:
true if the array contains the object
long [] imageHashes= {12l,13l,14l,15l};
System.out.println(ArrayUtils.contains(imageHashes, 13l));
I know that Guava has a BiMultimap class internally but didn't outsource the code. I need a data structure which is bi-directional, i.e. lookup by key and by value and also accepts duplicates.
i.e. something like this: (in my case, values are unique, but two values can point to the same key)
0 <-> 5
1 <-> 10
2 <-> 7
2 <-> 8
3 <-> 11
I want to be able to get(7) -> returning 2 and get(2) returning [7, 8].
Is there another library out there which has a data structure I can make use of?
If not, what do you suggest is the better option to handle this case? Is keeping two Multimaps in memory one with and the other with a bad practice?
P.S.: I have read this question: Bidirectional multi-valued map in Java but considering it is dated in 2011, I thought I'll open a more recent question
What do you mean by
Guava has a BiMultimap class internally but didn't outsource the code
The code of an implementation is here.
I didn't check if this is a working implementation, nor if it made it into a release or if I'm just looking at some kind of snapshot. Everything is out in the open, so you should be able to get it.
From a quick glance at the source code it looks like the implementation does maintain two MultMaps, and this should be fine for the general case.
If you don't need the whole bunch of Guava HashBiMultimap functionality, but just getByKey() and getByValue(), as you specified, I can suggest the approach, where only one HashMultiMap is used as a storage.
The idea is to treat provided key and value as equilibrium objects and put both of them in the storage map as keys and values.
For example: Let we have the following multiMap.put(0, 5), so we should get the storage map containing something like this [[key:0, value:5], [key:5, value:0]].
As far as we need our BiMultiMap to be generic, we also need to provide some wrapper classes, that should be used as storage map type parameters.
Here is this wrapper class:
public class ObjectHolder {
public static ObjectHolder newLeftHolder(Object object) {
return new ObjectHolder(object, false);
}
public static ObjectHolder newRightHolder(Object object) {
return new ObjectHolder(object, true);
}
private Object object;
private boolean flag;
private ObjectHolder(Object object, boolean flag) {
this.object = object;
this.flag = flag;
}
public Object getObject() {
return object;
}
#Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof ObjectHolder)) return false;
ObjectHolder that = (ObjectHolder) o;
if (flag != that.flag) return false;
if (!object.equals(that.object)) return false;
return true;
}
#Override
public int hashCode() {
int result = object.hashCode();
result = 31 * result + (flag ? 1 : 0);
return result;
}
}
And here is the MultiMap:
public class BiHashMultiMap<L, R> {
private Map<ObjectHolder, Set<ObjectHolder>> storage;
public SimpleBiMultiMap() {
storage = new HashMap<ObjectHolder, Set<ObjectHolder>>();
}
public void put(L left, R right) {
ObjectHolder leftObjectHolder = ObjectHolder.newLeftHolder(left);
ObjectHolder rightObjectHolder = ObjectHolder.newRightHolder(right);
put(leftObjectHolder, rightObjectHolder);
put(rightObjectHolder, leftObjectHolder);
}
private void put(ObjectHolder key, ObjectHolder value) {
if (!storage.containsKey(key)) {
storage.put(key, new HashSet<ObjectHolder>());
}
storage.get(key).add(value);
}
public Set<R> getRight(L left) {
return this.get(ObjectHolder.newLeftHolder(left));
}
public Set<L> getLeft(R right) {
return this.get(ObjectHolder.newRightHolder(right));
}
private <V> Set<V> get(ObjectHolder key) {
Set<ObjectHolder> values = storage.get(key);
if (values == null || values.isEmpty()) {
return null;
}
Set<V> result = new HashSet<V>();
for (ObjectHolder value : values) {
result.add((V)value.getObject());
}
return result;
}
}
Thing that could seem strange is the left and right prefixed variable everywhere. You can think of them as left is the original key, that was putted to map and right is the value.
Usage example:
BiHashMultiMap<Integer, Integer> multiMap = new BiHashMultiMap<Integer, Integer>();
multiMap.put(0,5);
multiMap.put(1,10);
multiMap.put(2,7);
multiMap.put(3,7);
multiMap.put(2,8);
multiMap.put(3,11);
Set<Integer> left10 = multiMap.getLeft(10); // [1]
Set<Integer> left7 = multiMap.getLeft(7); // [2, 3]
Set<Integer> right0 = multiMap.getRight(0); // [5]
Set<Integer> right3 = multiMap.getRight(3); // [7, 11]
So to get left value we need to provide right value as key and to get right value we need to provide left as a key.
And of course to make map fully function we need to provide other methods, like remove(), contains() and so on.
I was asked this in interview. using Google Guava or MultiMap is not an option.
I have a class
public class Alpha
{
String company;
int local;
String title;
}
I have many instances of this class (in order of millions). I need to process them and at the end find the unique ones and their duplicates.
e.g.
instance --> instance1, instance5, instance7 (instance1 has instance5 and instance7 as duplicates)
instance2 --> instance2 (no duplicates for instance 2)
My code works fine
declare datastructure
HashMap<Alpha,ArrayList<Alpha>> hashmap = new HashMap<Alpha,ArrayList<Alpha>>();
Add instances
for (Alpha x : arr)
{
ArrayList<Alpha> list = hashmap.get(x); ///<<<<---- doubt about this. comment#1
if (list == null)
{
list = new ArrayList<Alpha>();
hashmap.put(x, list);
}
list.add(x);
}
Print instances and their duplicates.
for (Alpha x : hashmap.keySet())
{
ArrayList<Alpha> list = hashmap.get(x); //<<< doubt about this. comment#2
System.out.println(x + "<---->");
for(Alpha y : list)
{
System.out.print(y);
}
System.out.println();
}
Question: My code works, but why? when I do hashmap.get(x); (comment#1 in code). it is possible that two different instances might have same hashcode. In that case, I will add 2 different objects to the same List.
When I retrieve, I should get a List which has 2 different instances. (comment#2) and when I iterate over the list, I should see at least one instance which is not duplicate of the key but still exists in the list. I don't. Why?. I tried returning constant value from my hashCode function, it works fine.
If you want to see my implementation of equals and hashCode,let me know.
Bonus question: Any way to optimize it?
Edit:
#Override
public boolean equals(Object obj) {
if (obj==null || obj.getClass()!=this.getClass())
return false;
if (obj==this)
return true;
Alpha guest = (Alpha)obj;
return guest.getLocal()==this.getLocal()
&& guest.getCompany() == this.getCompany()
&& guest.getTitle() == this.getTitle();
}
#Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + (title==null?0:title.hashCode());
result = prime * result + local;
result = prime * result + (company==null?0:company.hashCode());
return result;
}
it is possible that two different instances might have same hashcode
Yes, but hashCode method is used to identify the index to store the element. Two or more keys could have the same hashCode but that's why they are also evaluated using equals.
From Map#containsKey javadoc:
Returns true if this map contains a mapping for the specified key. More formally, returns true if and only if this map contains a mapping for a key k such that (key==null ? k==null : key.equals(k)). (There can be at most one such mapping.)
Some enhancements to your current code:
Code oriented to interfaces. Use Map and instantiate it by HashMap. Similar to List and ArrayList.
Compare Strings and Objects in general using equals method. == compares references, equals compares the data stored in the Object depending the implementation of this method. So, change the code in Alpha#equals:
public boolean equals(Object obj) {
if (obj==null || obj.getClass()!=this.getClass())
return false;
if (obj==this)
return true;
Alpha guest = (Alpha)obj;
return guest.getLocal().equals(this.getLocal())
&& guest.getCompany().equals(this.getCompany())
&& guest.getTitle().equals(this.getTitle());
}
When navigating through all the elements of a map in pairs, use Map#entrySet instead, you can save the time used by Map#get (since it is supposed to be O(1) you won't save that much but it is better):
for (Map.Entry<Alpha, List<Alpha>> entry : hashmap.keySet()) {
List<Alpha> list = entry.getValuee();
System.out.println(entry.getKey() + "<---->");
for(Alpha y : list) {
System.out.print(y);
}
System.out.println();
}
Use equals along with hashCode to solve the collision state.
Steps:
First compare on the basis of title in hashCode()
If the title is same then look into equals() based on company name to resolve the collision state.
Sample code
class Alpha {
String company;
int local;
String title;
public Alpha(String company, int local, String title) {
this.company = company;
this.local = local;
this.title = title;
}
#Override
public int hashCode() {
return title.hashCode();
}
#Override
public boolean equals(Object obj) {
if (obj instanceof Alpha) {
return this.company.equals(((Alpha) obj).company);
}
return false;
}
}
...
Map<Alpha, ArrayList<Alpha>> hashmap = new HashMap<Alpha, ArrayList<Alpha>>();
hashmap.put(new Alpha("a", 1, "t1"), new ArrayList<Alpha>());
hashmap.put(new Alpha("b", 2, "t1"), new ArrayList<Alpha>());
hashmap.put(new Alpha("a", 3, "t1"), new ArrayList<Alpha>());
System.out.println("Size : "+hashmap.size());
Output
Size : 2