Use of a Comparable with indexOf - java

I tried the following simple test.
class ZiggyTest{
public static void main(String[] args){
List<Cities> cities3 = new ArrayList<Cities>();
Cities city = new Cities("London");
cities3.add(new Cities("Manchester"));
cities3.add(new Cities("Glasgow"));
cities3.add(new Cities("Leeds"));
cities3.add(city);
cities3.add(new Cities("Leicester"));
cities3.add(new Cities("Croydon"));
cities3.add(new Cities("Watford"));
System.out.println("IndexOf(new Croydon) "
+ cities3.indexOf(new Cities("Croydon")));
System.out.println("IndexOf(city) "
+ cities3.indexOf(city));
}
}
class Cities implements Comparable<Cities>{
String title;
Cities(String aTitle){
this.title = aTitle;
}
public String getTitle(){
return title;
}
public String toString(){
return "Title : " + title;
}
public int compareTo(Cities c){
return title.compareTo(c.getTitle());
}
}
The output of the above test is
IndexOf(new Croydon) -1
IndexOf(city) 3
I understand why the second line is producing 3 but I don't understand why the first line is not finding the new one with title="Croydon".
The API describes the indexOf method as one that
Returns the index of the first occurrence of the specified element in this list, or -1 if this list does not contain the element. More formally, returns the lowest index i such that (o==null ? get(i)==null : o.equals(get(i))), or -1 if there is no such index.
I think the API is saying that if the object is null then it will return the index of the first occurrence of a null object from the list. If it is not null it will return the index of the first occurrence where the passed in object equals method returns true.
Shouldn't the the object created as cities3.indexOf(new Cities("Croydon") be equal to the object added previously as cities3.add(new Cities("Croydon"));?

Redefine equals instead of compareTo if you want perform search operations.
By default indexOf compares using equals operation, and in your case equals not redefined, that means you have comparison by references. When you create new City object with the same name you have another reference to object, so equals return false.
P.S. If you are using object that describe city, preferable name for class City not Cities

In Swift 5.3, if your object inherits from NSObject, you'll need to do this:
The method name is "isEqual"
public override func isEqual(_ object: Any?) -> Bool {
if let myObj = object as? MyObj {
return myObj.someProperty == self.someProperty
}
return false
}

Related

Why is .contains returning false when I add a duplicate? [closed]

