Search a base class's object in a Set of Derived class - java

I've a class called Skill as:
public class Skill {
private final int type;
private final int level;
public Skill(int type, int level) {
this.type = type;
this.level = level;
}
// Getters
#Override
public int hashCode() {
int h = 17;
h = 31 * h + type;
h = 31 * h + level;
return h;
}
#Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj instanceof Skill) {
Skill that = (Skill) obj;
return this.type == that.type && this.level == that.level;
}
return false;
}
}
Another called PSkill as:
public class PSkill extends Skill {
private final int preferenceLevel;
private final boolean mandatory;
public PSkill(int type, int level, int preferenceLevel, boolean mandatory) {
super(type, level);
this.preferenceLevel = preferenceLevel;
this.mandatory = mandatory;
}
// Getters
#Override
public int hashCode() {
return super.hashCode();
}
#Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj instanceof PSkill) {
PSkill that = (PSkill) obj;
return super.equals(obj)
&& this.preferenceLevel == that.preferenceLevel
&& this.mandatory == that.mandatory;
}
return false;
}
}
My requirement:
Search a set of PSkill objects to find a match for Skill object.
For e.g.: Skill - type:1, level:2 is a match for PSkill - type:1, level:2, preferenceLevel: any, mandatory: any
When I run the below, it works.
public class Invoker {
public static void main(String[] args) {
Set<PSkill> skills = new HashSet<>(Arrays.asList(new PSkill(1, 1, 1, true), new PSkill(1, 2, 1, true)));
System.out.println(skills.contains(new Skill(1, 1))); // prints true
System.out.println(skills.contains(new Skill(1, 3))); // prints false
}
}
And I know why because the hashCode() implementation for both types is same and in HashSet implementation, key.equals(k) is used and key in my case is Skill object and hence, the equality works.
From HashSet implementation
final Node<K,V> getNode(int hash, Object key) {
Node<K,V>[] tab; Node<K,V> first, e; int n; K k;
if ((tab = table) != null && (n = tab.length) > 0 &&
(first = tab[(n - 1) & hash]) != null) {
if (first.hash == hash && // always check first node
((k = first.key) == key || (key != null && key.equals(k)))) // here
return first;
if ((e = first.next) != null) {
if (first instanceof TreeNode)
return ((TreeNode<K,V>)first).getTreeNode(hash, key);
do {
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k)))) // and here
return e;
} while ((e = e.next) != null);
}
}
return null;
}
I know I broke the hashCode() and equals() contract. But the code is working as it should, i.e. check if a Skill matches any of the PSkills in the set.
My question is:
Is the equality check key.equals(k) in HashSet implementation dependent and could be reversed in a future release i.e. k.equals(key) and the code will stop working?
Also, any better ways to do it which could make it less fragile without simply looping over the collection? Thanks

This behavior is certainly not guaranteed. The contract is that if you provide correct implementations of hashCode and equals, you get a correct implementation of a hashset. If you don't follow your end of the contract, you don't have any guarantees on how well a set will work.

Related

Java Custom Object with multiple properties as Map key or concatenation of its properties

