Generics and Collections in Java Programming language

Abhiroop Nandi Ray
30 min readSep 23, 2018

What are generics?

According to Oracle java document website:

In a nutshell, generics enable types (classes and interfaces) to be parameters when defining classes, interfaces and methods. Much like the more familiar formal parameters used in method declarations, type parameters provide a way for you to re-use the same code with different inputs. The difference is that the inputs to formal parameters are values, while the inputs to type parameters are types

Generics were added in Java version 5.0. This is was implemented with an intention to allow a type (like Strings, Integers, custom objects etc.) or method to operate on different types. Generics also offers compile time safety. It catches invalid or mismatched types at the compilation. For example let us take a look at the code below:

Figure 1: Casting required as no generics is used

Casting is required as generics are no used

Figure 2: No casting required as generics is used

No casting required as generic is used

Generics enables programmers to enable write code to implement generic algorithms to work on different collections or data structures. Generics are not only safe they are also easy to interpret and read the code. The generic method declaration have a place for type parameter sections are written within angular brackets. (< / > ). Generics can be written by comma separated.

Before going more into generics and collections and their usages we need to have some fair idea on some the common methods requires. These are described below.

The toString() method:

This method belongs to the Object class i.e. the top level class and so this method is available to all the classes in the Java. The implementation of the toString() method in implemented as below:

toString() method as implemented in String class

The method toString() returns a string representation of the object. he method returns a string that “textually represents” this object. The result is expected to be a concise but informative representation that is easy for a person to read.

As we can see if we use the toString() method of the object class we will get the class name followed by ‘@’ with some alphanumeric hexadecimal expression of the object’s hashcode. We will get some information but in reality gives very little human readable information. To get some information regarding the object the toString() methods It is recommended that all subclasses override this method.

Let us see the following examples below to see the differences between overridden toString() method and normal Object class toString() method.

toString() method not overridden

The output of the above code will be something like this: PlayerClass@48987 (The number may vary).

But if we override the toString() method we will modify the code like below:

toString() method overridden by the class/object

The output of the above code will be:

The class name is PlayerClass and the player’s name is Lionel Messi. (It will also work similarly for C Ronaldo).

Now we have got some information about the class which are useful to a human person. We can override the toString() method and can as many information as we need to pass.

Thus it is important and a good coding style to override the toString() method in a class so that if required some proper, human readable information can be get from the class.

The equals(Object o) method:

This method belongs to the object class and is accessible by any class. It provides whether some other object is “equal” or same this one.

From javaDoc we see,

* The {@code equals} method for class {@code Object} implements
* the most discriminating possible equivalence relation on objects;
* that is, for any non-null reference values {
@code x} and
* {
@code y}, this method returns {@code true} if and only
* if {
@code x} and {@code y} refer to the same object
* ({
@code x == y} has the value {@code true}).
* <p>
* Note that it is generally necessary to override the {
@code hashCode}
* method whenever this method is overridden, so as to maintain the
* general contract for the {
@code hashCode} method, which states
* that equal objects must have equal hash codes.

The implementation to the method in the Object class is shown in below:

toString() as implemented in String class

We can see here basically, the method is only using the operator “==” check and returns the value. If the reference variable is same or refers to same object it returns true else false. It checks the bits in the variable and returns the Boolean value accordingly i.e. if they are identical true is returned or else it returns false.

“==” are used if we want to know whether two reference variables are equal or not.

equal() method is used on objects to check whether the contents of the two objects are same or not.

So if we use the Object#equals(Object o) method to check two objects it basically performs the action of “==” operator. The whole objective of the equals(Object o) method gets invalid. So we need to override the method in each class to get the proper result. Let’s see the following codes how with and without overriding the method can change the output.

The main class:

The Main Class

The sample class without overriding the equals(Object o):

toString() method not overriden

The output will be as followed:

sample1 & sample2 equals(): false

sample1 & sample2 == operator false

Now, let us override the toString method in the SampleClass and run the program again. The code will become as shown below:

toString() method overridden

The output will be as followed:

sample1 & sample2 equals(): true

sample1 & sample2 == operator false

In the first case as we have not overridden the method equals(Object o) the equals value implemented the Object#toString() method and in reality performed ‘==’ operation. Both the reference variable sample1 and sample2 creates similar objects but are different reference variable. So both the results came as false.

In the second case we have overridden the equals(Object 0) method which checks the contents of the objects now, instead of the reference variable. So we see the equal(Object o) checks the contents of both the objects and when it was found same it returns true. But still the ‘==’ operator returns false as expected, since both are different reference variable and are not the same.

But, as we have seen in String class without even overriding the equals(Object o) method we can use them and also get the proper responses. That is because in String class, Java has already overridden method. This is the equals(Object o) method of string class.

equals(Object o) method in String class

Basically the method is checking each characters in the two strings and if all of them are equal i.e. the contents of them are equal it returns true else false.

Many such classes have internal implementation, but not all. So we need to be careful while using .equals(Object o) method on objects to get the required proper value.

It gives us one important fact: if ‘==’ returns true .equals(Object o) will always be true, but the opposite doesn’t work that way.

The contract of equals(Object o) method

· Reflexive: a.equals(a) will always returns true

· Symmetric: if a.equals(b) is true, b.equal(a) will always be true

· Transitive: if a.equals(b) is true and b.equals(c) is true then a.equals(c) will always be true

Consistent: If not information provided to the classes is changed it will always returns the same, no matter how many time it is called.

a.equals(null) will always returns to null.

The hashCode() method:

This method belongs in the Object class so any class so any class can use and override it. The hashCode() method is used to increase the performance of searching in large quantities of data. Hashcodes are generated using a mathematical or hashcode algorithm and generating a output.

Hashcode may not are mostly unique for each object but they may not be unique in certain corner cases. Let’s have a look into it.

Consider we have few names like Alex, Bob, Anil and Nila. We want to implement hashcode algorithm to them. Our algorithm is to assign each letters their respective position number i.e. A=1, B=2, C=3 and so on. Then add those numbers and generate the hashcode.