Closed. This question needs to be more focused. It is not currently accepting answers.
Want to improve this question? Update the question so it focuses on one problem only by editing this post.
Closed 2 years ago.
Improve this question
I'm writing a simple method to add an object to an ArrayList if it doesn't already contain it. I'm using the .contains method, but for some reason, when I add a duplicate of an object to the ArrayList, the .contains method returns false, even though I have already added the same object to the ArrayList.
This is my City class:
class City {
private String name, country;
//getters, setters, constructor
}
When I have an ArrayList like this:
List<City> destinations = new ArrayList<>();
with one object added to it
destinations.add(new City("Edmonston", "CA"));
If I check if it contains a duplicate object like this, it returns false
destinations.contains(new City("Edmonston", "CA"))
Here's the full code:
Main method
,
Output
City class
Trip class
Thanks for the help!
See the documentation of List#contains given below:
Returns true if this list contains the specified element. More
formally, returns true if and only if this list contains at least one
element e such that Objects.equals(o, e).
Thus, you need to override equals method in class City. You can do it as follows:
#Override
public boolean equals(Object obj) {
City other = (City) obj;
return Objects.equals(name, other.name) && Objects.equals(country, other.country);
}
In order for contains() to work properly, you have to override the equals() and hashCode() methods in your class. If you don't, the base for equality is that the objects referenced are one and the same.
For example:
City c1 = new City("London", "UK") ;
City c2 = new City("London", "UK") ;
System.out.println(c1.equals(c2)) ; // prints false
c2 = c1 ;
System.out.println(c1.equals(c2)) ; // prints true
EDIT: To override the equals() method, see Arvind Kumar Avinash's answer.
The way the contains method works, according to the documentation, is that it uses Objects.equals(o, e) where o is the object you're checking exists in the ArrayList and e is every element in the array.
Objects.equals(a, b) first checks if either of the arguments are null, and then uses a.equals(b) to check if they're the same.
By default, the equals method in the Object just does a == b, which only returns true if a is the exact same object as b, i.e., a and b both refer to the same location in memory and doesn't care about the variables inside the object.
City c = new City("foo", "bar");
c == new City("foo", "bar") //not true
c == c //true
Because of this default implementation of equals:
City c = new City("foo", "bar");
c.equals(new City("foo", "bar")) //not true
c.equals(c) //true
Therefore, you need to override the equals method in your City class so that you can check whether 2 City objects are equal. Arvind Kumar Avinash's answer provides a way to do this.
However, every collection can implement the contains and similar methods differently. HashSets may use the hash code of objects to compare them. Therefore, it's important to also override the hashCode method for your class.
public int hashCode() {
return Objects.hash(name, country);
}
If you want to insert only one City object in your list, use HashSet and override the equals() and hashCode() methods from Object class in your City class.
public static void main(String[] args) {
Set<City> list = new HashSet<City>();
list.add(new City("Edmonston", "CA"));
list.add(new City("Edmonston", "CA"));
list.add(new City("Edmonston", "CA"));
System.out.println(list); //Edmonston in CA
}
And your City class:
class City {
private String name, country;
public City(String s1, String s2) {
this.name = s1;
this.country = s2;
}
#Override
public boolean equals(Object obj) {
if (obj instanceof City)
return this.name == ((City) obj).name & this.country == ((City) obj).country;
else
return false;
}
#Override
public int hashCode() {
return this.country.hashCode() + this.name.hashCode();
}
#Override
public String toString() {
return this.name + " in " + this.country;
}
}

Who calls the equals method of the class while putting the elements into the HashMap?