I have a requirement where I have to aggregate a number of objects based on its properties. Object has around 10 properties and aggregation must be done on all its properties. For example -
If there are two objects A and B of some class C with properties p1, p2, p3,...p10, (all properties are of String type) then these two objects must be considered equal only if all its corresponding properties are equal.
For this I have two approaches in mind using HashMap in Java-
Approach 1 - Using key as Object of tyep C and Value as Integer for count and increase the count every time an existing object is found in Map otherwise create a new key value pair.
HahsMap<C, Integer>
But in this approach since I have to aggregate on all the properties, I will have to write(override) an equals() method which will check all the string properties for equality and similarly some implementation for hashCode().
Approach 2 - Using key as a single string made by concatenation of all the properties of object and value as a wrapper object which will have two properties one the object of type C and another a count variable of Integer type.
For each object(C) create an String key by concatenation of its properties and if key already exists in the Map, get the wrapper object and update its count property, otherwise create a new key, value pair.
HashMap<String, WrapperObj>
In this approach I don't have to do any manual task to use String as key and also it is considered a good practice to use String as key in Map.
Approach 2 seems easy to implement and efficient as opposed to Approach 2 every time when equals is called all the properties will be checked one by one.
But I am not sure whether Approach 2 in a standard way of comparing two objects and performing this kind of operation.
Please suggest if there is any other way to implement this requirement, like if there is any better way to implement equals() method for using it as key when all its properties should be taken into consideration when checking for equality of objects.
Example -
Class whose objects needs aggregation with hash and equals implementation in case of Approach 1
public class Report {
private String p1;
private String p2;
private String p3;
private String p4;
.
.
.
private String p10;
#Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((p1 == null) ? 0 : p1.hashCode());
result = prime * result + ((p2 == null) ? 0 : p2.hashCode());
result = prime * result + ((p3 == null) ? 0 : p3.hashCode());
result = prime * result + ((p4 == null) ? 0 : p4.hashCode());
return result;
}
#Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (!(obj instanceof Report))
return false;
Report other = (Report) obj;
if (p1 == null) {
if (other.p1 != null)
return false;
} else if (!p1.equals(other.p1))
return false;
if (p2 == null) {
if (other.p2 != null)
return false;
} else if (!p2.equals(other.p2))
return false;
if (p3 == null) {
if (other.p3 != null)
return false;
} else if (!p3.equals(other.p3))
return false;
if (p4 == null) {
if (other.p4 != null)
return false;
} else if (!p4.equals(other.p4))
return false;
.
.
.
if (p10 == null) {
if (other.p10 != null)
return false;
} else if (!p10.equals(other.p10))
return false;
return true;
}
}
Code For aggregation Approach 1-
Map<Report, Integer> map = new HashMap<Report, Integer>();
for(Report report : reportList) {
if(map.get(report) != null)
map.put(report, map.get(report)+1);
else
map.put(report, 1);
}
Approach 2 - With wrapper class and not implementing equals and hash for Report class.
public class Report {
private String p1;
private String p2;
private String p3;
private String p4;
public String getP1() {
return p1;
}
public void setP1(String p1) {
this.p1 = p1;
}
public String getP2() {
return p2;
}
public void setP2(String p2) {
this.p2 = p2;
}
public String getP3() {
return p3;
}
public void setP3(String p3) {
this.p3 = p3;
}
public String getP4() {
return p4;
}
public void setP4(String p4) {
this.p4 = p4;
}
Report warpper class -
public class ReportWrapper {
private Report report;
private Integer count;
public Report getReport() {
return report;
}
public void setReport(Report report) {
this.report = report;
}
public Integer getCount() {
return count;
}
public void setCount(Integer count) {
this.count = count;
}
}
Code For aggregation Approach 2-
Map<String, ReportWrapper> map = new HashMap<String,
ReportWrapper>();
for(Report report : reportList) {
String key = report.getP1() + ";" + report.getP2() +
";" + report.getP3() +
";" + .....+ ";" + report.getP10();
ReportWrapper rw = map.get(key);
if(rw != null) {
rw.setCount(rw.getCount()+1);
map.put(key, rw);
}
else {
ReportWrapper wrapper = new ReportWrapper();
wrapper.setReport(report);
wrapper.setCount(1);
map.put(key, wrapper);
}
}
PSI: Here I am more concerned about, which approach is better.
Consider using the equals and hashcode methods that you can get generated from an IDE or use a tool like Lombok which will do it for you using an annotation and you don't have to write any code.
For lombok:
https://projectlombok.org/features/EqualsAndHashCode
How to use #EqualsAndHashCode With Include - Lombok
This is what IDEA generates if you want to go that route. No special process required.
#Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
Report report = (Report) o;
return Objects.equals(prop1, report.prop1) &&
Objects.equals(prop2, report.prop2) &&
Objects.equals(prop3, report.prop3) &&
Objects.equals(prop4, report.prop4) &&
Objects.equals(prop5, report.prop5) &&
Objects.equals(prop6, report.prop6) &&
Objects.equals(prop7, report.prop7) &&
Objects.equals(prop8, report.prop8) &&
Objects.equals(prop9, report.prop9);
}
#Override
public int hashCode() {
return Objects.hash(prop1, prop2, prop3, prop4, prop5, prop6, prop7, prop8, prop9);
}