In this table, we can see, even though we have 4 different hascodes. But two of them though are different in names have same hashcodes. In real scenarios hashing generally have more than one entry. One i.e. hashcode used to find first, then to use equals(Object o) to find the exact object. But if two objects are equal, their hascodes must always be equal, but the opposite is not true.

Hashcodes can return same value for all objects or some objects depending upon its implementation. If a hashCode function is written that it will return a constant value all the time, every objects will have same hashcode even though they are not the same. This makes hashcode not so useful and the equals(Object o) method finally will find out the exact object. This is an inefficient coding. The goal is that two equal objects should have equal hashcodes.

So to make an efficient program both the methods equals(Object o) and hashCode() should be properly overridden to fetch the exact object and also to make the fetching faster. For example if a hashcode is implemented improperly along with the equal(Object o) method (shown below), it will still be legal but extremely inefficient and improper.

Improper use of hashCode() and equals(Object o) methods

The contract of hashCode() method:

· In single execution the hascodes must be same for same object. But it may vary in another execution of the process.

· If two objects are equals, i.e. equals(Object o) returns true, then their hashcode will definitely be equal.

· If two objects are not equals, i.e. equals(Object o) returns I, still their hashcode can be same.

Collections in Java:

Collections, Collection and collection, these three words have three completely different interpretation in Java. So let us clear it out, before we dig into them.

1. collection (lower case c): Represents any data structures in which objects are stored, retrieved and iterated over.

2. Collection (Upper case c): It is an interface of java.lang.Collection. Set, Queue and List are extended from it. There is no direct implementation of Collection.

3. Collections (Upper case C and ends with s): It represents a class off java.lang.Collections. It contains a set of utility methods for the use of data structures or collections.

Basic functions of Collection:

· Add an object to the collection

· Remove an object to the collection

· Find out, if an object is present in the collection

· Retrieve an object from the collection without deleting it.

· Iterating through the collection on each objects

Important interfaces of the Collections framework and their concrete classes:

1. Interfaces:

· Collection

· List

· Queue

· Set

· Map

· Navigable Set

· Navigable Map

· Sorted Set

· Sorted Map

2. Concrete implementation of classes

Concrete classes from interfaces

Each and every collection doesn’t implement the Collection interface. Utilities and maps have separate super classes. The following image will display the hierarchy structure of the Collections framework.

The hierarchy of Collection interface
The hierarchy of Object class
The hierarchy of Map interface
Index of the above figures

Collections can be Lists, Queues, Maps or sets. These data structures can be sorted/unsorted or ordered/unordered.

Any above mentioned collections can be of the following three states:

1> Unsorted and unordered

2> Unsorted and ordered

3> Sorted and ordered

But it can never be sorted and unordered. Because sorted is a specific type of ordering. When an object is sorted by some parameter it is known as sorted and so automatically it gets ordered in a particular fashion.

Ordered:

· An ordered collections can be iterated in a non-random specific order. Generally by the index of insertion.

· Hastable though internally maintains order is not an ordered collection as multiple calls of iteration can results in different outputs. Though in LinkedHashSet maintains order by insertion. The last element inserted is the last one.

· ArraryList is strictly ordered by the index of the object inserted.

· Some data structures uses natural ordering (like alphabetically or numerically) and they are also sorted.

Sorted:

· A sorted collection is a collection which maintains a order of the objects by some rule or rules known as sorted-rules.

· A sorted collection has nothing to do with the indexes, or the time of insertion, deletion or the position where it is inserted.

· A sorted collection depends on some parameter(s) of the object, to arrange them in an ordered way. It is done on the properties of the object itself.

· Sorting can be done using natural order mostly, but they can also be customized depending upon the requirements.

Now let us have some idea on the different collections used in Java.

1>List Interface:

· List are ordered collections maintained by indexes. Any list is maintained by indexes. List has a set of index based methods, which are not available in non-list collections.

· The list methods can get, add, remove, and find the index of an object on the basis of indexes specified when specific method is invoked. If no index is mentioned the object gets added to the end of the list.

· There are 3 list implementations: ArrayList, LinkedList and Vector (rarely used now a days, as thread safe coding has imporved).

ArrayList:

ArrayList is a kind of modern, upgraded and sophisticated array. They have many similarities with arrays and many more features.

· Array list are ordered collections with their indexes naturally but they are not sorted, unless explicitly mentioned.

· When an array is declared the size needs to be mentioned and it can’t grow more than that. ArrayList is kind of growing arrays. While initializing, an ArrayList does not need to specify the size of it. As object gets inserted the size grows and if removed the size decreases by itself.

· ArrayList is faster in iteration and also faster in accessing objects randomly using indexes of course.(it is a list after all)

· With comparison to LinkedList, ArrayList are faster for iteration, but LinkedList performs better in case of insertion.

LinkedList:

· LinkedList are ordered collections with their indexes naturally but they are not sorted, unless explicitly mentioned.

· LinkedList is very similar to ArrayList but the objects are doubly linked. So LinkedList can be traversed from beginning to end and vice versa. Same way new objects can be added or deleted.

· Because of being doubly linked, it is a good option for implementing other data structures like stack or queue.

· With comparison to ArrayList it is faster in adding an object but iteration might be relatively slower.

Vector: (rarely used now a days)

· An old collection, since the inception of Java

· Works same as ArrayList but all methods are synchronized and thread safe and thus slower.

· As thread safe coding can be done in other ways, vector is not much is used now a days.

2>Set Interface:

A set is an interface where only unique objects are allowed to store. No duplication on objects are allowed. The equals(Object o) determines two objects, and if same object is tried to add to it, it won’t allow.

HashSet:

· A HashSet is an unordered and unsorted collection. In different execution of iteration it might give different outputs.

· HashSet is used when there is no need of any orderly addition on objects and duplicity should be prevented.

· HashSet uses hashcode code of objects which in turn checks the equals(Object o) method to while inserting an object. If same object is already there in the set, the insertion is avoided.