I am new to Java (very new).
I am trying to understand HashMap and the equals method of a class and how it overrides the duplicates.
Please see following code:
public class Student {
Integer StudentId;
String Name;
String City;
public Student(Integer studentId, String name, String city) {
super();
StudentId = studentId;
Name = name;
City = city;
}
public Integer getStudentId() {
return StudentId;
}
public String getName() {
return Name;
}
public String getCity() {
return City;
}
#Override
public int hashCode() {
System.out.println("haschode is called for " + this);
final int prime = 31;
int result = 1;
result = prime * result + ((StudentId == null) ? 0 : StudentId.hashCode());
return result;
}
#Override
public boolean equals(Object obj) {
System.out.println("equals is called for " + this);
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Student other = (Student) obj;
if (StudentId == null) {
if (other.StudentId != null)
return false;
} else if (!StudentId.equals(other.StudentId))
return false;
return true;
}
#Override
public String toString() {
return "\n Student [StudentId=" + StudentId + ", Name=" + Name + ", City=" + City + "] \n";
}
public static void main(String[] args) {
// TODO Auto-generated method stub
Map<Student, String> myMap = new HashMap<Student, String>();
myMap.put(new Student(1, "andy", "p"), "Great"); //Line 1
myMap.put(new Student(2, "sachin", "m"), "Better");
myMap.put(new Student(3, "dev", "s"), "Good");
myMap.put(new Student(1, "andy", "p"), "Excellent"); // Line 4
System.out.println(myMap);
}
}
Now, the code written in main() calls the equals method only when I write the code to put the same key again i.e. "Line 4" (see my code indentation).
Why is the equals method not called for "Line 2" and "Line 3"??
It should call equals for every put line .... correct?
I am missing some understanding here and am left with questions:
(1) Why is every put not calling the equals method to check the equality of class members?
(2) Who triggers the call of the Student class equals method?
It should call equals for every put line .... correct ?
No. A HashMap will call equals only after it encounters a hash collision between an existing key and the one given in put.
Rephrased, it calls hashCode first to determine which "hash bucket" to put the key into, and if there are already keys inside the target bucket, it then uses equals to compare the keys in the bucket for equality.
Since the value of Student.hashCode() is based on ID alone, during insertion, the map only needs to call equals when it encounters a Student key with the same ID as what is being inserted. If no existing keys have the same hashCode as the one being inserted, there is no need to call equals.
This makes HashMap very efficient during insertion. This is also why there is a contract between hashCode and equals: If two objects are equal as defined by equals, they must also have the same hashCode (but not necessarily vice-versa).
equals() is not called if the hashCode() result is different. It's only the same for Line 1 and Line 4 (same student Id of 1), so equals() is called for that.
Note that hashCode() may be the same for two objects that aren't equals(), but two equals() objects must never have a different hashCode():
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.
So the initially different hash code is enough to not call equals() afterwards.
The whole purpose of a hash-based map is to operate on hash values (for efficiency that is).
The Map first and foremost cares about different hash values. Thus, as long as any "incoming" key has an (so far) unknown hash, equality doesn't matter.
Only when you run into a conflicting hash, then it matters whether that incoming key is actually a different key, or the same key. In the first case, you add a new key/value pair to the map, in the second case, you update an already stored key with a potential new value!
Therefore calling equals() only happens for situations where the Map implementation has to decide whether two keys that have the same hash are equal, or not.
If hash code differs, then there is no case for calling equals. Look at the code for HashMap(). If the hash is the same, then equals is called.
As you can see while running your code, hashcode() is called for every .put() call.
Basicly hashcode is called for every put() operation, if it is unique then a new element can be placed in the map - as one of the conditions for the hashcode() says, that different hashcodes always represent different objects. However, different object don't always have different hashcodes. Because of that, if the hashcodes are the same for two objects, hashmap have to check object equality with equals().
If you want to watch how the hashCode and equals methods work for selected values,
create the following map:
Map<Key, Value> map = new HashMap<>();
Then create instances of the following classes and use them to populate the Map. I recommend using a String as the object of both classes since I used its equals method in both.
Notice that you supply the hashCode to be returned. This allows it to be the same or different so you can see how the map behaves in different situations.
class Value {
private Object obj;
private int hashCode;
public Value(Object obj, int hashCode) {
this.obj = obj;
this.hashCode = hashCode;
}
public int hashCode() {
System.out.println("Value: hashCode is called - " + hashCode);
return hashCode;
}
public boolean equals(Object o) {
System.out.println("Value: equals is called - " + obj);
return obj.equals(o);
}
public String toString() {
return "Value: obj = " + obj + ", hashCode = " + hashCode;
}
}
class Key {
private Object obj;
private int hashCode;
public Key(Object obj, int hashCode) {
this.obj = obj;
this.hashCode = hashCode;
}
public int hashCode() {
System.out.println("Key: hashCode is called - " + hashCode);
return hashCode;
}
public boolean equals(Object o) {
System.out.println("Key: equals is called - " + obj);
return obj.equals(o);
}
public String toString() {
return "Key: obj = " + obj + ", hashCode = " + hashCode;
}
}
You can read the source code of HashMap.java in JDK 1.7.
Than you will understand the questions you asked.
Other answers are more helpful after you have reading the source code of HashMap.

dosent understand how sort works

I'm trying to understand how the comparable compareTo method sort the input. Below is the compareTo method implemented:
#Override
public int compareTo(Name n) {
int lastCmp = lastName.compareTo(n.lastName);
return (lastCmp != 0 ? lastCmp : firstName.compareTo(n.firstName));
}
The input array to the Collections.sort method is:
Name nameArray[] = {
new Name("John","Smith"),
new Name("Karl","Ng"),
new Name("Jeff","Smith"),
new Name("Tom","Rich")
};
List<Name> names = Arrays.asList(nameArray);
Collections.sort(names);
I don't understand what values are taken in to the compareTo method. (n.lastName and lastname) in which order?
The Collections.sort() method uses different algorithms to sort depending on the collection length and type (I think...)
The compareTo method should return negatives, 0, or positives if the object is less-than, equal or greater-than respectively.
the lastName variable refers to the Last Name in the Name class, composed of firstName:String and lastName:String.
The method first compares the lastName (object of type String) to the lastName of the passed object "n" (of type Name). If it is different than 0 (meaning not equal) return that value. If it is equal then compare the first name and return that.
So it is only comparing the two strings (firstName and lastName of the Name objects).

