Problem with using a Hashmap with a custom inner class - java

I have code that looks like the following:
private void MethodToDo(SpecialObject o) {
Map<InfoObj, Integer> totalNeeds = new HashMap<InfoObj, Integer>();
for (ListObject obj : o.getListOfObjects()) {
InfoObj infoObj = new InfoObj(obj.getTitle(), obj.getId());
Integer need = totalNeeds.get(infoObj);
if (need == null) {
need = new Integer(obj.getNeeded());
} else {
need = need + obj.getNeeded();
}
totalNeeds.put(infoObj, need);
}
}
The object is a private inner class (in the same class as that method) that looks like this:
private class InfoObj {
private String title;
private Integer id;
public InfoObj(String title, Integer id) {
this.title = title;
this.id = id;
}
public String getTitle() {
return title;
}
public Integer getId() {
return id;
}
#Override
public boolean equals(Object io2) {
if (this == io2) { return true; }
if ( !(io2 instanceof InfoObj) ) { return false; }
InfoObj temp = (InfoObj) io2;
return this.id.equals(temp.id) && this.title.equals(temp.title);
}
#Override
public int hashCode() {
final int prime = 7;
int result = 1;
result = prime * result
+ ((this.title == null) ? 0 : this.title.hashCode());
result = prime * result
+ ((this.id == null) ? 0 : this.id.hashCode());
return result;
}
However, despite overriding the equals and hashCode methods, the hashMap will still contain repeat keys (as in title and id are equivalent...but still show up in multiple places). I think I'm doing everything correctly, but realize I could be missing something...
Also, I know there are repeat keys because I loop through the keySet and output the results, which results in objects with the same title and id showing up multiple times.

Per your comment, a HashMap cannot contain the same keys per the implementation the same key would be:
(e.hash == hash && ((k = e.key) == key || key.equals(k)))
And since you are following the contract for equals and hashcode, any object you create here:
InfoObj infoObj = new InfoObj(obj.getTitle(), obj.getId());
With the same id and title, will be considered the same key, and if the map previously contained a mapping for the key, the old value is replaced.

It looks like everything is OK here.

Related

Can we use java stream in the below scenario?

I have a class say Student that have a field marks of type Double. I am creating a list of Student objects. Each student object may have marks field set to null or set with same value in different student objects. I have a problem where I want to return a single student object from this list based on below conditions:
when all students have same marks then return null.
else return student that have highest marks.
I wonder if there is a better approach using java stream api to get it done. Thank you in advance.
Student class:
public class Student {
private Double marks;
public Double getMarks() {
return marks;
}
public void setMarks(Double marks) {
this.marks = marks;
}
#Override
public String toString() {
return "Student [marks=" + marks + "]";
}
public Student(Double marks) {
super();
this.marks = marks;
}
}
Using Streams, you can do it while collecting to TreeMap and verifying the lastEntry as in:
private Student highestMarkUniqueStudent(List<Student> studentList) {
if(studentList.size() == 0) return null;
if(studentList.size() == 1) return studentList.get(0);
TreeMap<Integer, List<Student>> map = new TreeMap<>(studentList.stream()
.collect(Collectors.groupingBy(Student::getMarks)));
List<Student> highestMarkStudents = map.lastEntry().getValue();
// only one highest or all same marks or more than one with highest mark
return highestMarkStudents.size() == 1 ? highestMarkStudents.get(0) : null;
}
You can do like this:
Indeed by using TreeMap you have the highest marks in the first entry. so by checking its value, you can return desire result. if all marks have the same value so the first entry has to value more than one and if you have two or more highest marks then the first entry has still more than one student object in the list.
TreeMap<Integer, List<Student>> map = list.stream()
.collect(Collectors.groupingBy(
Student::getMarks,
() -> new TreeMap<Integer, List<Student>>(Comparator.reverseOrder()),
Collectors.mapping(Function.identity(), Collectors.toList())));
Map.Entry<Integer, List<Student>> firstEntry = map.firstEntry();
if (firstEntry.getValue().size() <= 1) {
result = firstEntry.getValue().get(0);
}
You can work with streams, but you'd need a helper object which would be able to collect your data.
The work now either can be done with redution or with collection.
With reduction, you'd do
students.stream().reduce(
new Helper(),
(helper, student) -> new Helper(helper, student));
class Helper {
private Student bestStudent = null;
private boolean different = false;
public Helper() {
}
public Helper(Helper oldHelper, Student newStudent) {
if (oldHelper.bestStudent == null) {
bestStudent = newStudent;
} else if (student.getMark() > oldHelper.bestStudent.getMark()) {
different = true;
bestStudent = student;
} else if (student.getMark() < oldHelper.bestStudent.getMark()) {
different = true;
}
}
public Student getResult() {
return different ? bestStudent : null;
}
}
but that creates a new Helper object for every Student.
With collection, we'd do
students.stream().collect(Helper::new, Helper::accept, Helper::combine);
class Helper {
private Student bestStudent = null;
private boolean different = false;
public Helper() {
}
public void accept(Student newStudent) {
if (bestStudent == null) {
bestStudent = newStudent;
} else if (newStudent.getMark() > bestStudent.getMark()) {
different = true;
bestStudent = newStudent;
} else if (newStudent.getMark() < bestStudent.getMark()) {
different = true;
}
}
public void combine() (Helper other) {
if (bestStudent == null) {
bestStudent = other.bestStudent;
different = other.different;
} else if (other.bestStudent != null && other.bestStudent.getMark() > bestStudent.getMark()) {
different = true;
bestStudent = other.bestStudent;
} else if (other.bestStudent != null && other.bestStudent.getMark() < bestStudent.getMark()) {
different = true;
}
}
public Student getResult() {
return different ? bestStudent : null;
}
}
(Note: 1. Code not tested, 2. parts of the basic logic taken from another answer.)
You might not need streams. Sort the list highest mark firsts, if the first 2 are same, return null, otherwise return the first student.
students.sort(Comparator.comparing(Student::getMarks).reversed());
boolean firstTwoHaveSameMarks = students.get(0).getMarks().equals(students.get(1).getMarks());
return firstTwoHaveSameMarks ? null : students.get(0);
If 2 or more students have the same highest mark, it returns null, otherwise it returns the student.

HashMap<Node, Integer> get returns null even when hashcode() is the same

I have a custom class Node:
public static class Node{
int _id;
// adjacent node and its cost
HashMap<Node, Integer> _adjList = new HashMap<>();
public Node(int id){
_id = id;
}
public boolean equals(Node n) {
if (n == null)
return false;
return _id == n._id;
}
public int hashCode(){
return _id;
}
public String toString(){
return "("+_id+")";
}
}//Node
I use it as a key to the HashMap<Node, Integer> costs, where for a given node, there is a cost of getting to the node associated with it.
Later in the program, I have costs filled with values:
{(1)=0, (2)=24, (3)=3, (4)=15}
Then, later in the code, when I query costs like this:
for (int i = 0; i <= g.nNodes; ++i){
Node tempNode = new Node(i);
Integer cost = currentPathCosts.get(tempNode);
System.out.println("cost:"+cost);
}
I get
null
null
null
as the output.
What is wrong? The hashcode() of the Nodes with the same _id should be the same..Am I missing something?
UPDATE:
I was missing Node other = (Node)n; in the equals() method.
the way to override hashcode() and equals() that worked for me:
#Override
public boolean equals(Object n) {
if (n == null)
return false;
Node other = (Node)n;
return _id == other._id;
}
#Override
public int hashCode(){
return _id;
}
Add the #Override annotation on your equals() method, as you should always do when you want to override a method, and the compiler will tell you what the problem is. (Do the same for hashCode, while you're at it).
You're not overriding Object.equals(). You're defining a different, overloaded equals() method.

Is the below is correct equals and hashCode implementation in Java?

When an object's value is equal i need to return it is true.
example
#Override
public int hashCode() {
return new HashCodeBuilder().append(value).toHashCode();
}
#Override
public boolean equals(final Object obj) {
if (obj instanceof NumberValTO) {
final NumberValTO other = (NumberVal) obj;
return new EqualsBuilder().append(value, other.getValue()).isEquals();
}
return false;
}
Is the above is fine or wrong?
I saw in few applications where hashcode is being multiple with each and every field of the table and not sure whether it is a correct approach.
Assume an entity has 4 columns
Assume an entity has 2 columns
Which is the best approach to generate the same?
Also, do we need to implement hashcode() and equals() for a hibernate entity class?
Thanks.
Yes it's fine, assuming those are the Apache Commons helper classes you're using. #Vino is correct to point out that adding if (obj == this) return true; to the start of your equals method can be a worthwhile optimisation, but your methods look correct as is.
Your example is fine as it provides a fair implementation of equals and hashcode methods. I am using this way in my project.
To answer you 1 question You can read through http://www.ideyatech.com/2011/04/effective-java-equals-and-hashcode/
To answer you 2 question follow link : Hibernate: When is it necessary to implement equals() and hashCode(), and if so, how?
Your equals and hashCode methods are both using the EqualsBuilder and HashCodeBuilder from Apache's commons-lang correctly, though you should add a reference check - if (obj == this) return true - to the equals method.
An argument I've recently been given against using EqualsBuilder and HashCodeBuilder was that it was less performant, so I tested it.
I created a HashMap, added 10K entries and then compared the look-up times for the same key object, once using a traditional equals and hashCode, then again with the EqualsBuilder and HashCodeBuilder. The idea is that getting the values by their key will hammer the both the equals and hashCode methods and give a good comparison of their performance.
While the EqualsBuilder and HashCodeBuilder implementations where slower, the difference was in the region of 60ns with an average look-up time of about 320ns for the commons-lang implementation and 260ns for the traditional approach (I've shown the code I used below).
IMHO this performance penalty should only be a concern where the equals and hashCode are called repeatedly over a large set of objects and even then, only where the small performance gain is worth sacrificing that clarity of you code.
Anyway, here's the class I used to test the performance difference:
public class Example
{
private Type operationType;
private long identity;
private String name;
private BigDecimal value;
public Example(Type operationType, long identity, String name, BigDecimal value)
{
this.operationType = operationType;
this.identity = identity;
this.name = name;
this.value = value;
}
public Example(Example example)
{
this.operationType = example.operationType;
this.identity = example.identity;
this.name = example.name;
this.value = example.value;
}
public long getIdentity()
{
return identity;
}
public String getName()
{
return name;
}
public BigDecimal getValue()
{
return value;
}
#Override
public boolean equals(Object obj)
{
if (Type.TRADITIONAL.equals(operationType))
{
if (this == obj)
{
return true;
}
if (obj == null || getClass() != obj.getClass())
{
return false;
}
Example example = (Example)obj;
return getIdentity() == example.getIdentity()
&& ((getName() == null && example.getName() == null) || getName().equals(example.getName ()))
&& ((getValue() == null && example.getValue() == null) || getValue().equals(example.getValue()));
}
else
{
return this == obj || obj instanceof Example &&
new EqualsBuilder()
.append(getIdentity(), ((Example)obj).getIdentity())
.append(getName(), ((Example)obj).getName())
.append(getValue(), ((Example)obj).getValue())
.isEquals();
}
}
#Override
public int hashCode()
{
if (Type.TRADITIONAL.equals(operationType))
{
int result = (int)(getIdentity() ^ (getIdentity() >>> 32));
result = 31 * result + (getName() != null ? getName().hashCode() : 0);
result = 31 * result + (getValue() != null ? getValue().hashCode() : 0);
return result;
}
else
{
return new HashCodeBuilder().append(getIdentity()).append(getName()).append(getValue()).toHashCode();
}
}
public static enum Type
{
TRADITIONAL,
COMMONS
}
}
And here's the test:
public class ExampleTest
{
#Test
public void testMapLookupWithTraditional() throws Exception
{
double total = 0;
for (int i = 0; i < 10; i++)
{
total += testMapLookup(Example.Type.TRADITIONAL);
}
System.out.println("Overall Average: " + (total / 10));
}
#Test
public void testMapLookupWithCommons() throws Exception
{
double total = 0;
for (int i = 0; i < 10; i++)
{
total += testMapLookup(Example.Type.COMMONS);
}
System.out.println("Overall Average: " + (total / 10));
}
private double testMapLookup(Example.Type operationType) throws Exception
{
Map<Example, String> examples = new HashMap<Example, String>();
while (examples.size() < 10000)
{
long now = System.currentTimeMillis();
Example example = new Example(
operationType,
now,
"EXAMPLE_" + now,
new BigDecimal(now)
);
examples.put(example, example.getName());
Thread.sleep(1);
}
int count = 0;
double average = 0;
double max = 0;
double min = Double.MAX_VALUE;
for (Example example : examples.keySet())
{
Example copiedExample = new Example(example);
long start = System.nanoTime();
examples.get(copiedExample);
long duration = System.nanoTime() - start;
average = ((average * count++) + duration) / count;
if (max < duration) max = duration;
if (min > duration) min = duration;
}
System.out.println("Average: " + average);
System.out.println("Max: " + max);
System.out.println("Min: " + min);
return average;
}
}

Edit element in arraylist, find index?

I'm about to create two methods for creating and changing customer profiles. Creating profile is no problem. Everything seems to go well there. But, when I shall then go in and change the profile, I get it not to work.
The indexOf() gives me -1, even though the value I search for available :S
Anyone have a good solution to this?
The problem is in the editProfile-method!
public class Profile{
String name;
long id;
int accNr = 1000;
double balance;
}
ArrayList<Profile> profileList = new ArrayList<Profile>();
public boolean newProfile(long id, String name, int amount){
Profile newProfile = new Profile();
Profile accNr = new Profile();
int ACC = accNr.accNr++;
newProfile.accNr = ACC;
newProfile.id = id;
newProfile.name = name;
newProfile.balance = amount;
profileList.add(newProfile);
return true;
}
public void editProfile(long id, String newName){
int ID = (int)id;
System.out.print(ID);
int index = profileList.indexOf(id);
System.out.print(index);
profileList.get(index);
}
The indexOf method will use the equals method to determine if your Profile exists in the list. You must override the equals method in Profile to return the proper result.
Second, it won't find your Profile, because you are passing a long to indexOf, and neither a long nor a Long will be found in the list. If you must retrieve the Profile by a long, then it makes more sense to have a Map<Long, Profile> instead of an ArrayList<Profile>. Then you can call get(id) to retrieve the Profile. Usually, you should override the hashCode method if you override equals, but because a Profile isn't being used as the key here, it's not necessary.
profileList contains Profile instances and you are trying to get the index of a long.
One solution would be overriding equals method in Profile class.
#Override
public boolean equals(Object obj) {
...
}
Another solution (not very recommended) would be looping over elements of profileList and manually checking for matches, like:
for (Profile element : profileList)
if (element.getID() == id)
...
Probably your Profileneeds to override equals and hashCode methods. Eclipse can generate then, Would be like taking your example:
public class Profile {
String name;
long id;
int accNr = 1000;
double balance;
#Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + accNr;
long temp;
temp = Double.doubleToLongBits(balance);
result = prime * result + (int) (temp ^ (temp >>> 32));
result = prime * result + (int) (id ^ (id >>> 32));
result = prime * result + ((name == null) ? 0 : name.hashCode());
return result;
}
#Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Profile other = (Profile) obj;
if (accNr != other.accNr)
return false;
if (Double.doubleToLongBits(balance) != Double
.doubleToLongBits(other.balance))
return false;
if (id != other.id)
return false;
if (name == null) {
if (other.name != null)
return false;
} else if (!name.equals(other.name))
return false;
return true;
}
}