LinkedHashSet:

· LinkedHashSet is an ordered version of HashSet.

· It maintains a doubly-linked list across all elements.

· Iteration through HashSet is unpredictable but as LinkedHashSet is ordered iteration is maintained in an orderly fashion. So it is recommended to use when Map is required to iterate in orderly fashion.

Both the above collection requires to override the hashCode() method properly. Map use hascodes of objects to determine delicacy. If the default Object#hashCode() method is used meaningfully equal objects may get added, excluding the main objective of set to keep only unique objects and allows no duplicity.

TreeSet:

· TreeeSet is one of the two sorted collections, the other being TreeMap.

· Guarantees that the elements inserted will be in the order of ascending.

· Sorting is done mostly using the natural sorting order

· Sorting can be implemented in custom order as well via the constructor that lets the developer to set the own rule of sorting.

3>Map Interface:

Map implements with unique identifiers but not by unique objects. Unlike Set maps can have duplicate values, but each value will have an unique id. Map uses key to get the objects or the value. It uses equals(Object o) method to check whether two pair of keys are equal or not.

HashMap:

· HashMap is an unsorted and unordered data structure.

· HashMap is useful when the iteration is not required in a particular order.

· Objects are stored based on their hashcodes.

· One null key is allowed, but multiple null values are allowed inside a HashMap

Hashtable:

· An old collection of Java, since its inception

· Works exactly like HashMap, but the methods are synchronized and thread-safe.

· No null key or values are allowed in a Hashtable

LinkedHashMap:

· LinkedHashMap is ordered collection. Its order is maintained by the indexes.

· Slower in insertion and deletion than HashMap, but performs faster iteration.

TreeMap:

· TreeMap is one of the two sorted collection, the other being TreeSet.

· Sorted order is maintained by implementing the natural sorted order.

· Custom sort-order can be applied via the constructor, passing a Comparable or Comparator.

4>Queue Interface:

Queue is a collection used to keep a list of objects to be done one by ne or to hold objects of “to-dos”. Typically queues maintains FIFO (First in, First out) rule but this can be customized using priority queue.

PriorityQueue:

· Basic queue collections can be handled using LinkedList which generally uses FIFO rule.

· Priority queue is used to create a priority parameter among the objects that will create a “priority-in, priority-out” queue sequence as opposed to the normal FIFO.

· PriorityQueue objects are ordered by natural ordering, yet they can be customized by passing custom Comparator. In either case the ordering is represented by their priority.

Comparable and Comparator interfaces:

Sorting of data is one of them most important task of collections. Data in collections can be sorted or ordered in different ways. For efficient and custom sorting we use can use either Comparable or Comparator interfaces. These interfaces offers ways to compare two objects and sort them with respect to their parameters mentioned. The interfaces are used in Arrays.sort() and Collections.sort() to sort objects.

Comparable

A comparable object is efficient of comparing it’s own object with another. The class itself must implement the java.lang.Comparable interface in order to be able to compare its instances. Objects can be sorted using single argument sort() method.

Comparator

A comparator object is in charge of comparing two different objects. The class is not comparing its own instances, but with some other class’s instances. A new class needs to be formed for which implements Comparator. This comparator class must implement the java.util.Comparator interface. Objects can be sorted by passing both the list to be sorted and the comparator object.

Let us see an example of natural sorting. Let us run the below program and check the output.

public class SortingPracticeMain {
public static void main(String[] args) {

ArrayList<String> arrayList = new ArrayList<>();

arrayList.add("Sourav Ganguly");
arrayList.add("Sachin Tendulkar");
arrayList.add("Rahul Dravid");
arrayList.add("Zaheer Khan");
arrayList.add("Yuvraj Singh");
arrayList.add("Harbhajan Singh");
arrayList.add("Javagal Srinath");
arrayList.add("Ashish Nehra");
arrayList.add("Virender Sehwag");
arrayList.add("Ajit Agarkar");
arrayList.add("Mohammad Kaif");

System.out.println("Unsorted list: " + arrayList);

Collections.sort(arrayList);

System.out.println("Sorted list: " + arrayList);
}
}

The output is as follows:

Unsorted list: [Sourav Ganguly, Sachin Tendulkar, Rahul Dravid, Zaheer Khan, Yuvraj Singh, Harbhajan Singh, Javagal Srinath, Ashish Nehra, Virender Sehwag, Ajit Agarkar]

Sorted list: [Ajit Agarkar, Ashish Nehra, Harbhajan Singh, Javagal Srinath, Rahul Dravid, Sachin Tendulkar, Sourav Ganguly, Virender Sehwag, Yuvraj Singh, Zaheer Khan]

Here we can see the method Collection.sort() has used natural sorting i.e. sorted the elements in alphabetical order. Similar type of natural sorting also happens for integers.

But what if we want to tweak the code and add more parameter apart from just name or numbers like ids?

Like the following example, if we have a class which will take players name, his skill, jersey number and matches played before a certain event (here Cricket WC 2003) Collections.sort() method will fail. Rather it will give compilation error.

Let us check the code below:

public class SortingCustomMain {
List<PlayerInfo> infos = new ArrayList<>();
infos = populateList();

System.out.println("Unsorted player info list: ");

for(PlayerInfo info: infos)
System.out.print(info.getPlayerName() + ", ");

Collections.sort(infos);

System.out.println("\n");
System.out.println("Sorted player info list: ");

for(PlayerInfo info: infos)
System.out.print(info.getPlayerName() + ", ");

}

