0
// Objects for the bookstore I created

BookStore bookStore = new BookStore("Subu's Book Store","Perambur");
bookStore.addItem(new Item("Animal Farm",20));
bookStore.addItem(new Item("Animal Farm",20));

Output if I used the original working method:

Animal Farm has been bought
Animal Farm is already bought

Output for the method that doesn't work using contains()

Animal Farm has been bought
Animal Farm has been bought

Output for the method that doesn't work using indexof()

-1
Animal Farm has been bought
-1
Exception in thread "main" java.lang.IndexOutOfBoundsException: Index -1 out of bounds for length 1
at java.base/jdk.internal.util.Preconditions.outOfBounds(Preconditions.java:64)
at java.base/jdk.internal.util.Preconditions.outOfBoundsCheckIndex(Preconditions.java:70)
at java.base/jdk.internal.util.Preconditions.checkIndex(Preconditions.java:248)
at java.base/java.util.Objects.checkIndex(Objects.java:372)
at java.base/java.util.ArrayList.get(ArrayList.java:459)
at com.company.subusproject.BookStore.searchItem(BookStore.java:41)
at com.company.subusproject.BookStore.addItem(BookStore.java:16)
at com.company.subusproject.Main.main(Main.java:11)

This is the original method that works

public boolean searchItem(Item item) {
  for (int i = 0; i < this.myStocks.size(); i++) {
    Item foundItem = this.myStocks.get(i);
    if (foundItem.getName().equals((item.getName()))) {
      return true;
    }
  }
  return false;
}

This is the method I don't know why it doesn't work, how I intended this method to work is, if the variable searchingItem returns true then the method searchItem will return if not it returns false

public boolean searchItem(Item item) {
  // this method returns the boolean value true if found, if not returns false   
  boolean searchingItem = this.myStocks.contains(item);
  if(searchingItem){
    return true;
  } else {
    return false;
  }
}

This is also doesn't work as I intended. The way I intended this method to work is, when i found the index position of the element i am looking for and then compare it with the elements that are available in the list myStocks and that isn't the case in this scenario as well.

public boolean searchItem(Item item) {
  // this method returns the index number of the founded element, if not returns -1
  int itemPosition = this.myStocks.indexOf(item); // this line always returns -1 in this scenario
  for(int i=0; i<this.myStocks.size(); i++){
    if(myStocks.get(itemPosition).getName().equals(myStocks.get(i).getName())){
      return true;
    }
  }
  return false;
}

Store class to create a any store class

public abstract class Store {
  private String name;
  private String address;
    
  public Store(String name, String address) {
    this.name = name;
    this.address = address;
  }
    
  public abstract boolean addItem(Item item);
    
  public abstract boolean removeItem(Item item);
    
  public abstract boolean searchItem(Item item);        
}

Basic items class to add any kind of items

public class Item {
    
  private String name;
  private int price;
    
  public Item(String name, int price){
    this.name = name;
    this.price = price;
  }
    
  public String getName(){
    return this.name;
  }
    
  public int getPrice(){
    return this.price;
  }  
}
    

Actual book store class where it has the methods to add and search

public class BookStore extends Store {
        
  private List<Item> myStocks;
        
  public BookStore(String name, String address) {
    super(name, address);
    this.myStocks = new ArrayList<>();
  }
        
  public boolean addItem(Item item) {
    if (!searchItem(item)) {
      this.myStocks.add(item);
      System.out.println(item.getName() + " has been bought");
      return true;
    } else {
      System.out.println(item.getName() + " is already bought");
      return false;
    }
  }
        
  public boolean searchItem(Item item) {
    for (int i = 0; i < this.myStocks.size(); i++) {
      Item foundItem = this.myStocks.get(i);                
      if(foundItem.getName().equals((this.myStocks.get(i).getName()))) {
        return true;
      }
    }
    return false;
  }
}

Clearly, I know the assumption I am making about how it should work is wrong and the problem is with those inbuild methods and how I wanted them to work but that isn't the case.

