0

I have two classes, one named Bird the other Runtime. The bird class creates birds - name, latin name. The Runtime class has 4 methods, only one is important for this question, that is the 'add' method. The add method when called upon needs to take input from the user that is name and latin name, these are saved into a string variable 'name' and 'latin name' and I call the Bird class constructor and pass in these string variables into its parameter and finally it is added to an ArrayList. However I get duplicate values, if I were to write the same bird twice.

I have tried to convert the ArrayList into a set and convert it back again into an ArrayList, i did this within the add method, this did not work. I suspect it is down to my poor understanding of how objects are stored in an ArrayList. I also created a getName method within the Bird class, so I can use list.get(i).getName, and if the name is equal to the one typed by the user, it prompts the user accordingly, if not it is added to my ArrayList. This also did not work. I also tried a for loop that would go through the ArrayList and an if statement would determine if the name typed by the user exists within the ArrayList, this also did not work, the attempt was early on so I can't remember exactly the error message, but the add method is called from within a while loop, and I think the error message was concurrent modification, I'm not entirely sure so please ignore that, my point is showing the various solutions I tried.

Below is The Bird class

    public class Bird{

       int obeservation = 0;
       String name;
       String latinName;

       public Bird(String name, String latinName){
         this.name = name;
         this.latinName = latinName;
       }

       public void addBird(String name, String latinName){
         this.name = name;
         this.latinName = latinName;
       }

       public String getName(){
         return this.name;
       }

       public String statistics(){
         return this.name + " (" + this.latinName + ") : " +         
         this.obeservation + " observation";
       }
   }