private static void populateList(){
PlayerInfo playerInfo1 = new PlayerInfo("Sourav Ganguly", "batsman", "Captain",24, 218);
PlayerInfo playerInfo2 = new PlayerInfo("Ajit Agarkar", "bowler", "Player",9, 110);
PlayerInfo playerInfo3 = new PlayerInfo("Rahul Dravid", "batsman/wk", "Player",5, 196);
PlayerInfo playerInfo4 = new PlayerInfo("Mohammad Kaif", "batsman", "Player",11, 39);
PlayerInfo playerInfo5 = new PlayerInfo("Zaheer Khan", "bowler", "Player",34, 56);
PlayerInfo playerInfo6 = new PlayerInfo("Ashish Nehra", "bowler", "Player",64, 30);
PlayerInfo playerInfo7 = new PlayerInfo("Virender Sehwag", "batsman", "Player",44, 57);
PlayerInfo playerInfo8 = new PlayerInfo("Harbhajan Singh", "bowler", "Player",3, 63);
PlayerInfo playerInfo9 = new PlayerInfo("Yuvraj Singh", "all-rounder", "Player",12, 59);
PlayerInfo playerInfo10 = new PlayerInfo("Javagal Srinath", "batsman", "Player",7, 229);
PlayerInfo playerInfo11 = new PlayerInfo("Sachin Tendulkar", "batsman", "Player",10, 303);

playerInfoArrayList.add(playerInfo1);
playerInfoArrayList.add(playerInfo2);
playerInfoArrayList.add(playerInfo3);
playerInfoArrayList.add(playerInfo4);
playerInfoArrayList.add(playerInfo5);
playerInfoArrayList.add(playerInfo6);
playerInfoArrayList.add(playerInfo7);
playerInfoArrayList.add(playerInfo8);
playerInfoArrayList.add(playerInfo9);
playerInfoArrayList.add(playerInfo10);
playerInfoArrayList.add(playerInfo11);
}
}

The PlayerInfo Class:

public class PlayerInfo {

private String playerName;
private String playingSkill;
private String playingRole;
private int jerseyNumber;
private int ODIsplayed;


public PlayerInfo(String playerName, String playingSkill, String playingRole, int jerseyNumber, int ODIsplayed) {
this.playerName = playerName;
this.playingSkill = playingSkill;
this.playingRole = playingRole;
this.jerseyNumber = jerseyNumber;
this.ODIsplayed = ODIsplayed;
}
}

In this case the Collection.sort(list) line will give compilation error or in older Java version it might give runtime exception in the main class and it will not let the program run.

Collections has a single argument sort method it can take lists, but not lists of PlayerInfo types. (Why? We will find soon.). There is an argument mismatch here. The java doc in Collections.sort method says that it takes list arguments and which must implements Comparable interface. Thankfully in case of String class Comparable is already implemented and so we can use String lists like in the first example. The PlayerInfo class has no implementation of Comparable interface. Here the compiler does not understand how to sort or which parameter to use to order the items. Natural sorting is not possible here. If we use Comparable or Comparator interface our code should work and also it can be customized.

Both the overridden methods returns integer and in same way. if,
Negative: this object < other object
Zero: this object == other object
Positive: this object > other object

For example:

The Comparable interface: Needs to override compareTo(Object o) method.

public class SampleComparable implements Comparable<PlayerInfo> {
@Override
public int compareTo(PlayerInfo o) {
return o.getPlayerName().compareTo(o.getPlayerName());
}
}

Comparable Interface: Needs to override the compare(Object o1, Object o2) method.

public class SampleComparator implements Comparator<PlayerInfo> {
@Override
public int compare(PlayerInfo o1, PlayerInfo o2) {
return o1.getPlayerName().compareTo(o2.getPlayerName());
}
@Override
public int compareTo(PlayerInfo o) {
return playingRole.compareTo(o.playingRole);
}

public String getPlayerName() {
return playerName;
}

public String getPlayingSkill() {
return playingSkill;
}

public String getPlayingRole() {
return playingRole;
}

public int getODIsplayed() {
return ODIsplayed;
}
}

By switching the arguments in the invocation of the method we can get inverted results.

Implementing Comparable:

Now we can implement Comparable interface to the PlayerInfo class and override the compareTo(PlayerInfo o) method and invoke the Collections.sort(Object o) method without any error. Let us do it and check the output.

The new PlayerInfo class:

public class PlayerInfo implements Comparable<PlayerInfo>{

private String playerName;
private String playingSkill;
private String playingRole;
private int jerseyNumber;
private int ODIsplayed;


public PlayerInfo(String playerName, String playingSkill, String playingRole, int jerseyNumber, int ODIsplayed) {
this.playerName = playerName;
this.playingSkill = playingSkill;
this.playingRole = playingRole;
this.jerseyNumber = jerseyNumber;
this.ODIsplayed = ODIsplayed;
}

public String getPlayerName() {
return playerName;
}

public String getPlayingSkill() {
return playingSkill;
}

public String getPlayingRole() {
return playingRole;
}

public int getODIsplayed() {
return ODIsplayed;
}

@Override
public int compareTo(PlayerInfo o) {
return playingRole.compareTo(o.playingRole);
}
}

If we run the same program again we get the output as:

Unsorted player info list:
Sourav Ganguly, Ajit Agarkar, Rahul Dravid, Mohammad Kaif, Zaheer Khan, Ashish Nehra, Virender Sehwag, Harbhajan Singh, Yuvraj Singh, Javagal Srinath, Sachin Tendulkar,

Sorted player info list:
Ajit Agarkar, Ashish Nehra, Harbhajan Singh, Javagal Srinath, Mohammad Kaif, Rahul Dravid, Sachin Tendulkar, Sourav Ganguly, Virender Sehwag, Yuvraj Singh, Zaheer Khan,

Now the Collections.sort objects get to know how to compare objects and make the order.

Comparator Interface:

Comparator operator gives the capability to sort a collection in a variety of ways. In Comparable method the class needs to implement the interface. But in Comparator a separate class needs to implement the method which can be passed to the overloaded method of Collection.sort(), which takes both a list and a comparator object. The sort method uses the comparator instance to sort out the given list.

Let us check the following example in which we will consider the older PlayerInfo class without implementing Comparable class. Here we will use Comparator interface in the program which will show the captain’s name first, then the batsmen and then the bowlers. The batsmen and bowlers will also be sorted by the number of matches they played till now and then if not possible in alphabetically.

Here is how we will write our code:

The Main Class: (Usage of comparable interface removed and Comparator interface usage added)

public class SortingCustomMain {

public static void main(String[] args) {
List<PlayerInfo> originalList;
List<PlayerInfo> infos = new ArrayList<>();
SampleComparator comparator = new SampleComparator();

originalList = populateList();
infos.addAll(originalList);

System.out.println("Unsorted player info list: ");

for (PlayerInfo info : infos)
System.out.print(info.getPlayerName() + ", ");

List<PlayerInfo> infos2 = originalList;

SampleComparator2 comparator2 = new SampleComparator2();
Collections.sort(infos2, comparator2);

System.out.println("\n");
System.out.println("Sorted player info list using comparator: ");

//Didn't used enhanced loop to add (C) before captain
for(int i = 0; i< infos2.size(); i++) {
if(i == 0 ) {
System.out.print(infos2.get(i).getPlayerName() + (" (C) ") + ", ");
}
else if(infos2.get(i).getPlayingSkill().contains(Constants.WICKET_KEEPER)){
System.out.print(infos2.get(i).getPlayerName() + (" (WK) ") + ", ");
}else {
System.out.print(infos2.get(i).getPlayerName() + ", ");
}
}
}


private static List<PlayerInfo> populateList() {
List<PlayerInfo> playerInfoArrayList = new ArrayList<>();
PlayerInfo playerInfo1 = new PlayerInfo("Sourav Ganguly", Constants.BATSMAN, "Captain", 24, 218);
PlayerInfo playerInfo2 = new PlayerInfo("Ajit Agarkar", Constants.BOWLER, "Player", 9, 110);
PlayerInfo playerInfo3 = new PlayerInfo("Rahul Dravid", Constants.WICKET_KEEPER, "Player", 5, 96);
PlayerInfo playerInfo4 = new PlayerInfo("Mohammad Kaif", Constants.BATSMAN, "Player", 11, 39);
PlayerInfo playerInfo5 = new PlayerInfo("Zaheer Khan", Constants.BOWLER, "Player", 34, 6);
PlayerInfo playerInfo6 = new PlayerInfo("Ashish Nehra", Constants.BOWLER, "Player", 64, 30);
PlayerInfo playerInfo7 = new PlayerInfo("Virender Sehwag", Constants.BATSMAN, "Player", 44, 57);
PlayerInfo playerInfo8 = new PlayerInfo("Harbhajan Singh", Constants.BOWLER, "Player", 3, 63);
PlayerInfo playerInfo9 = new PlayerInfo("Yuvraj Singh", Constants.BATSMAN, "Player", 12, 59);
PlayerInfo playerInfo10 = new PlayerInfo("Javagal Srinath", Constants.BOWLER, "Player", 7, 229);
PlayerInfo playerInfo11 = new PlayerInfo("Sachin Tendulkar", Constants.BATSMAN, "Player", 10, 303);

playerInfoArrayList.add(playerInfo1);
playerInfoArrayList.add(playerInfo2);
playerInfoArrayList.add(playerInfo3);
playerInfoArrayList.add(playerInfo4);
playerInfoArrayList.add(playerInfo5);
playerInfoArrayList.add(playerInfo6);
playerInfoArrayList.add(playerInfo7);
playerInfoArrayList.add(playerInfo8);
playerInfoArrayList.add(playerInfo9);
playerInfoArrayList.add(playerInfo10);
playerInfoArrayList.add(playerInfo11);

return playerInfoArrayList;
}

}

The PlayerInfo class: (same as mentioned before)

public class PlayerInfo {

private String playerName;
private String playingSkill;
private String playingRole;
private int jerseyNumber;
private Integer ODIsplayed;


public PlayerInfo(String playerName, String playingSkill, String playingRole, int jerseyNumber, Integer ODIsplayed) {
this.playerName = playerName;
this.playingSkill = playingSkill;
this.playingRole = playingRole;
this.jerseyNumber = jerseyNumber;
this.ODIsplayed = ODIsplayed;
}

public String getPlayerName() {
return playerName;
}

public String getPlayingSkill() {
return playingSkill;
}

public String getPlayingRole() {
return playingRole;
}

public Integer getODIsplayed() {
return ODIsplayed;
}

@Override
public String toString() {
return "PlayerInfo{" + "playerName='" + playerName + '\'' + ", playingSkill='" + playingSkill + '\'' + ", playingRole='" + playingRole + '\'' + ", jerseyNumber=" + jerseyNumber + ", ODIsplayed='" + ODIsplayed + '\'' + '}';
}
}

The Comparator Class:

public class SampleComparator2 implements Comparator<PlayerInfo> {
@Override
public int compare(PlayerInfo o1, PlayerInfo o2) {
int playingSkill = o1.getPlayingSkill().compareTo(o2.getPlayingSkill());

if (o1.getPlayingRole().contains(Constants.CAPTAIN) || o2.getPlayingRole().contains("Captain")) {
//Make sure captain's name goes to top
return Integer.MAX_VALUE;
}
if (playingSkill == 0) {
if (o1.getPlayingSkill().contains(Constants.BATSMAN) && o2.getPlayingSkill().contains(Constants.BATSMAN)) {
//To make sure plyers who played more matches comes before the others we changed the comparison in reverse
return o2.getODIsplayed().compareTo(o1.getODIsplayed());
} else if ((o1.getPlayingSkill().contains(Constants.BOWLER) && o2.getPlayingSkill().contains(Constants.BOWLER))) {
//To make sure plyers who played more matches comes before the others we changed the comparison in reverse
return o2.getODIsplayed().compareTo(o1.getODIsplayed());
}else if(o1.getPlayingSkill().contains(Constants.WICKET_KEEPER) && o2.getPlayingSkill().contains("wk")){
return o2.getODIsplayed().compareTo(o1.getODIsplayed());
}
} else {
return o1.getPlayingSkill().compareTo(o2.getPlayingSkill()); //Batsman will come before bowlers's list. No offence to the bowlers, they win matches.
}
return 0;
}
}

