Generics, Arrays And Collections In Java
In a previous article I introduced you to generic programming in Java. In this article we’ll go deeper and look at some problems with non-generic coding with collections, benefits of generics, and some extra fun with generics in Java.
Setting Up
Before going deeper into discussions and code, let's setup our environment. You need to have JDK (preferably the latest version) installed on your system. You can choose any code editor or any IDE you like. I am going to use the IntelliJ IDEA from JetBrains. Create a class called Gen2 with a main method inside it. So, our initial code will look as follows:
public class Gen2 {
public static void main(String[] args){
// Execution of our code starts here.
}
}
Array, Type Safety and The Restrictions
Arrays are one those data structures in Java that we cannot live without. They are type safe; if you declare an array of one type you cannot put data or objects of another type inside the array. In a typed language like Java this behavior is desired. Let's try to put a string inside an array of integers.
public class Gen2 {
public static void main(String[] args){
int[] iarr = new int[10];
iarr[0] = 1;
iarr[1] = 2;
iarr[2] = 3;
iarr[3] = "A string";
}
}
Hit compile to see the following error:
Error:(7, 19) java: incompatible types: java.lang.String cannot be converted to int
It's great news that it is type safe, but there are some problems with arrays, the biggest of which is that arrays are not dynamically extendable. You cannot put more than 10 elements in the array above. If you want to put more elements inside it then you have to create another array of a larger size and copy all of the elements from this array, and then add the extra element you want to put there.
List Is The Savior
In the collections framework of the JDK there is an interface called List. The implementation of List that we are going to use is ArrayList. ArrayList is a dynamically sizable array like data structure. You can add as many elements as you want or keep it empty. You will not need to declare its size nor resize it when you want to add more elements. Let's create an instance of the ArrayList and add elements to it.
import java.util.ArrayList;
import java.util.List;
public class Gen2 {
public static void main(String[] args){
List list = new ArrayList();
list.add("String 1");
list.add(new Integer(1));
list.add(new Object());
}
}
Try to compile and run it. It works!
The Departure Of Type Safety
In the above example we did not need to declare the size of the array list. We did not need to resize, copy array, or do any hard work to put more elements into it. That is a great advantage. But great power comes with great responsibilities and all advantages come with some disadvantages.
We had to sacrifice the type safety for additional benefits. Now we can put any type of object into the array list called list in our code. In a typed language, type safety is very important. Lack of type safety may introduce bugs and runtime exceptions. We may not be able to stay in bed on a Sunday morning due to the issues that may occur anytime due to the lack of type safety.
The Issue Of Typecasting
If we want to get elements from the list we will have to cast them to proper types before using. But why? Every object we put inside the list will be kept as an Object reference. We can get elements from the list with the help of the get() method. get() takes an integer as the index and returns an Object.
Let's try to print all the elements from list.
import java.util.ArrayList;
import java.util.List;
public class Gen2 {
public static void main(String[] args){
List list = new ArrayList();
list.add("String 1");
list.add(new Integer(1));
list.add(new Object());
for(Object o : list){
System.out.println(o);
}
}
}
Outputs:
String 1
1
java.lang.Object@4554617c
But what if we want to get the Integer inside the list and add it to number 5? Let's try that in code:
import java.util.ArrayList;
import java.util.List;
public class Gen2 {
public static void main(String[] args){
List list = new ArrayList();
list.add("String 1");
list.add(new Integer(1));
list.add(new Object());
System.out.println(
list.get(1) + 5
);
}
}
Try to compile it. There is an error at compile time:
Error:(12, 29) java: bad operand types for binary operator '+'
first type: java.lang.Object
second type: int
Notice that our java compiler informs us that the first operand of the + operator is of type Object.
To solve this problem, we can cast the result from the get() method.
System.out.println(
(Integer)list.get(1) + 5
);
Compile and run to see the result as 6. So, typecasting saved the day.
But we have objects of different types inside the list. We have to remember every element's index, its type and cast it accordingly. But, we use array like data structures not to remember those things. That means we have two problem now:
- Lack of type safety
- Unnecessary and undesired casting
The Real Hero: Generics
We need to have dynamically sized array like data structure, and no type casting. We can get that benefit from the generic version of List or ArrayList. With the help of diamond operator we have to tell which type of objects the array list can contain.
import java.util.ArrayList;
import java.util.List;
public class Gen2 {
public static void main(String[] args){
List<String> list = new ArrayList<String>();
list.add("String 1");
list.add(new Integer(1));
list.add(new Object());
}
}
Try to compile the code. It won't compile and throw some errors like below.
Error:(8, 13) java: no suitable method found for add(java.lang.Integer)
method java.util.Collection.add(java.lang.String) is not applicable
(argument mismatch; java.lang.Integer cannot be converted to java.lang.String)
method java.util.List.add(java.lang.String) is not applicable
(argument mismatch; java.lang.Integer cannot be converted to java.lang.String)
Error:(9, 13) java: no suitable method found for add(java.lang.Object)
method java.util.Collection.add(java.lang.String) is not applicable
(argument mismatch; java.lang.Object cannot be converted to java.lang.String)
method java.util.List.add(java.lang.String) is not applicable
(argument mismatch; java.lang.Object cannot be converted to java.lang.String)
That's great. We have type safety now. Bad code must not compile.
Now, let's add only strings to the list. Then try to get elements from it with the help of the get() method without doing any type casting.
import java.util.ArrayList;
import java.util.List;
public class Gen2 {
public static void main(String[] args){
List<String> list = new ArrayList<String>();
list.add("String 1");
list.add("String 2");
list.add("String 3");
String str1 = list.get(2);
System.out.println("The third element of the list is of type String and the value of it is: " + str1);
}
}
Outputs:
The third element of the list is of type String and the value of it is: String 3
Our code compiles and runs as expected. So, now we have both type safety and no pain of type casting. We also have an array like data structure for which we do not need to declare size, neither do we need resize that manually ever.
Every type of collection can be used with generics. Try to look at the official reference guide for various collection types and practice with coding.
Conclusion
The people behind Java always try to improve the language with new and new features. Once there were no generics in Java but now we have them. Thought it may seem difficult to learn at first, once you get accustomed to them you will not be able to live without them. In future articles I will provide you more lessons on generics in Java.
Be sure to visit the homepage to find hundreds of SDKs, APIs, and libraries for Java development.
Recent Stories
Top DiscoverSDK Experts
Compare Products
Select up to three two products to compare by clicking on the compare icon () of each product.
{{compareToolModel.Error}}
{{CommentsModel.TotalCount}} Comments
Your Comment