Comparing two objects using an equals method, Java - java

I have an array of objects that I want to compare to a target object. I want to return the number of objects that exactly match the target object.
Here is my count method:
public int countMatchingGhosts(Ghost target) {
int count=0;
for (int i=0;i<ghosts.length;i++){
if (ghosts[i].equals(target));
count++;
}
return count;
And here is my equals method:
public boolean equals(Ghost other){
if(this == other) return true;
if( !(other instanceof Ghost) ) return false;
Ghost p = (Ghost)other;
if (this.x == p.x && this.y == p.y && this.direction==p.direction && this.color.equals(p.color))
return true;
else
return false;
I run some test code, and I expect 1 matching only, but I get 3 instead. Do you see any errors?

There is a ; at the end of your if:
if (ghosts[i].equals(target));
^
This makes count++; happen always irrespective of what your equals method returns.

You should override this function:
public boolean equals(Object other) { }
Do note the Object class being used in method's signature instead of Ghost. Your can use #Override annotation to get a compiler error if you are not using method signature correctly.
#Override
public boolean equals(Object other) { }
Having said that, what's probably happening in your code is what the other answer is stating...

Just thought I add that while implementing the equals method in your code, you must also implement (override) the hashCode method. This is a general contract that you must follow for the best performances.
Below is an excerpt from Joshua Bloch's book "Effective Java"
Item 9: Always override hashCode when you override equals
A common source of bugs is the failure to override the hashCode method. You
must override hashCode in every class that overrides equals. Failure to do so
will result in a violation of the general contract for Object.hashCode, which will
prevent your class from functioning properly in conjunction with all hash-based
collections, including HashMap,HashSet, and Hashtable.
Here is the contract, copied from the Object specification [JavaSE6]:
• Whenever it is invoked on the same object more than once during an execution
of an application, the hashCode method must consistently return the
same integer, provided no information used in equals comparisons on the
object is modified. This integer need not remain consistent from one execution
of an application to another execution of the same application.
• If two objects are equal according to the equals(Object) method, then calling
the hashCode method on each of the two objects must produce the same
integer result.
And just like Pablo said, if you use anything other than the Object class in your equals method signature, you aren't actually overriding the equals method, and your program won't work as expected.
Take for example this small program that copies a List to a Set(which cannot contain duplicates) and prints the new Collection. Try swapping equals(Object obj) with equals(Item obj) and see what happens when you run the program. Also, comment out the hashCode() method and run the program and observe the difference between using it and not.
public class Item {
private String name;
private double price;
private String countryOfProduction;
public Item(String name, double price, String countryOfProduction) {
this.setName(name);
this.setPrice(price);
this.setCountryOfProduction(countryOfProduction);
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public double getPrice() {
return price;
}
public void setPrice(double price) {
this.price = price;
}
public String getCountryOfProduction() {
return countryOfProduction;
}
public void setCountryOfProduction(String countryOfProduction) {
this.countryOfProduction = countryOfProduction;
}
public String toString() {
return "Item Name: " + getName() + "\n" +
"Item Price: N" + getPrice() + "\n" +
"Country of Production: " + getCountryOfProduction() + "\n";
}
#Override
public boolean equals(Object obj) {
if(!(obj instanceof Item)) {
return false;
}
if(obj == this) {
return true;
}
Item other = (Item)obj;
if(this.getName().equals(other.getName())
&& this.getPrice() == other.getPrice()
&& this.getCountryOfProduction().equals(other.countryOfProduction)) {
return true;
} else {
return false;
}
}
public int hashCode() {
int hash = 3;
hash = 7 * hash + this.getName().hashCode();
hash = 7 * hash + this.getCountryOfProduction().hashCode();
hash = 7 * hash + Double.valueOf(this.getPrice()).hashCode();
return hash;
}
public static void main (String[]args) {
List<Item> items = new ArrayList<>();
items.add(new Item("Baseball bat", 45, "United States"));
items.add(new Item("BLUESEAL Vaseline", 1500, "South Africa"));
items.add(new Item("BLUESEAL Vaseline", 1500, "South Africa"));
Collection<Item> noDups = new HashSet<>(items);
noDups.stream()
.forEach(System.out::println);
}
}

Related

Comparing two objects names in an equals method

I'm a little lost on how to correctly compare objects that I'm testing. My issue is that the tests themselves always come out as true due to the code, but any other way I think of doesn't work correctly either.
public class Element {
private String atomLetter;
private String name;
public Element(String atomLetter, String name) {
this.atomLetter = atomLetter.toUpperCase();
this.name = name.toLowerCase();
}
public Element(String atomLetter) {
this(atomLetter, "");
}
public String getAtomLetter() {
return atomLetter;
}
public String getName() {
return name;
}
// TODO: two elements are considered to be equal if they have the same atom letter.
#Override
public boolean equals(final Object obj) {
if (atomLetter == this.atomLetter){
return true;
}
return false;
}
#Override
public String toString() {
return "Element{" +
"'" + atomLetter + "'" +
", name='" + name + '\'' +
'}';
}
}
In this case, the outcome comes out exactly the same, but the issue is the equals method.
#Test
public void testSimpleMolecules() {
// simple carbon
Molecule m1 = new Molecule("");
assertTrue(m1.isEmpty());
assertEquals(0, m1.size());
m1.add(new Element("C"));
assertFalse(m1.isEmpty());
assertEquals(1, m1.size());
assertEquals(new Element("C"), m1.get(0));
// simple hydrogen
Molecule m2 = new Molecule("");
m2.add(new Element("H"));
assertFalse(m2.isEmpty());
assertEquals(1, m2.size());
assertEquals(new Element("H"), m2.get(0));
// simple nitrogen
Molecule m3 = new Molecule("");
m3.add(new Element("N"));
assertFalse(m3.isEmpty());
assertEquals(1, m3.size());
assertEquals(new Element("N"), m3.get(0));
// simple oxygen
Molecule m4 = new Molecule("");
m4.add(new Element("O"));
assertFalse(m4.isEmpty());
assertEquals(1, m4.size());
assertEquals(new Element("O"), m4.get(0));
}
In your equals method, you are comparing this object's atomLetter to itself.
if (atomLetter == this.atomLetter){
Instead, you need to cast the obj argument to the Element class and compare its atomLetter to this.atomLetter
Element other = (Element) obj;
return this.atomLettet == other.atomLettet;
Of course, you'll likely want to test that the cast is possible before actually doing it, and say that the objects are not equal if they are of different classes. Also test for null. The javadoc for object.Equals() explains all if the requirements for a proper equals method.
The Answer by DarkSigma is correct about your comparison of this.atomLetter to itself. You have a few other issues with your equals.
Compare content, not references
Your code … == this.atomLetter is comparing object references (pointers) rather than the textual content of those String objects. In other words, you are asking if the two variables both refer to the same object, that is, the same chunk of memory.
Always compare String content by calling String::equals or String::equalsIgnoreCase.
For implementing equals, you can test for references being the same, as a quick first part of the equality testing. But this alone is not enough.
if ( this == other ) return true;
Test for null
You should test for null. If the other object reference is null, there is no object, so there can be no equality.
if ( other == null ) return false;
Test for class
You can also make sure the class of the two objects match.
if ( other == null || getClass() != other.getClass() ) return false;
Cast
As the other Answer mentioned, you should cast the passed Object, having gotten past the class-matching test shown above.
Element element = ( Element ) other;
Check for matching content
As the last test, check for matching content.
In this particular case, I suspect you do care about case matching. So we call String::equals rather than String::equalsIgnoreCase.
return getAtomLetter().equals( element.getAtomLetter() );
Example equals method
Let's pull that all together into a single equals implementation.
#Override
public boolean equals ( Object other )
{
if ( this == other ) return true;
if ( other == null || getClass() != other.getClass() ) return false;
Element element = ( Element ) other;
return getAtomLetter().equals( element.getAtomLetter() );
}
Tip: Your IDE will generate this code for you. No need to write this yourself. For example, in IntelliJ, choose: Code > Generate > equals() and hashCode.
Always implement hashCode when implementing equals
As discussed many times on Stack Overflow, such as here, when writing an equals method, always write a hashCode method using the same logic.
#Override
public int hashCode ( )
{
return Objects.hash( getAtomLetter() );
}
Example class
So we end up with a Element class that looks like this.
package work.basil.example;
import java.util.Objects;
public class Element
{
// Member fields
private String atomLetter, name;
// Constructor
public Element ( String atomLetter , String name )
{
this.atomLetter = Objects.requireNonNull( atomLetter ).toUpperCase();
if ( this.atomLetter.isBlank() ) { throw new IllegalArgumentException();}
this.name = Objects.requireNonNull( name ).toLowerCase();
}
// Getters (read-only).
public String getAtomLetter ( ) {return atomLetter;}
public String getName ( ) {return name;}
// `Object` overrides
#Override
public boolean equals ( Object other )
{
if ( this == other ) return true;
if ( other == null || getClass() != other.getClass() ) return false;
Element element = ( Element ) other;
return getAtomLetter().equals( element.getAtomLetter() );
}
#Override
public int hashCode ( )
{
return Objects.hash( getAtomLetter() );
}
#Override
public String toString ( )
{
return "Element{ " +
"atomLetter='" + atomLetter + '\'' +
" | name='" + name + '\'' +
" }";
}
}
You can implement the equals method this way. However, you also have to implement hashCode for correctness.
For equals
#Override
public boolean equals(Object obj)
{
// check the instance of obj
if (!(obj instanceof Element)) return false;
// check if obj is itself
if (obj == this) return true;
// cast obj as Element
Element e = (Element) obj;
// compare fields
return this.atomLetter.equals(e.atomLetter) &&
this.name.equals(e.name);
}
For hashcode, you can implement it a number of ways, but usually this is the quickest and easiest way.
#Override
public int hashCode()
{
return Objects.hash(atomLetter, name);
}

HashMap ignoring overridden hashCode and equals methods

I am loading data on network traffic from a file. The information I'm loading is attacker IP address, victim IP address, and date. I've combined these data into a Traffic object, for which I've defined the hashCode and equals functions. Despite this, the HashMap I'm loading them into treats identical Traffic objects as different keys. The entire Traffic object complete with some simple test code in the main method follows:
import java.util.HashMap;
public class Traffic {
public String attacker;
public String victim;
public int date;
//constructors, getters and setters
#Override
public int hashCode() {
long attackerHash = 1;
for (char c:attacker.toCharArray()) {
attackerHash = attackerHash * Character.getNumericValue(c) + 17;
}
long victimHash = 1;
for (char c:victim.toCharArray()) {
victimHash = victimHash * Character.getNumericValue(c) + 17;
}
int IPHash = (int)(attackerHash*victimHash % Integer.MAX_VALUE);
return (IPHash + 7)*(date + 37) + 17;
}
public boolean equals(Traffic t) {
return this.attacker.equals(t.getAttacker()) && this.victim.equals(t.getVictim()) && this.date == t.getDate();
}
public static void main(String[] args) {
Traffic a = new Traffic("209.167.099.071", "172.016.112.100", 7);
Traffic b = new Traffic("209.167.099.071", "172.016.112.100", 7);
System.out.println(a.hashCode());
System.out.println(b.hashCode());
HashMap<Traffic, Integer> h = new HashMap<Traffic, Integer>();
h.put(a, new Integer(1));
h.put(b, new Integer(2));
System.out.println(h);
}
}
I can't speak to the strength of my hash method, but the outputs of the first two prints are identical, meaning it at least holds for this case.
Since a and b are identical in data (and therefore equals returns true), and the hashes are identical, the HashMap should recognize them as the same and update the value from 1 to 2 instead of creating a second entry with value 2. Unfortunately, it does not recognize them as the same and the output of the final print is the following:
{packagename.Traffic#1c051=1, packagename.Traffic#1c051=2}
My best guess at this is that HashMap's internal workings are ignoring my custom hashCode and equals methods, but if that's the case then why? And if that guess is wrong then what is happening here?
The problem here is your equals method, which does not override Object#equals. To prove this, the following will not compile with the #Override annotation:
#Override
public boolean equals(Traffic t) {
return this.attacker.equals(t.getAttacker()) &&
this.victim.equals(t.getVictim()) &&
this.date == t.getDate();
}
The implementation of HashMap uses Object#equals and not your custom implementation. Your equals method should accept an Object as a parameter instead:
#Override
public boolean equals(Object o) {
if (!(o instanceof Traffic)) {
return false;
}
Traffic t = (Traffic) o;
return Objects.equals(attacker, t.attacker) &&
Objects.equals(victim, t.victim) &&
date == t.date;
}

How do the compareTo and equals methods work in this example?

I do not understand the compareTo() method and equals() method. How do they work in the code below? Please describe it for me, and look at my comments under the methods.
I do not understand how it prints out the patients ordered according to the prio for each patient. Help would be appreciated.
import java.util.PriorityQueue;
public class Patient implements Comparable<Patient> {
private String firstname;
private String lastname;
private String personNbr;
private int prio;
private int number;
private static int total = 0;
public Patient(String firstname, String lastname, String personNbr, int prio) {
this.firstname = firstname;
this.lastname = lastname;
this.personNbr = personNbr;
this.prio = prio;
total++;
number = total;
}
public int compareTo(Patient rhs) {
if(prio==rhs.prio) {
return number - rhs.number; // what happens here?
} else {
return prio - rhs.prio; // what happens here?
}
}
public boolean equals(Object rhs) {
if (rhs instanceof Patient) { // what happens here?
return compareTo((Patient) rhs) == 0; // what happens here?
} else {
return false;
}
}
public String toString() {
return this.firstname + this.lastname + this.personNbr + this.prio;
}
public static void main(String[] args) {
PriorityQueue<Patient> pq = new PriorityQueue<Patient>();
pq.offer(new Patient("Kalle", "Karlsson", "8503622-1213", 3));
pq.offer(new Patient("Lisa", "Svensson", "840312-1224", 7));
pq.offer(new Patient("Lena", "Nilsson", "820323-1224", 9));
pq.offer(new Patient("Kallee", "Karlssonn", "85503622-1213", 3));
System.out.println(pq.toString());
}
}
the output is
[KalleKarlsson8503622-12133, KalleeKarlssonn85503622-12133, LenaNilsson820323-12249, LisaSvensson840312-12247]
equals() method is used to compare the two instances of the class for equality. It comes from Object class of java. All the class in the java have equals() method available for comparison. You can always provide custom implementation according to the class member and equality definition.
The compareTo() is used primary by Collections framework for sorting the array or ordering the elements in a priority queue. compareTo() comes from Comparable interface.
public int compareTo(Patient rhs) {
if(prio==rhs.prio) {
return number - rhs.number; // First line
} else {
return prio - rhs.prio; // Second line
}
}
The compareTo() method is defined on the basis of number and prio attributes of the class. The compareTo() return negative, zero or positive when current instance is smaller then passed, equal or grater respectively.
public boolean equals(Object rhs) {
if (rhs instanceof Patient) { // First Line
return compareTo((Patient) rhs) == 0; // Second Line
} else {
return false;
}
}
In the first line, you are checking that argument passed to method is instance of Patient class. Because you are checking the equality of two instance which should be of same type.
Second line, You are using the compareTo() method to define the equality of the instances.
Update in the question:
Here is the toString() method.
#Override
public String toString() {
return
"firstname='" + firstname +
" lastname='" + lastname +
" personNbr='" + personNbr +
" prio=" + prio +
" number=" + number;
}
You need to poll() instead of just printing them. poll() retrieves and removes the head of this queue. That will represent the true ordering of the elements in the queue, not the toString().
while (!pq.isEmpty()) {
System.out.println(pq.poll());
}
output:
firstname='Kalle lastname='Karlsson personNbr='8503622-1213 prio=3 number=1
firstname='Kallee lastname='Karlssonn personNbr='85503622-1213 prio=3 number=4
firstname='Lisa lastname='Svensson personNbr='840312-1224 prio=7 number=2
firstname='Lena lastname='Nilsson personNbr='820323-1224 prio=9 number=3
Analysis:
Kalle and Kallee have the highest priority (least value), but the number for Kalle is lower than Kallee. Others are organized by the prio.
The conmpareTo method is defining an ordering such that the prio field is sorted first, then the number field. If the prio fields are equal then the difference is derived from the number field. If prio is not equal then it is the prio field that is compared.
The equals method states that two objects are equal if compareTo returns zero - which makes sense if you think about it.

problems with hashmaps get function [duplicate]

This question already has answers here:
Why do I need to override the equals and hashCode methods in Java?
(31 answers)
Closed 9 years ago.
I have a hashmap which key is an object of my inner class "Key".
My problem is that when I use get(key) it never gives anything back. Since get works with equals I have overwritten equals in my Key class, so it should work for the get method, but apparently it does not.
Any suggestions?
CODE:
public class Infrastruktur
{
private Zuechter online;
private HashMap<Key,Zuechter> zuechter;
Infrastruktur()
{
zuechter = new HashMap<Key,Zuechter>();
}
}
public void login(String name, String passwort)
{
Key hashMapKey = new Key(name, passwort);
if(this.zuechter.get(hashMapKey) != null)
this.online = this.zuechter.get(hashMapKey);
}
public void register(String name, String passwort)
{
if(name != null && passwort != null)
{
this.zuechter.put(new Key(name,passwort),new Zuechter());
login(name, passwort);
}
}
public void logOut()
{
this.online = null;
}
public Zuechter getOnline() {
return this.online;
}
private class Key
{
String name;
String passwort;
Key(String name, String passwort)
{
this.name = name;
this.passwort = passwort;
}
#Override
public boolean equals(Object o)
{
if (o == null) return false;
if (o == this) return true;
if (!(o instanceof Key)) return false;
Key key = (Key)o;
if(this.name.equals(key.name) && this.passwort.equals(key.passwort)) return true;
return false;
}
}
/* Testing */
public static void main(String[] args)
{
Infrastruktur inf = new Infrastruktur();
inf.register("Jakob", "passwort");
inf.logOut();
inf.login("Jakob", "passwort");
System.out.println(inf.getOnline().test());
}
}
If I run the class this is the output I get:
not found
not found
Exception in thread "main" java.lang.NullPointerException
at Infrastruktur.main(Infrastruktur.java:105)
You should also implement hashCode() for your Key class. An example implementation could be:
#Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + name.hashCode();
result = prime * result + passwort.hashCode();
return result;
}
Use Eclipse to generate the hashCode method of your class. In any Map scenario, Java hashes the key value to allow 0(1) read access.
It simply hashes to jump to a reference if found. All Java IDEs have a Generate hashCode and equals option. A simple example, with null checks omitted.
#Override
public int hashCode() {
int hash = 3;
hash = 7 * hash + this.name.hashCode();
hash = 7 * hash + this.passwort.hashCode();
return hash;
}
You must override hashCode() in every class that overrides equals(). Failure to do so will result in a violation of the general contract for Object.hashCode(), which will prevent your class from functioning properly in conjunction with all hash-based collections, including HashMap, HashSet, and Hashtable.
from Effective Java, by Joshua Bloch
tl;dr either generate hashCode() manually,
#Override
public int hashCode() {
int hash = 31;
hash = 29 * hash + Objects.hashCode(name);
hash = 29 * hash + Objects.hashCode(passwort);
return hash;
}
use IDE hashCode generation, or just use generic (albeit slower)
#Override
public int hashCode() {
return Objects.hash( name, passwort );
}
... you can even write a generic hashCode() for any class using reflection (very slow, but good as placeholder)
btw, omitting null checks in hashCode() for mutable or immutable objects with null as a valid field value is one of the easiest ways to introduce bugs into code - that's exactly why either explicit check or Objects.hashCode() is needed.

removeAll from Interface Set

I want to compare database dump to xml and *.sql. In debagge toRemove and toAdd only differ in dimension. toRemove has size 3, toAdd has size 4. But after running the code, removeAll, toRemove has size 3 and toAdd has size 4. What's wrong?
final DBHashSet fromdb = new DBHashSet(strURL, strUser, strPassword);
final DBHashSet fromxml = new DBHashSet(namefile);
Set<DBRecord> toRemove = new HashSet<DBRecord>(fromdb);
toRemove.removeAll(fromxml);
Set<DBRecord> toAdd = new HashSet<DBRecord>(fromxml);
toAdd.removeAll(fromdb);
Update:
public class DBRecord {
public String depcode;
public String depjob;
public String description;
public DBRecord(String newdepcode, String newdepjobe, String newdesc) {
this.depcode = newdepcode;
this.depjob = newdepjobe;
this.description = newdesc;
}
public String getKey() {
return depcode + depjob;
}
public boolean IsEqualsKey(DBRecord rec) {
return (this.getKey().equals(rec.getKey()));
}
public boolean equals(Object o) {
if (o == this)
return true;
if (o == null)
return false;
if (!(getClass() == o.getClass()))
return false;
else {
DBRecord rec = (DBRecord) o;
if ((rec.depcode.equals(this.depcode)) && (rec.depjob.equals(this.depjob)))
return true;
else
return false;
}
}
}
In order to properly use HashSet (and HashMap, for that matter), you must implement a hashCode() as per the following contract:
Whenever it is invoked on the same object more than once during an execution of a Java application, the hashCode method must consistently return the same integer, provided no information used in equals comparisons on the object is modified. This integer need not remain consistent from one execution of an application to another execution of the same application.
If two objects are equal according to the equals(Object) method, then calling the hashCode method on each of the two objects must produce the same integer result.
It is not required that if two objects are unequal according to the equals(java.lang.Object) method, then calling the hashCode method on each of the two objects must produce distinct integer results. However, the programmer should be aware that producing distinct integer results for unequal objects may improve the performance of hash tables.
The code you've supplied for DBRecord does not overide it, hence the problem.
You'd probably want to override it in the following way, or something similar:
#Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + depcode.hashCode();
result = prime * result + depjob.hashCode());
return result;
}

Categories

Resources