Java Compare 2 class objects

public class Glasses {
String manufacturer;
int price;
String type;
Glasses() {
}
Glasses(String prd, int pr) {
manufacturer = prd;
price = pr;
}
Glasses(String prd, int pr, String t) {
manufacturer = prd;
price = pr;
type = t;
}
public static void Compare() {
}
}
this is my code and i want to compare 2 glasses like i initialise an object glasses1(ray-ban,200,squared) and glasses2(china,100,rounded) and I want them to be compared. How can i do that?
You can overwrite the method equals(Object) from class Object which returns the boolean if they are equal (you can define by yourself within this method what needs to be equal to define these instances as equal).
If you want the classes to be sortable for sorted map then you can implement the interface Comparable and the method int compare(Object).
For instance:
#Override
public int compareTo(Glasses other) {
if (equals(other)) {
return 0;
} else {
//FIXME: when is a Glasses object smaller then an other Glasses object??
return 0;
}
}
#Override
public boolean equals(Object o) {
boolean equal = false;
if (o instanceof Glasses) {
Glasses other = (Glasses) o;
equal = true;
equal &= (manufacturer == null && other.manufacturer == null) || (manufacturer != null && manufacturer.equals(other.manufacturer));
equal &= price == other.price;
equal &= (type == null && other.type == null) || (type != null && type.equals(other.type));
}
return equal;
}
The implementation of the compare method is buggy because I don't know your logic when a Glasses object is 'smaller' or 'bigger' than an other Glasses object. Maybe you just need the equals method?
Addition:
The equals method can be made a little bit smaller but maybe a little bit harder to understand (depends on the fact if you know the handling of the "?"-operator):
#Override
public boolean equals(Object o) {
boolean equal = false;
if (o instanceof Glasses) {
Glasses other = (Glasses) o;
equal = true;
equal &= manufacturer == null ? other.manufacturer == null : manufacturer.equals(other.manufacturer);
equal &= price == other.price;
equal &= type == null ? other.type == null : type.equals(other.type);
}
return equal;
}

Example of 2 objects in java having same hash value but equals method returns false on them

We all know that if equals method returns true, then two objects are equal.
Can anybody give an example where 2 objects have the same hash value but they are actually different?
I'm assuming you're familiar with the contract(s) associated with overriding equals() and hashCode(), and the implications of a collision-prone hashCode implementation. Given that, the following trivial example uses an object that holds two Integers and implements a very simple hashCode, and demonstrates how easy it is to have two objects that aren't equal but have the same hashCode. Providing a more sophisticated hashCode algorithm can alleviate this.
The output of running main is:
hashCodes: ih1: 6, ih2: 6
equals: false
Example code:
package example.stackoverflow;
public class IntHolder
{
private Integer primaryData;
private Integer secondaryData;
public IntHolder(Integer primaryData, Integer secondaryData)
{
this.primaryData = primaryData;
this.secondaryData = secondaryData;
}
#Override
public int hashCode()
{
return ((primaryData == null) ? 0 : primaryData.hashCode()) +
((secondaryData == null) ? 0 : secondaryData.hashCode());
}
#Override
public boolean equals(Object obj)
{
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
IntHolder other = (IntHolder) obj;
if (primaryData == null)
{
if (other.primaryData != null)
return false;
}
else if (!primaryData.equals(other.primaryData))
return false;
if (secondaryData == null)
{
if (other.secondaryData != null)
return false;
}
else if (!secondaryData.equals(other.secondaryData))
return false;
return true;
}
public static void main(String[] args)
{
IntHolder ih1 = new IntHolder(1, 5);
IntHolder ih2 = new IntHolder(3, 3);
System.out.println("hashCodes: ih1: " + ih1.hashCode() + ", ih2: " + ih2.hashCode());
System.out.println("equals: " + ih1.equals(ih2));
}
}
For reference, Eclipse's auto-generated hashCode() for the IntHolder class is:
#Override
public int hashCode()
{
final int prime = 31;
int result = 1;
result = prime * result
+ ((primaryData == null) ? 0 : primaryData.hashCode());
result = prime * result
+ ((secondaryData == null) ? 0 : secondaryData.hashCode());
return result;
}
String str1="abcdef";
String str2="abcdfG";
They both have the same hashcode and equals method returns false.
public class Employee {
protected long employeeId;
public boolean equals(Object o){
if(o == null) return false;
if(!(o instanceof) Employee) return false;
Employee other = (Employee) o;
return this.employeeId == other.employeeId;
}
public int hashCode(){
return (int) this.employeeId;
}
}
In this example, we have overridden the equals method - two employees are equal when they will have same employee id.
If two Employee objects are equal, they will also have the same hash code.
Your Ans -
In this example, we also implemented the hash code - hashcode is the employeeId that is rounded down to an int. That means that many employee id's could result in the same hash code, but these Employee objects would still not be equal, since they don't have the same employee id.

