10/180: Collections and Generics in Java
Today is day 10 of 180 days learning challenge, which I have taken to improve myself and include learning as my daily habit. In my story I always mention this first because I want to keep reminding my self why this challenge exists.
Collections
This is one of the most important topics in JAVA, understanding it well makes your understanding with JAVA code more clear. Below are few collections I am going to discuss today:
- ArrayList
- LinkedList
- HashSet
- LinkedHashSet
Collections are basically more high level data structures. Why is the need for that? Because Java primitive data types have certain limitations that like in Arrays we need to define the size of the array and it we need the same array of bigger size later in the code we have to create a new array and copy the data of existing array into that. But ArrayList solves this problem, we don’t have to define the size of it. Though we have the option to define the size but not mandatory. Initially ArrayList is of size 10 and it keeps on doubling the size when there is the need of more space required in the arrayList.
ArrayList is a resizeable array.
ArrayList words = new ArrayList(); //before Java 7 we used to define array list like this, but problem with this declaration is that it is not type specific, so it considers every value inserted to the array as object. ArrayList is collection of objects
words.add(“hello”);
words.add("there");
words.add(0);
words.add(10);
words.add(12.0);
words.add('H');
String item = words.get(0) //this line of code will give error as currently arraylist is not type specific and it will consider every thing as object. To make it correct we need to type cast this
String item = (String) words.get(0)
Now the problem with this whole code is that we should already be aware of of what is inside which index, which is not possible as this code is not type safe so it can cause many issues. But this problem has been solved by the use of generic, which we are going to discuss later over here.
ArrayList<String> words = new ArrayList<String>(); //Java is a type safe language that mean you will not get confused because of the type. And generics takes care of the type safety. Now we can only add strings to this arraylist.
LinkedList<int> numbers = new LinkedList<int>(); // This line of code will give issue as we cant add primitive data types, but can only add there respective wrappers classes.
LinkedList<Integer> number = new LinkedList<>(); //now I have kept the diamond empty as with new features added to Java, it gave us the flexibility to not write duplicate things.
numbers.add(100);
numbers.add(200);
numbers.add(45);
numbers.add(1000);//for loop to traverse through the list
for(int number : numbers){
System.out.println(number);
}
Now LinkedList and arrayList have same functions mostly, except LinkedList is a list of nodes and it has few extra methods like removeFirst(), peekFirst(), peekLast(), etc.
HashSet<Integer> values = new HashSet<Integer>();
HashSet is used to prevent duplicates. It doesn’t maintain any order, if you want that order should be maintained then you can use LinkedHashSet.
values.add(12);
values.add(43);
values.add(15);
values.add(67);
values.add(43);
for(Integer value : values){
System.out.println(value);
}
And you can see in its results that last 43 which we have added is a duplicate so it wont get added to the hashset and it won’t get displayed in the result.
Let’s create a custom object
class Animal{
String name;
Integer age;
public Animal(String name, Integer age){
this.name = name;
this.age = age;
}
//getters & setters & toString method
}HashSet<Animals> animals = new HashSet<>();
Animal animal1 = new Animal("Dog", 12);
Animal animal2 = new Animal("Cat", 8);
Animal animal3 = new Animal("Bird", 3);
Animal animal4 = new Animal("Dog", 12);
Animal animal5 = new Animal("Kangaroo", 4);
for(Animal value : animals){
System.out.println(value);
}
It will print the duplicate value. But why so, if you will check it using the equals method it will print false
System.out.println(animal1.equals(animal4));
//The result for the above line of code will be false.
To make it correct we need to override the equals method in Animal class
@Override
public boolean equals(Object obj){
if(this == obj)
return true;
if(obj == null)
return false;
if(getClass() != obj.getClass())
return false;
Animal other = (Animal) obj;
if(age != other.age)
return false;
if(name == null){
if(other.name != null)
return false;
elseif(!name.equals(other.name))
return false;
return true;
}
If now you will run the code, the .equals method will return true. But when you will run the Hashset code, it will still display the duplicate value, it is because both animal1 and animal4 have different hashcodes, so we need to override the hashcode method. And we don’t basically have to write these overridden methods, it is already generated by the IDE.
@Override
public int hashCode(){
final int prime = 31;
int result = 1;
result = prime * result + age;
result = prime * result + ((name == null) ? 0 : name.hashCode());
return result;}
And now if you will see the result, it won’t show the duplicates.
Generics
As above we have already seen in ArrayList collection, how confusing it may become if we not define the Generics. Generics takes care of the type safety. And generics are used inside the diamond brackets. <String> like this. But how we create generics, or how we make code generic available for every type of object.
Lets create a class container
public class Container{
String item1;
String item2;
//constructor
//getters
//setters}
Conatiner container = new Container(12, Hello);
int myVar = (String)container.getItem1();
//The problem with the above code is that it is not generic and to have the correct results we have to type caste. LET's create a generic class
public class Container<i1, i2>{
i1 item1;
i2 item2;
// change every were the string type as i1 and i2 respectively
}
//This is the way of declaring a generic class, that means <i1, i2> are two generic objects and we can pass any objects inplace of that as per the requirementConatiner<Integer, String> container = new Container<>(12, Hello);
//This is how we can pass the objects, if we won't pass anything, it will work but it keeps on displaying warning and it will consider everything as an Object, and to use that we need to type caste it.
The above was a generic class, now let’s see how can we create a Generic method
public static Set union(Set set1, Set set2){
Set result = new HashSet(set1);
result.addAll(set2);
return result;
}
//The above function will keep on display warnings as raw type, and will consider everything as object, but we have to make it generic one
public static Set<E> union(Set<E> set1, Set<E> set2){
Set<E> result = new HashSet<E>(set1);
result.addAll(set2);
return result;
}
//The above method will still display an error Type E is undefined, so we need to define it, to define it we just need to add it before the return type.public static <E> Set<E> union(Set<E> set1, Set<E> set2){
Set<E> result = new HashSet<E>(set1);
result.addAll(set2);
return result;
}