newToJava
newToJava

Reputation: 173

Adding new elements to an array without duplication in Java

In case 5 of the switch statement below I'd like the user to input one of the students from the array and select a module from the modules array to enrol them on without duplication. Any ideas/examples would be very useful. Thanks in advance.

Control Class:

import java.util.Scanner;

public class Control {

public void run() {


    while (true) {
        Menu menu = new Menu();
        menu.getMainMenu();

        try {
            Scanner scan = new Scanner(System.in);

            int selection = scan.nextInt();
            switch (selection) {
                case 1:
                    for (Student student : students) {
                        System.out.print(student.getName() + " ");
                    }
                    break;
                case 2:
                    for (Module module : modules) {
                        System.out.print(module.getName() + " ");
                    }
                    break;
                case 3:
                    ...
                case 4:
                    ...
                case 5:
                    // Print out students 
                    System.out.println("select a student: ");
                    for (int i = 0; i < students.length; i++) {
                        System.out.println(i + " " + students[i]);
                    }

                    selection = scan.nextInt();

                    ############################
                    Confusion here
                    ############################

                case 6:
                    System.out.println("Goodbye!");
                    System.exit(0);
                    break;
                default:
                    System.out.println("Invalid option selected. You must enter a number between 1 & 6!");
            } // end switch

        } catch (Exception e) {
            System.out.println("Invalid entry. You must enter a number between 1 & 6");
        }
    } // end while

}
}

Upvotes: 1

Views: 483

Answers (7)

Adriaan Koster
Adriaan Koster

Reputation: 16209

There is a conceptual weakness in your class model which is giving you troubles. Because a Student has a list of Modules and a Module also has a list of Students you have to do bookkeeping in two places and there is a potential for inconsistency, for example:

jane : UFCE1, UFCE2
UFCE1 : alex, mike

An object model like this is guaranteed to give you (or the developer who has to maintain your code after you have left) terrible headaches.

What you could do is remove the lists from the Student and Module classes and create a service class which keeps track of enrollments:

public class EnrollmentService {

    private final Map<Module, List<Student>> enrollments;

    public boolean addModule(Module module) {...

    public boolean enroll(Student student, Module module) {...

    public final List<Student> getStudents(Module module) {...

    public List<Module > getModules(Student student) {...
}

(See complete code example)

Each design decision is a tradeoff. Here the obvious drawback is the 'getModules' method, which has to walk the map to collect all modules a given student has enrolled in. You could opt for a second map to keep track of this reverse lookup which is faster, but gives you a double bookkeeping again. Having a double bookkeeping in a single class is a bit less of a pain than having it in separate classes though...

Another often used and perfectly valid way to model this, is to keep track of the Student-Module relation in only one of the domain classes. In this case I would choose Module but it really depends on your requirements. Of course you still need a service class to enroll Students and perform lookups.

A few remarks in closing:

  • creating a suitable class model is hardly ever a case of Animal -> Mammal -> Cow like they often make you believe in OOP 101.
  • The quality of a class model is measured by how easy it is to understand and how well it solves your problem, now and (hopefully) in the future.
  • A good model divides the responsibilities in an understandable way and co-locates state and behaviore where possible
  • When certain behavior does not clearly belong in a single class, it is usually a good idea to create a separate (service) class to do the work.
  • If you find it hard to come up with a good name for a class (or method) this often means your model does not suit the problem well enough. Rethink the model (and the problem) in that case.

I hope this helps!

Upvotes: 1

mprabhat
mprabhat

Reputation: 20323

Your criteria for equality is name for Student add equals and hashcode implementation to it like :

/* (non-Javadoc)
 * @see java.lang.Object#hashCode()
 */
@Override
public int hashCode() {
    final int prime = 31;
    int result = 1;
    result = prime * result + ((name == null) ? 0 : name.hashCode());
    return result;
}

/* (non-Javadoc)
 * @see java.lang.Object#equals(java.lang.Object)
 */
@Override
public boolean equals(Object obj) {
    if (this == obj) {
        return true;
    }
    if (obj == null) {
        return false;
    }
    if (!(obj instanceof Student)) {
        return false;
    }
    Student other = (Student) obj;
    if (name == null) {
        if (other.name != null) {
            return false;
        }
    } else if (!name.equals(other.name)) {
        return false;
    }
    return true;
}

Now in your Module class addStudent() method you have following choices of implementation :

 if(!students.contains(student) {
   students.add(student);
 } // You will add to the list only if Student with same name doesn't exist.

OR change to Set implementation so from List students it will become Set and then in your addStudent method your call will be students.add(student);//this call will check if there is any student object already in the set with same name, if yes it wont add or else student will be added

Upvotes: 0

rajah9
rajah9

Reputation: 12339

In your example, the identifier and test-for-uniqueness for the student is the name (jane/alex/mike).

If you used a HashMap with the name as the index, then adding (with .put) to the HashMap will add if new but not duplicate if repeated.

You might want to consider overriding equals() and hashCode() to tell Java how to determine whether two students are the same. The name alone will give you problems if you have two different students with the same name.

Upvotes: 1

user949300
user949300

Reputation: 15729

While I agree with the sentiments to use Sets, sometimes a List feels better or more natural because it has the notion of ordering. You can, for example, sort by GPA and ask for the 3rd best student.

you could do something like

synchronized boolean addFoo(Foo foo) {
  if mList.contains(foo)
     return false;
  else
     return mList.add(foo);
}

Upvotes: 0

M S
M S

Reputation: 4093

You can use set implementation (HashSet, LinkedHashSet) to avoid duplicates.

or use ArrayList. But in this case do the check

list.contains(obj)

before insertion

With HashSet you will not know the order of insertion. but with LinkedHashSet and ArrayList you can

and if needed you can use

toArray()

function in Set or ArrayList to convert the list to array

Upvotes: 1

Colby
Colby

Reputation: 459

I would use a Set instead of an array if possible. Then you can just implement .equals and .hashcode in your Student class and have control over what 'duplication' means in your application.

If order is important too, perhaps a LinkedHashSet...

Upvotes: 0

Paul Croarkin
Paul Croarkin

Reputation: 14675

If you want to avoid duplicates, don't use arrays or Lists. Use a Set.

Upvotes: 1

Related Questions