Code for deleting rows from ArrayList#1 that appear in another ArrayList#2 and are non-unique in ArrayList#1

This post is the continuation of my previous post. Now I have a code that I'd like to compile. The only difference is that now I'm using lists of my own class List<Row> instead of List<Integer[]>. In particular look at hashCode in Row, because it provides a compilation error.
public class Row {
private String key;
private Integer[] values;
public Row(String k,Integer[] v) {
this.key = k;
this.values = v;
}
public String getKey() {
return this.key;
}
public Integer[] getValues() {
return this.values;
}
#Override
public boolean equals(Object obj) {
if(this == obj)
return true;
if((obj == null) || (obj.getClass() != this.getClass()))
return false;
// object must be Row at this point
Row row = (Row)obj;
return ((key == row.key) && (values == row.values));
}
#Override
public int hashCode () { // HERE I HAVE A PROBLEM. DON'T KNOW HOW TO IMPLEMENT IT
return this.key;
}
}
public class Test {
public static void main(String[] args) {
List<Row> allRows = new ArrayList<Row>();
allRows.add(new Row("0",new Integer[]{1,2,3}));
allRows.add(new Row("0",new Integer[]{1,2,2}));
allRows.add(new Row("1",new Integer[]{1,2,3}));
allRows.add(new Row("2",new Integer[]{1,1,1}));
allRows.add(new Row("2",new Integer[]{1,1,1}));
List<Row> selectedRows = new ArrayList<Row>();
selectedRows.add(new Row("0",new Integer[]{1,2,3}));
selectedRows.add(new Row("2",new Integer[]{1,1,1}));
System.out.println(allRows);
System.out.println(selectedRows);
List<Row> refreshedRows = refreshRows(allRows,selectedRows);
System.out.println(refreshedRows);
}
private static List<Row> refreshRows(List<Row> allRows,List<Row> selectedRows) {
Set<Row> set1 = new HashSet<Row>();
Iterator<Row> it = allRows.iterator();
while(it.hasNext()) {
Row curr = it.next();
if (!set1.add(curr) && selectedRows.contains(curr)) {
it.remove();
}
}
return allRows;
}
}
The result, i.e. refreshedArray, should be equal to:
key = "0", values = {1,2,3}
key = "0", values = {1,2,2};
key = "1", values = {1,2,3};
key = "2", values = {1,1,1};
Try with the following. Despite minor changes, most of the code is generated by Netbeans IDE 7.0:
#Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
final Row other = (Row) obj;
if ((this.key == null) ? (other.key != null) : !this.key.equals(other.key)) {
return false;
}
if (!java.util.Arrays.deepEquals(this.values, other.values)) {
return false;
}
return true;
}
#Override
public int hashCode() {
int hash = 3;
hash = 79 * hash + (this.key != null ? this.key.hashCode() : 0);
hash = 79 * hash + java.util.Arrays.deepHashCode(this.values);
return hash;
}
Look at the signature for hashcode(). It returns a primitive integer. You are returning key which is of type String. Try something like this:
#Override
public int hashCode() {
int hash = 1;
hash = hash * 31 + key.hashCode();
//hash = hash * 31 + otherFields.hashCode() etc
return hash;
}
which your IDE can even generate for you. You should probably read up on hashcodes. Your equals method looks wrong too. What is meant by comparing the two Integer arrays for equality?