HashMap in java cannot hash MyObject

I have defined a simple private class named SetOb which contains an int and a Set data structure. I have a HashMap in the 'main' method with SetOb as Key and Integer as value. Now as you can see in the main method, when I feed the HashMap with a SetOb instance and then look for an instance with exactly the same value, it returns 'null'. This has happened with me quite a few times before when I use my own defined data structures like SetOb as Key in HashMap. Can someone please point me what am I missing ?
Please note that in the constructor of SetOb class, I copy the Set passed as argument.
public class Solution {
public static Solution sample = new Solution();
private class SetOb {
public int last;
public Set<Integer> st;
public SetOb(int l , Set<Integer> si ){
last = l;
st = new HashSet<Integer>(si);
}
}
public static void main(String[] args) {
Map<SetOb, Integer> m = new HashMap< SetOb, Integer>();
Set<Integer> a = new HashSet<Integer>();
for(int i =0; i<10; i++){
a.add(i);
}
SetOb x = sample.new SetOb(100, a);
SetOb y = sample.new SetOb(100, a);
m.put(x,500);
Integer val = m.get(y);
if(val!= null) System.out.println("Success: " + val);
else System.out.println("Failure");
}
}
Your x and y are not the same object instances hence contains is not able to match y against x, which ends up not finding the matching key/value in the Map.
If you want the match to succeed, please implement(override) hasCode & equals method in SetOb which will compare the field values.
Sample methods(Eclipse generated) as below:
#Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + last;
result = prime * result + ((st == null) ? 0 : st.hashCode());
return result;
}
#Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
SetOb other = (SetOb) obj;
if (last != other.last)
return false;
if (st == null) {
if (other.st != null)
return false;
} else if (!st.equals(other.st))
return false;
return true;
}
The default implementation of hashCode uses object identity to determine the hash code. You will need to implement hashCode (and equals) in your private class if you want value identity. For instance:
private class SetOb {
public int last;
public Set<Integer> st;
public SetOb(int l , Set<Integer> si ){
last = l;
st = new HashSet<Integer>(si);
}
#Override
public boolean equals(Object other) {
if (other.class == SetOb.class) {
SetOb otherSetOb = (SetOb) other;
return otherSetOb.last == last && otherSetOb.st.equals(st);
}
return false;
}
#Override
public int hashCode() {
return 37 * last + st.hashCode();
}
}
SetOb needs to override the hashCode() and thus the equals() methods.
Hash-based collections use these methods to store (hashCode()) and retrieve (hashCode()) and equals()) your objects.

Categories

Resources