Why objects are not same added to hashset with same value, even hashCode and equals are overriden

Here is the code which produces hashset size 3 instead 2
package dump.test;
import java.util.*;
public class WrappedString {
private String s;
public WrappedString(String s) { this.s = s; }
public static void main(String[] args) {
HashSet<Object> hs = new HashSet<Object>();
WrappedString ws1 = new WrappedString("aardvark");
WrappedString ws2 = new WrappedString("aardvark");
String s1 = new String("aardvark");
String s2 = new String("aardvark");
hs.add(ws1); hs.add(ws2); hs.add(s1); hs.add(s2);
System.out.println(hs.size()+hs.toString());
}
public boolean equals(Object aSong) {
String s = aSong.toString();
System.out.println(s);
return s.equals(this.s);
}
public int hashCode() {
System.out.println(this.s + "-" + this.s.hashCode());
return this.s.hashCode();
}
/*public int compareTo(Object aSong) {
String s = aSong.toString();
return this.s.compareTo(s);
}*/
}
It always print below output if equals and hashCode are overridden
you can see both objects having same code in output but counted as different and produced count as 3
this is if we do not override equals and hashCode
Please assist me how this works.
The problem is that your case is not symmetric. If the implementation decides to invoke equals on the String instance with your class as an argument, it will definitely return false and thus your code will not always work.
From the JavaDoc for the Set interface:
A collection that contains no duplicate elements. More formally, sets contain no pair of elements e1 and e2 such that e1.equals(e2), and at most one null element. As implied by its name, this interface models the mathematical set abstraction.
So, HashSet checks the equals(Object) before adding the new element. As the second String is equal to the first, it's not added. On your WrappedString, you are not overriding the equals(Object), so, you are using the one inherited from Object, which simply checks the object IDs (659e0bfd and 2a139a55 in your case).
adding
public String toString() {
return this.s;
}
cleared my confusion. Previously it couldn't convert true value from casting object to string and needed to override toString to return underlying value.

How do I write a compareTo method which compares objects?