implementing compareTo method for several fields

I want to compare two object based on 5-tuple which are:
srcAddr, dstAddr, srcPort, dstPort, protocol
here is what i have:
public class Flows implements Serializable, Comparable {
String srcAddr, dstAddr, srcPort, dstPort, protocol;
public int compareTo(Flows arg0) {
if(this.srcAddr == arg0.srcAddr &&
this.dstAddr == arg0.dstAddr &&
this.srcPort == arg0.srcPort &&
this.dstPort == arg0.dstPort &&
this.protocol == arg0.protocol)
return 0;
}
}
But it doesn't work. It says can not compare two strings.
Can anyone help me to know what is the problem?
Thanks.
The compiler / code checker is warning you that comparing String values with == is almost always a mistake.
But fixing that won't really help because your code does nothing like what a correctly implemented compareTo method should do.
A straight-forward implementation of compareTo for your Flows class would be:
public int compareTo(Flows other) {
int res = this.srcAddr.compareTo(other.srcAddr);
if (res != 0) {
return res;
}
res = this.dstAddr.compareTo(other.dstAddr);
if (res != 0) {
return res;
}
res = this.srcPort.compareTo(other.srcPort);
if (res != 0) {
return res;
}
res = this.dstPort.compareTo(other.dstPort);
if (res != 0) {
return res;
}
return this.protocol.compareTo(other.protocol);
}
That assumes the the fields are never null. If they are, then write a safeCompare(String, String) method that takes care with nulls and apply it to each field as above.
EDIT
Given that you are defining compareTo you also ought to declare equals and hashCode to be consistent with them. Otherwise certain collection methods are likely to behave incorrectly.
EDIT 2
The compiler error you mention in a comment on how to override compareTo method happens because the int compareTo(Flow flow) method actually implements the compareTo method of Comparable<Flow>. If you are going to declare Flow as implementing the raw interface type Comparable then the signature needs to be
public int compareTo(Object obj) {
Flow flow = (Flow) obj;
...
But a better solution would be to change the class declaration to:
public class Flows implements Serializable, Comparable<Flow> {
...
Try:
#Override
public int compareTo(final Flows that) {
return ComparisonChain.start().
compare(this.srcAddr, that.srcAddr).
compare(this.dstAddr, that.dstAddr).
compare(this.srcPort, that.srcPort).
compare(this.dstPort, that.dstPort).
compare(this.protocol, that.protocol).
result();
}
Requires Guava
Use string.equals() instead of ==.
You can write like this also, I have done like this in my project
public int compareTo(Flows arg0) {
int comp1, comp2, comp3, comp4;
comp1 = this.srcAddr.compareTo(arg0.srcAddr);
comp2 = this.dstAddr.compareTo(arg0.dstAddr);
comp3 = this.srcPort.compareTo(arg0.srcPort);
comp4 = this.protocol.compareTo(arg0.protocol);
if (comp1 == 0 && comp2 == 0 && comp3 == 0 && comp4 == 0) {
return 0;
} else {
if (comp1 != 0)
return comp1;
else {
if (comp2 != 0)
return comp2;
else {
if (comp3 != 0)
return comp3;
else {
if (comp4 != 0)
return comp4;
else
return 0;
}
}
}
}
}

Categories