The Contestants’ class:

public class Constants {

public static final String CAPTAIN ="captain";
public static final String BATSMAN = "batsman";
public static final String BOWLER = "bowler";
public static final String WICKET_KEEPER = "batsman/" +
"wicket_kepper";

}

Let use see the output when this program runs:

Unsorted player info list:
Sourav Ganguly, Ajit Agarkar, Rahul Dravid, Mohammad Kaif, Zaheer Khan, Ashish Nehra, Virender Sehwag, Harbhajan Singh, Yuvraj Singh, Javagal Srinath, Sachin Tendulkar,

Sorted player info list using comparator:
Sourav Ganguly ( C ), Sachin Tendulkar, Yuvraj Singh, Virender Sehwag, Mohammad Kaif, Rahul Dravid (WK) , Javagal Srinath, Ajit Agarkar, Harbhajan Singh, Ashish Nehra, Zaheer Khan,

Thus, here we get a list of unordered unsorted players’ list. But we ca use our own customized Comparator interface and make a list sorted accordingly.

Basic differences between Comparable and Comparator interfaces

Array and ArrayList:

Both Array and ArrayList are similar but they have lots of differences. ArraList can be said a modern, open-minded, sophisticated implementation of Array. But it is also exactly not like that. In collections we use both of them as per requirement. Both are widely used in different programs. Lets us see a little bit about them.

Arrays are data structures which keeps a series of same data types like strings or integers. So, it is also important to order or sort the data in an array.

Data in an Array can be sorted by invoking either of the single argument or double argument sort() method of the Array class. The single argument takes the array t sort as a parameter and the double argument along with the array also takes and Comparator object to customize the sorting.

1> Arrays.sort(arrayToSort)

2> Arrays.sort(arrayToSort, comparatorObj)

Also since arrays can contain a lot of primitives, the sort method of Arrays class is overloaded a lot of times to support each of them. The sort() method of both the Arrays and Collections classes are static. So they work on the object passed and invoke on them directly. Of course the elements to be sorted must be mutually comparable.

Searching Arrays and Collections:

Collections contains series of data which needs to accessed on time. So searching them is one of the most important task and both Arrays and Collections allows to do that. While searching few rules are followed, they are mentioned below:

1. BinarySeacrh method is used for searching

2. If the search result is succcessful it returns the index of the object searched

3. If the searchresult is unsuccessful if returns an integer value whichrepresents the insertion point. But here is a thing for some fun.

· Returns a negative value as the insertion point. This is because zero itself is a valid insertion point, generally the first element of any collections.

· The actual insertion point returned is represented as below:

( — (insertion point) — 1). Like, if the insertion point is 2 the value returned will be -3.

4..Before searching the collections must be sorted in some kind of order.

5. Unsorted collections results in unpredictable search results.

6. Searching should be done following the way it was sorted. Natural order for natural sorting, comparator for customized sorting.

Conversions between Arrays and ArrayList:

To convert an Array to ArrayList Java API provides as List() method and to convert an list to Array toArray() method can be used. Its simple and very lucid.

Let us see the following code to convert and Array to ArrayList.

public class ArrayAndArrayList {

public static void main(String[] args) {

String[] stringsArray = new String[5];

stringsArray[0] = "Pulp Fiction";
stringsArray[1] = "Intersteller";
stringsArray[2] = "Fight Club";
stringsArray[3] = "Se7en";
stringsArray[4] = "The Dark Knight";
for(int i = 0; i < stringsArray.length; i ++) {
System.out.println("Array elements: " + stringsArray[i]);
}

List stringArrayList = Arrays.asList(stringsArray); //This line changes the array to a list item
System.out.print("\n");
System.out.println("Array is now changed to a list!");
System.out.println("Size of ArrayList asList(): " + stringArrayList.size());

System.out.print("\n");

System.out.println("List value at index 3: " + stringArrayList.get(3));

System.out.print("\n");


System.out.println("Changing array");

stringsArray[3] = "The Wolf of wall street";
System.out.println("List value at index 3: " + stringArrayList.get(3));

System.out.print("\n");

System.out.println("Changing list");
stringArrayList.set(3, "Gangs of New York");

System.out.println("List value at index 3: " + stringArrayList.get(3));
System.out.println("Array value at index 3: " + stringsArray[3]);
System.out.print("\n");


System.out.println("Size of Array: " + stringsArray.length);
System.out.println("Size of ArrayList: " + stringArrayList.size());
}
}

The output will be as follows:

Array elements: Pulp Fiction
Array elements: Intersteller
Array elements: Fight Club
Array elements: Se7en
Array elements: The Dark Knight

Array is now changed to a list!
Size of ArrayList asList(): 5

List value at index 3: Se7en

Changing array
List value at index 3: The Wolf of wall street

Changing list
List value at index 3: Gangs of New York
Array value at index 3: Gangs of New York

Size of Array: 5
Size of ArrayList: 5

But, but, but, have you noticed one interesting thing here. Though the array has been changed to a list and each pf them works nicely with their own methods, if we change value of any one of collections, the other one also got changed. Though at index 3 in the list the value was “se7en” as we changed the only the array , the list also got updated. Even though both are different collections, they are joined as a hip.

Let us now change an ArrayList to an Array.

public class ArrayListAndArray {

public static void main(String[] args) {

List<Integer> integerList = new ArrayList<Integer>();
for(int i = 0; i < 10; i++){
integerList.add(i);
}

Object[] objects = integerList.toArray(); //Creates and array of objects
System.out.println("Object array length: " + objects.length);

Integer[] ints = new Integer[10];
ints = integerList.toArray(ints); //Creates and array of integer
System.out.println("Integer array length: " + objects.length);

System.out.print("\n");

System.out.println("Integer array list at index: " + integerList.get(3));

ints[3] = 33;
System.out.println("Integer array at index 3: " + ints[3]);
System.out.println("Integer ArrayList at index 3: " + integerList.get(3));

}
}

The output will be as followed:

Object array length: 10
Integer array length: 10