I am learning about arrays, and basically I have an array that collects a last name, first name, and score.
I need to write a compareTo method that will compare the last name and then the first name so the list could be sorted alphabetically starting with the last names, and then if two people have the same last name then it will sort the first name.
I'm confused, because all of the information in my book is comparing numbers, not objects and Strings.
Here is what I have coded so far. I know it's wrong but it at least explains what I think I'm doing:
public int compare(Object obj) // creating a method to compare
{
Student s = (Student) obj; // creating a student object
// I guess here I'm telling it to compare the last names?
int studentCompare = this.lastName.compareTo(s.getLastName());
if (studentCompare != 0)
return studentCompare;
else
{
if (this.getLastName() < s.getLastName())
return - 1;
if (this.getLastName() > s.getLastName())
return 1;
}
return 0;
}
I know the < and > symbols are wrong, but like I said my book only shows you how to use the compareTo.
This is the right way to compare strings:
int studentCompare = this.lastName.compareTo(s.getLastName());
This won't even compile:
if (this.getLastName() < s.getLastName())
Use
if (this.getLastName().compareTo(s.getLastName()) < 0) instead.
So to compare fist/last name order you need:
int d = getFirstName().compareTo(s.getFirstName());
if (d == 0)
d = getLastName().compareTo(s.getLastName());
return d;
The compareTo method is described as follows:
Compares this object with the specified object for order. Returns a
negative integer, zero, or a positive integer as this object is less
than, equal to, or greater than the specified object.
Let's say we would like to compare Jedis by their age:
class Jedi implements Comparable<Jedi> {
private final String name;
private final int age;
//...
}
Then if our Jedi is older than the provided one, you must return a positive, if they are the same age, you return 0, and if our Jedi is younger you return a negative.
public int compareTo(Jedi jedi){
return this.age > jedi.age ? 1 : this.age < jedi.age ? -1 : 0;
}
By implementing the compareTo method (coming from the Comparable interface) your are defining what is called a natural order. All sorting methods in JDK will use this ordering by default.
There are ocassions in which you may want to base your comparision in other objects, and not on a primitive type. For instance, copare Jedis based on their names. In this case, if the objects being compared already implement Comparable then you can do the comparison using its compareTo method.
public int compareTo(Jedi jedi){
return this.name.compareTo(jedi.getName());
}
It would be simpler in this case.
Now, if you inted to use both name and age as the comparison criteria then you have to decide your oder of comparison, what has precedence. For instance, if two Jedis are named the same, then you can use their age to decide which goes first and which goes second.
public int compareTo(Jedi jedi){
int result = this.name.compareTo(jedi.getName());
if(result == 0){
result = this.age > jedi.age ? 1 : this.age < jedi.age ? -1 : 0;
}
return result;
}
If you had an array of Jedis
Jedi[] jediAcademy = {new Jedi("Obiwan",80), new Jedi("Anakin", 30), ..}
All you have to do is to ask to the class java.util.Arrays to use its sort method.
Arrays.sort(jediAcademy);
This Arrays.sort method will use your compareTo method to sort the objects one by one.
Listen to #milkplusvellocet, I'd recommend you to implement the Comparable interface to your class as well.
Just contributing to the answers of others:
String.compareTo() will tell you how different a string is from another.
e.g. System.out.println( "Test".compareTo("Tesu") ); will print -1
and System.out.println( "Test".compareTo("Tesa") ); will print 19
and nerdy and geeky one-line solution to this task would be:
return this.lastName.equals(s.getLastName()) ? this.lastName.compareTo(s.getLastName()) : this.firstName.compareTo(s.getFirstName());
Explanation:
this.lastName.equals(s.getLastName()) checks whether lastnames are the same or not
this.lastName.compareTo(s.getLastName()) if yes, then returns comparison of last name.
this.firstName.compareTo(s.getFirstName()) if not, returns the comparison of first name.
You're almost all the way there.
Your first few lines, comparing the last name, are right on track. The compareTo() method on string will return a negative number for a string in alphabetical order before, and a positive number for one in alphabetical order after.
Now, you just need to do the same thing for your first name and score.
In other words, if Last Name 1 == Last Name 2, go on a check your first name next. If the first name is the same, check your score next. (Think about nesting your if/then blocks.)
Consider using the Comparator interface described here which uses generics so you can avoid casting Object to Student.
As Eugene Retunsky said, your first part is the correct way to compare Strings. Also if the lastNames are equal I think you meant to compare firstNames, in which case just use compareTo in the same way.
if (s.compareTo(t) > 0) will compare string s to string t and return the int value you want.
public int Compare(Object obj) // creating a method to compare {
Student s = (Student) obj; //creating a student object
// compare last names
return this.lastName.compareTo(s.getLastName());
}
Now just test for a positive negative return from the method as you would have normally.
Cheers
A String is an object in Java.
you could compare like so,
if(this.lastName.compareTo(s.getLastName() == 0)//last names are the same
I wouldn't have an Object type parameter, no point in casting it to Student if we know it will always be type Student.
As for an explanation, "result == 0" will only occur when the last names are identical, at which point we compare the first names and return that value instead.
public int Compare(Object obj)
{
Student student = (Student) obj;
int result = this.getLastName().compareTo( student.getLastName() );
if ( result == 0 )
{
result = this.getFirstName().compareTo( student.getFirstName() );
}
return result;
}
If you using compare To method of the Comparable interface in any class.
This can be used to arrange the string in Lexicographically.
public class Student() implements Comparable<Student>{
public int compareTo(Object obj){
if(this==obj){
return 0;
}
if(obj!=null){
String objName = ((Student)obj).getName();
return this.name.comapreTo.(objName);
}
}

Categories