Below is the Runtime class

    public class Runtime {

        ArrayList<Bird> birds = new ArrayList<Bird>();

        Scanner scan = new Scanner(System.in);

        public void scan() {
            System.out.println("?");
            String answer = scan.nextLine().trim();
            while (!answer.equalsIgnoreCase("EXIT")) {
              System.out.println("?");
              answer = scan.nextLine().trim().toUpperCase();
            if (answer.equalsIgnoreCase("ADD")) {
                add();
            } else if (answer.equalsIgnoreCase("OBSERVATION")) {
                observation();
            } else if (answer.equalsIgnoreCase("STATISTICS")) {
                System.out.println("jhjh");//just to see if this is     
                working
                statistics();
            } 
        }
    }

below is the add method, also what I've commented is the attempts, currently the add method does not have an if statements to decide duplicates.

    public void add() {

         System.out.print("Name: ");
         String name1 = scan.nextLine().trim().toUpperCase();

         System.out.print("Latin Name: ");
         String latinName1 = scan.nextLine().trim().toUpperCase();

         birds.add(new Bird(name1, latinName1));


         /*
         Bird newBird = new Bird(name1, latinName1);

         for (int i = 0; i < birds.size(); i++) {
             if (birds.get(i).getName().equals(name)) {
                 System.out.println("Bird already exist");
                 return;
             } else {
                 birds.add(newBird);
             }
         }
         /*
          * hBirds.addAll(birds); birds = new ArrayList<Bird>();     

        birds.addAll(hBirds);
            * 
            * // Bird newBird = new Bird(name, latinName); 
            * /* if(birds.contains(name)){
            * System.out.println("That name already exist"); 
            * return; 
            * }else{ 
            * birds.add(newBird(name, latinName));
            * 
            * }
            */
     } 

The statistics method prints out the ArrayList, a foreach loop that goes through the ArrayList prints it out. The expected result if I input seagull twice should be one seagull value not two. How do i reject the duplicate?

Kiran Mistry
  • 2,614
  • 3
  • 12
  • 28
Baba
  • 3
  • 4
  • An `ArrayList` does not check for duplicates, you could stuff the same object in there over and over again. Use a `SortedSet`, make `Bird implements Comparable` and implement the methods `equals(Bird anotherBird)`, `hashCode()` and `compareTo(Bird differentBird)`. Having done that, you won't have any duplicates anymore... Maybe a `Set` is sufficient if you don't care for your birdies being sorted. – deHaar Jul 01 '19 at 12:24
  • You're not defining your equal condition for your class. Override equals method and use it to check if it already exists in the ArrayList before adding – fdelafuente Jul 01 '19 at 12:25
  • You could have your Bird class override `equals` and `hashcode` method. Then you could simply use the `contains` method of the list to check if the bird is already in there. – OH GOD SPIDERS Jul 01 '19 at 12:25
  • @OHGODSPIDERS - this is also mentioned in the popular answer below, how do I go about overriding the equals method and hashcode? – Baba Jul 01 '19 at 13:22
  • @Baba many IDEs already have a built in function to create those methods for you. If you are using Eclipse you would just need to select the Option "Source -> Generate hashCode() and equals()" and then in the following dialogue select the class fields that you want involve in checking for equality which would just be the "name" field in your case. – OH GOD SPIDERS Jul 01 '19 at 13:26
  • @Baba and in case you want to write those methods manually yourself or just want to properly understand them i recommend reading: [What issues should be considered when overriding equals and hashCode in Java?](https://stackoverflow.com/q/27581/6073886) – OH GOD SPIDERS Jul 01 '19 at 13:35
  • @OHGODSPIDERS Thanks was just about to ask for recommended reading material. Also the overriding of these methods will be done in the Bird class right? And why cant I just override the equals method, I haven't looked into hashcode or any other type of sets or lists, I don't think I am capable yet. – Baba Jul 01 '19 at 13:48
  • @baba Yes it would be done in the Bird class. As for your second question the short answer is because it is part of Javas design and Object contract that those 2 methods are related and therefor need to be overridden simultanously. A longer explanation can be found here: [Why do I need to override the equals and hashCode methods in Java?](https://stackoverflow.com/q/2265503/6073886) – OH GOD SPIDERS Jul 01 '19 at 14:15

2 Answers2

2

You can have two approaches here:

  • First: Traverse through ArrayList, if you can't find the same bird, add it to ArrayList. It is a worse approach.

  • Second: Store birds inside HashSet. In this case, you need to override .hashCode() and .equals(Object obj) methods. It is a better approach.

Before talking about how to generate .hashCode() and .equals(Object obj) methods, I want to mention about .hashCode() method and HashSet<T>.

HashSet<T>s provide a unique set of the elements inside. To achieve this, .hashCode() method of a class is used. If you override .hashCode() method in any class, you can get the benefit of using HashSet<T>s. If you don't override this method, Java automatically returns the memory address of the object. That's why your HashSet<Bird> was including duplicate elements.

.hashCode() and .equals() methods can be generated by lots of IDEs. I copied and pasted your Bird class to Eclipse. By using Alt+Shift+S -> h for Eclipse or Alt+Insert -> equals() and hashCode() for IntelliJ, automatically generated the methods below:

@Override
public int hashCode() {
    final int prime = 31;
    int result = 1;
    result = prime * result + ((latinName == null) ? 0 : latinName.hashCode());
    result = prime * result + ((name == null) ? 0 : name.hashCode());
    result = prime * result + obeservation;
    return result;
}

@Override
public boolean equals(Object obj) {
    if (this == obj)
        return true;
    if (obj == null)
        return false;
    if (getClass() != obj.getClass())
        return false;
    Bird other = (Bird) obj;
    if (latinName == null) {
        if (other.latinName != null)
            return false;
    } else if (!latinName.equals(other.latinName))
        return false;
    if (name == null) {
        if (other.name != null)
            return false;
    } else if (!name.equals(other.name))
        return false;
    if (obeservation != other.obeservation)
        return false;
    return true;
}

If you add these methods(I encourage you to generate in your IDE) to Bird class, you can use HashSet<Bird>. To avoid duplicates, simply add all of your Bird objects into defined HashSet<Bird>. You don't need any other data structure or equality check to control if any two Bird type objects are equal.

You will just need to change your object collection from ArrayList<Bird> birds = new ArrayList<Bird>(); to Set<Bird> birds = new HashSet<>();.

mdoflaz
  • 551
  • 6
  • 19
  • Either way, you'll want to properly implement `hashCode()` and `equals()` on `Bird` in order for it to work. – daniu Jul 01 '19 at 13:06
  • Thank you for the contribution. He is applying string comparison on name of the birds while new bird object is being added. So I didn't need to add this. Surely, it would be better to override equals method and compare objects that way. – mdoflaz Jul 01 '19 at 13:09
  • @mahmutoflaz Thanks for the reply, the answer below sorted my issue by Maurice Perry. You mention the second way is better. If I am honest i don't know how to go about doing it that way. But I really want to learn it if that is the best way. – Baba Jul 01 '19 at 14:14
  • @mahmutoflaz Unfortunately this worked but only till i used my observation method, this method would ask the user, what was observed? for which the user will reply with a name of a bird, if the bird existed in the set then it would take that object and increment its 'observation' field. However after a successful increment, I used the add method to add an existing birds name, and it would let me. Printing the set would show that two objects of the same name fields existed, only their observation fields were different. After calling the observation field again it would increment all objects. – Baba Jul 01 '19 at 18:39
-1

Move the add out of the loop:

    for (int i = 0; i < birds.size(); i++) {
        if (birds.get(i).getName().equals(name1)) {
            System.out.println("Bird already exist");
            return;
        }
    }
    birds.add(new Bird(name1, latinName1));
Maurice Perry
  • 9,261
  • 2
  • 12
  • 24
  • 1
    Why was this answer down voted, it has sorted my issue out. So if i understand this, for loop quite simply goes through the array, and if the 'if' statement is false the loop ends and carries on normally to the next statement which is birds.add...Thank you, only thing to sort out now is case sensitivity but that's easy. – Baba Jul 01 '19 at 13:38