Integer array list at index: 3
Integer array at index 3: 33
Integer ArrayList at index 3: 3

Here, we can see, even though the value of the array was changed it didn’t affected the value of list. The collections are not conjoined here. But pne thing to notice here, the method toArray() comes in two flavors:

1> It can create a new Object array

2> It can create an array of the type whose destination has been sent. (Like here we created an int[] array)

Use of Lists:

As mentioned earlier lists are used to store data in an ordered manner. Lists can be used for FIFO, keep track of objects, their visits by indexes or they may be used with priority value. List can be traversed using loop, enhanced loop of the good old iterator method. Lists are unordered and unsorted unless specifically made like that. As normal loop methods and enhanced loop are quite common we will have an example of iteration here below.

The main class:

public class UsingList {
public static void main(String[] args) {
List<SampleClass> stringList = new ArrayList<>();

SampleClass sampleClass1 = new SampleClass("Satyajit Ray");
SampleClass sampleClass2 = new SampleClass("Ritwik Ghatak");
SampleClass sampleClass3 = new SampleClass("Mrinal Sen");
SampleClass sampleClass4 = new SampleClass("Rituporno Ghosh");
SampleClass sampleClass5 = new SampleClass("Tapan Sinha");

stringList.add(sampleClass1);
stringList.add(sampleClass2);
stringList.add(sampleClass3);
stringList.add(sampleClass4);
stringList.add(sampleClass5);

Iterator<SampleClass> iterator = stringList.iterator();
while (iterator.hasNext()){
System.out.println("Using iterator on list of SampleClass Objects: " + iterator.next().getName());
}
}
}

The Sample Class:

public class SampleClass {

private String name;

public String getName() {
return name;
}

public SampleClass(String name) {

this.name = name;
}
}

The out of the program will be:

Using iterator on list of SampleClass Objects: Satyajit Ray
Using iterator on list of SampleClass Objects: Ritwik Ghatak
Using iterator on list of SampleClass Objects: Mrinal Sen
Using iterator on list of SampleClass Objects: Rituporno Ghosh
Using iterator on list of SampleClass Objects: Tapan Sinha

As we can see, we can traverse a list and find ts objects. Two things I feel should be mentioned here. The two methods of the Iterator class.

1> boolean hasNext() -> It returns true if the collection has an object on the next index on which it is pointing. It do not to the next element.

2> Object next() -> This object returns the value of the index on which it is pointed.

Using Sets:

Set does not accept duplicate values. But HashSet might have different type of Objects. But since TreeSet is one of the only two sorted collection, it accepts only one type of data type at each instance.

if we run the following porgram:

public static void main(String[] args) {
Set set = new HashSet();

boolean[] b = new boolean[5];

b[0] = set.add("a");
b[1] = set.add(new Integer(5));
b[2] = set.add("b");
b[3] = set.add("a");
b[4] = set.add("c");

for(int i = 0; i < b.length; i ++){
System.out.println("Boolean value returned: " + b[i]);
}

System.out.println("\n");

for(Object o : set){
System.out.println("Objects in set: " + o);
}

}
}

We will get an output like this:

Boolean value returned: true
Boolean value returned: false
Boolean value returned: true
Boolean value returned: false
Boolean value returned: true
Objects in set: a
Objects in set: b
Objects in set: c

But the below program will throw a runtime exception:

public static void main(String[] args) {
Set set = new TreeSet();
boolean[] b = new boolean[5];

b[0] = set.add("a");
b[1] = set.add(new Integer(5)); // If TreeSet is used all objects must be mutually comparable
b[2] = set.add("b");
b[3] = set.add("a");
b[4] = set.add("c");

for(int i = 0; i < b.length; i ++){
System.out.println("Boolean value returned: " + b[i]);
}

System.out.println("\n");

for(Object o : set){
System.out.println("Objects in set: " + o);
}

}
}

Output:

Exception in thread “main” java.lang.ClassCastException: java.lang.String cannot be cast to java.lang.Integer
at java.lang.Integer.compareTo(Integer.java:52)
at java.util.TreeMap.put(TreeMap.java:568)
at java.util.TreeSet.add(TreeSet.java:255)
at com.setpractice.main.SetPracticeMain.main(SetPracticeMain.java:14)

Using Maps:

Maps are sued to store values with unique IDs. Thus a map needs to override the hashCode() as well as equals(Object obj) to retrieve an object. The more efficiently an hashcode is implemented the faster the object will get searched. If the value in a map is changed the search will return null. As even though the key might be same, it will fail in the equals(0bject obj) method. Even if the search algorithm finds the proper(previous or current) hash key, it won’t find the object using equals(Object o).

Searching TreeSet and TreeMap:

TreeSet and TreeMap are only two collections which are always sorted and hence ordered. Let us check out the following code. This will give us some idea on the collections, their natural sorting process and an idea on some of their important methods: (We will use TreeSet, but same will happen for TreeMap, only in set we cannot have duplicate values, but TreeMap will duplicates)

public class TreeMapAndTreeSetMain {

private static int[] times = {800, 600, 100, 300, 500, 200, 700, 100, 900, 400, 650, 750};

public static void main(String[] args) {
TreeSet<Integer> integerTreeSet = new TreeSet<>();
for (int i = 0; i < times.length; i++) {
integerTreeSet.add(times[i]);
}

System.out.println("Original values: " + integerTreeSet);
System.out.print("\n");
for (Integer treeSet : integerTreeSet)
System.out.println("TreeSet values:" + treeSet);


System.out.print("\n");

System.out.println("TreeSet First object: " + integerTreeSet.first());
System.out.println("TreeSet Last object: " + integerTreeSet.last());

System.out.print("\n");

//Floor and ceiling methods
System.out.println("Tree Set ceiling: " + integerTreeSet.ceiling(650));
System.out.println("Tree Set floor: " + integerTreeSet.floor(300));

System.out.print("\n");

//higher and lower
System.out.println("Tree Set higher: " + integerTreeSet.higher(650));
System.out.println("Tree Set lower: " + integerTreeSet.lower(300));

System.out.print("\n");
//Descending iterator
Iterator<Integer> descendingIterator = integerTreeSet.descendingIterator();
System.out.print("Using descendingIterator: ");
while (descendingIterator.hasNext()) {
System.out.print(descendingIterator.next() + ", ");
}

System.out.println("\n");

//Using descending set
System.out.println("Using descending set: " + integerTreeSet.descendingSet());

System.out.println("\n");

//Using polling
System.out.println("TreeSet poll first: " + integerTreeSet.pollFirst());
System.out.println("Tree Set after poll first: " + integerTreeSet);

System.out.println("TreeSet poll first: " + integerTreeSet.pollLast());
System.out.println("Tree Set after poll last: " + integerTreeSet);

}
}