Markus Weninger
  • 11,931
  • 7
  • 64
  • 137
  • Please do not link textual output as images. – Markus Weninger Aug 18 '21 at 09:59
  • 2
    Please format your code and don't put code/text as images – Clashsoft Aug 18 '21 at 09:59
  • 8
    `contains` and `indexOf` rely on either the `hashCode` or `equals` method of object. You haven't implemented either of them so it will never work as it will always return a different hashCode and false for equals. – M. Deinum Aug 18 '21 at 10:00
  • You need to implement `equals`/`hashCode` on `Item` for `myStocks.contains(item)` to behave as expected, see https://medium.com/@sp00m/java-object-identity-or-how-to-override-equals-hashcode-and-compareto-400fd4547fe0. – sp00m Aug 18 '21 at 10:00
  • You need to only show the code that is relevant – Chris Maggiulli Aug 18 '21 at 10:04
  • sorry i edited thoroughly to the best i can express – mockingbird Aug 18 '21 at 10:30
  • Tip: If the main purpose of the class is to communicate data transparently and immutably, use the [records](https://openjdk.java.net/jeps/395) feature in Java 16 and later, to benefit from the default implementations of `equals` & `hashCode`. – Basil Bourque Aug 18 '21 at 15:05

2 Answers2

8

Solution:

You have to implement equals and hashCode in Item, as contains relies on equals.

If you do not override equals Java by default resorts to reference equality.

From the documentation of contains:

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 (o==null ? e==null : o.equals(e)).

Rules for overriding equals:

Partly taken from here: Java SE defines a contract that equals must fulfill. The equals method must be:

  • reflexive: an object must equal itself
  • symmetric: x.equals(y) must return the same result as y.equals(x)
  • transitive: if x.equals(y) and y.equals(z) then also x.equals(z)
  • consistent: the value of equals should change only if a property that is contained in equals changes (no randomness allowed)

Why also overriding hashCode?

Also partly taken from here. Java SE also defines a contract for the hashCode method. A thorough look at it shows how closely related hashCode and equals are. All three criteria in the contract of hashCode mention in some ways the equals method:

  • internal consistency: the value of hashCode may only change if a property that is in equals changes
  • equals consistency: objects that are equal to each other must return the same hash code
  • collisions: unequal objects may have the same hash code (i.e., just because two objects have the same hash code does not mean that they are equal)

I suggest to utilize the IDE to auto-generate equals and hashCode and to only use final fields within both methods to determine their return value.

Only if these rules are followed, we can expect data structures from the standard library (or any other data structure) to work correctly.

Markus Weninger
  • 11,931
  • 7
  • 64
  • 137
  • You explain that `contains` relies on `equals. Why does `hashCode` have to be implemented? – Basya Aug 18 '21 at 10:07
  • 2
    @Basya Because the documentation of `equals` demands that `hashCode` has to be implemented as well whenever `equals` is overriden. Methods are allowed to rely on this fact at any time. So even when `contains` might not use `hashCode` today, it might use it tomorrow. Just use your IDE to auto-generate both methods. – Zabuzard Aug 18 '21 at 10:11
  • 1
    Thank you @Zabuzard. I think the answer would be clearer with that explained... – Basya Aug 18 '21 at 10:14
  • This is wholly new information for me, I never heard of overriding equals and hashcode, could you explain why it is and how to implement it? How it is contains() is linked to equals and hashcode, i used contains many times on primitive data and it worked fine but i never usd it on objects. I did some google on this concept and it all are a little overwhelming for me? – mockingbird Aug 18 '21 at 11:26
  • 1
    I just expanded my answer. I hope it helps. If so, please accept the answer. :) – Markus Weninger Aug 18 '21 at 11:29
  • @mockingbird - you are correct that this would be a good addition to the answer. To implement the functions though -- most IDEs will generate them automatically for you I believe, and then you can just check them to confirm that they do what you want (compare the fields necessary to determine equality, ignore any fields which are not relevant such as temporary state information or some such). The basic idea is to tell the 'contains' function how to know whether two objects are equal. – Basya Aug 18 '21 at 11:31
  • 1
    @MarkusWeninger - I can't accept; I'm not the OP, but the edit got an upvote from me. I've learned something new today. – Basya Aug 18 '21 at 11:33
  • @mockingbird Did my answer help you in understanding the underlying problem? – Markus Weninger Aug 19 '21 at 08:06
  • Yes, it pushed me in the right way, your answer and the subsequent links and references that follow, and actually, when I override the equals() method in my item class, both indexOf() and contains() method works without overriding hashcode() method, what my doubts really are, the contracts of equals(), I can understand the theory as I learned them in algebra, but I can't make sense of them in the actual implementation of the contract such as reflexiv, symmetric , transitive and mainly about consistent with equals – mockingbird Aug 20 '21 at 09:59
-1

You need to implement equals() method in you Item class, contains() or indexOf() internally use equals() method. also Since it comes in contract you have to implement hascode() method this will make sue there is no breaking contract.

If you don't implement hashcode it will take memory location based on JVM implementation which will always return different int for equal object as well, it will fail while you use the Item object as key in hashmap, also its performance while hashing Collections much depends on Hascode implementations (long subject so not getting in details).

Read the javadoc.