Java Collection Framework and Generics

As the name suggests, a Java Collection is a collection of objects represented as a single unit. The idea is to provide a set of operations on the logically grouped elements like searching, sorting, etc.

collection framework hierarchy
source: https://techvidvan.com/tutorials/java-collection-framework/

As the image above shows, there are two core interfaces, Collection and Map. The collection further has interfaces for List, Queue, and Set. For each of these interfaces than we have a concrete set of classes implementing the functionality.

Let’s take a very simple example

Collection values = new ArrayList();
values.add(1);
values.add(2);
values.add("kamal");

We can see collection interface helps us create a new ArrayList, which gives us flexibility over the array where the List can grow during runtime.

But one challenge we can see in the code above is that when we are able to add numbers as well as String to the ArrayList. This is in contrast with the type safety provided by Java. To solve this Java provides us Generics to control over types of elements that can be added to a collection.

Generics Example

class MyClass<T>{
T value;

public T getValue() {
return value;
}

public void setValue(T value) {
this.value = value;
}
}

Coming to our previous code for creating collections

Collection<Integer> values = new ArrayList<>();
values.add(1);

In this example, we cannot add a String to values now.

What if we want to get or add into ArrayList at a particular index. List Interface adds these features on top of Collection. As already depicted in the hierarchy, List inherits Collection and then adds features on top of it.

List<Integer> values = new ArrayList<>();
values.add(1);
int num = values.get(0);

Iterating over a Collection

There are multiple ways to iterate over a collection

List<Integer> values = new ArrayList<>();
values.add(1);
values.add(2);
values.add(3);
Iterator<Integer> i= values.iterator();
while(i.hasNext()) {
    System.out.println(i.next());
}

Another way is to use for loop

for(Integer num:values) {
   System.out.println(num);
}

Or we can use Streams

values.stream().forEach(System.out::println)

Comparable vs Comparator

Another important thing one would like to do with the collection is to arrange elements in sorted order. For example, say we have a List created for Student class objects, and we want to arrange based on Roll numbers.

The easiest way is to make the class implement comparable

class Student implements Comparable<Student>{

 int rollno;
 String name;

 public Student(int rollno, String name) {
  this.rollno=rollno;
  this.name=name;
 }

 @Override
 public int compareTo(Student o) {
  return this.rollno<o.rollno?-1 : (this.rollno==o.rollno)?0:1;
 }
}

// In calling code, we can sort list made up of students 
Collections.sort(list);

There can be cases where it is handy to maintain comparison based on multiple criteria. For example, along with roll number, we want to make sure we can sort the list of students based on names in alphabetical order. In such a case it makes sense to implement Comparator as we can implement multiple comparators based on our need.

class NameComparator implements Comparator<Student>{
 @Override
 public int compare(Student o1, Student o2) {
  return o1.name.compareTo(o2.name);
 }
}

// In calling code, we will send compatator instance 
Collections.sort(list, new NameComparator());