The output of the above program will be:

Original values: [100, 200, 300, 400, 500, 600, 650, 700, 750, 800, 900]

TreeSet values:100
TreeSet values:200
TreeSet values:300
TreeSet values:400
TreeSet values:500
TreeSet values:600
TreeSet values:650
TreeSet values:700
TreeSet values:750
TreeSet values:800
TreeSet values:900

TreeSet First object: 100
TreeSet Last object: 900

Tree Set ceiling: 650
Tree Set floor: 300

Tree Set higher: 700
Tree Set lower: 200

Using descending Iterator: 900, 800, 750, 700, 650, 600, 500, 400, 300, 200, 100,

Using descending set: [900, 800, 750, 700, 650, 600, 500, 400, 300, 200, 100]

TreeSet poll first: 100
Tree Set after poll first: [200, 300, 400, 500, 600, 650, 700, 750, 800, 900]
TreeSet poll first: 900
Tree Set after poll last: [200, 300, 400, 500, 600, 650, 700, 750, 800]

We can see a lot of things here.

1> Even though the original values were not sorted or ordered, the TreeSet by itself used natural sorting and the output came in sorted order

2> We can search and find an object in the collection. Like here we used to display the first and the last object.

3> We used subSet, which took a min value and a max value and returned a subset of the original TreeSet.

4>We used ceiling/floor and higher/lower method with same values but got different outputs. Why? As lower gives the elements less than the given element, whereas floor gives less than or equal to the value of the given element. Similarly higher gives the value grater thean the given element and ceiling returns equal or greater than the given value, whichever is available.

5> We used descending order order iterator. As the name suggest it iterates over the values but in the descending order and returns them.

6> We also used pollFirst() and pollLast() method. Both them returned us the required value but at the same time they also remove the values from the Tree.

TreeMap has similar methods works exactly same, only differences are in some cases the names of them.

Backed Collection:

Some classes in java.util package supports the concept of “Backed Collection”. Backed collection are not the copy of of the original collection, but a part of it. We can check the below code and get an idea of it.

public class BackedMain {

public static void main(String[] args) {
TreeMap<String, String> stringTreeMap = new TreeMap<>();
stringTreeMap.put("a", "ant");
stringTreeMap.put("b", "bat");
stringTreeMap.put("c", "cat");
stringTreeMap.put("e", "elephant");
stringTreeMap.put("g", "goat");
stringTreeMap.put("h", "hippopotamus");
stringTreeMap.put("i", "igloo");
stringTreeMap.put("j", "jungle");
stringTreeMap.put("k", "karate");

SortedMap<String, String> stringSortedMap = stringTreeMap.subMap("b", "h");

System.out.println("Original map: " + stringTreeMap);
System.out.println("Sorted submap: " + stringSortedMap);

System.out.println("\n");

stringTreeMap.put("d", "dog");
stringTreeMap.put("f", "fling");

System.out.println("Original map after addition: " + stringTreeMap);
System.out.println("Sorted submap after addition: " + stringSortedMap);

System.out.println("\n");
stringTreeMap.pollFirstEntry();
System.out.println("Original map after pollFirstEntry: " + stringTreeMap);
System.out.println("Sorted submap after pollFirstEntry on original map: " + stringSortedMap);

System.out.println("\n");

stringSortedMap = stringSortedMap.headMap("d");
System.out.println("Sorted submap after headmap(\"e\"): " + stringSortedMap);
}
}

The output of the program is:

Original map: {a=ant, b=bat, c=cat, e=elephant, g=goat, h=hippopotamus, i=igloo, j=jungle, k=karate}
Sorted submap: {b=bat, c=cat, e=elephant, g=goat}

Original map after addition: {a=ant, b=bat, c=cat, d=dog, e=elephant, f=fling, g=goat, h=hippopotamus, i=igloo, j=jungle, k=karate}
Sorted submap after addition: {b=bat, c=cat, d=dog, e=elephant, f=fling, g=goat}

Original map after pollFirstEntry: {b=bat, c=cat, d=dog, e=elephant, f=fling, g=goat, h=hippopotamus, i=igloo, j=jungle, k=karate}
Sorted submap after pollFirstEntry on original map: {b=bat, c=cat, d=dog, e=elephant, f=fling, g=goat}

Sorted submap after headmap(“e”): {b=bat, c=cat}

Here we see we have an original TreeMap and a subMap i.e. a part of it. Both displayed their respective values at first. Later we added more values to one of it and it also got reflected in the other one. Thus basically though we have two reference variables and it seems we have two collections, basically it is the same object we are referring to.

So when one object got changed the other also gives the same output as the Objects are basically same. If a new entry is added to the original which is out of range of the subset we might not see it, but if we increase the range of the subset we can see the change. Backed Collection works this way.

Hope this article gives you an idea about Java generics and collections. Feel free to comment if you have and queries or face any issues. So long, good luck and happy coding.

I am thankful to :

First of all my parents, family and friends for their unconditional support

1>Java documentation by Oracle

2> SCJP book by Kathy Sierra and Bert Bates

3> https://docs.oracle.com/javase/7/docs/api/

4> https://www.stackoverflow.com

5> https://www.quora.com

6> My employers and the projects they assigned to me.

--

--