Basics
This section provides an introduction to generics and compares these to raw types, discussed before.
Table of contents
- Generics
- Can we assign a raw type to a generics list?
- Diamond Operator
- Can we use generics with primitive types?
- Are there cases where we cannot use generics?
Generics
Java 1.5 introduced generics. Generics allows us to provide hints to the Java compiler about the contents of our containers, such as List
or Set
to name two, as shown in the following example.
package demo;
import java.util.ArrayList;
import java.util.List;
public class App {
public static void main( final String[] args ) {
final List<String> children = new ArrayList<String>();
children.add( "Jade" );
children.add( "Aden" );
System.out.printf( "Children: %s%n", children );
}
}
In the above example, we declared a list of type String
. This means that our list can only contain String
s and we cannot put in any other type. Generics have the following advantages when compared to raw types.
Casting is not required anymore
We can retrieve items and use them without having to type cast them as shown in the following fragment.
final String firstChild = children.get( 0 );
Following is the complete example.
package demo; import java.util.ArrayList; import java.util.List; public class App { public static void main( final String[] args ) { final List<String> children = new ArrayList<String>(); children.add( "Jade" ); children.add( "Aden" ); System.out.printf( "Children: %s%n", children ); final String firstChild = children.get( 0 ); System.out.printf( "%s is the first child%n", firstChild ); } }
This is quite convenient.
Cannot add anything else but
String
s⚠ The following example does not compile!!package demo; import java.util.ArrayList; import java.util.List; public class App { public static void main( final String[] args ) { final List<String> children = new ArrayList<String>(); children.add( "Jade" ); children.add( "Aden" ); /* ⚠️ Cannot add an int to a list of type String */ children.add( 0, 7 ); System.out.printf( "Children: %s%n", children ); } }
The above example does not compile as, when we use generics, the Java compilers check and fails if the wrong type is provided, as shown next.
src/main/java/demo/App.java:14: error: incompatible types: int cannot be converted to String children.add( 0, 7 ); ^
This ensures that our list only contains the correct elements.
Can we assign a raw type to a generics list?
Yes. Consider the following example.
package demo;
import java.awt.Point;
import java.util.ArrayList;
import java.util.List;
public class App {
public static void main( final String[] args ) {
final List<String> children = createRawTypeList();
children.add( "Jade" );
children.add( "Aden" );
System.out.printf( "Children: %s%n", children );
}
public static List createRawTypeList() {
final List rawType = new ArrayList();
rawType.add( 7 );
rawType.add( new Point( 1, 2 ) );
return rawType;
}
}
The method createRawTypeList()
returns a raw type List
, which is then assigned to a List
of type String
. The above example compiles and print the following.
Children: [7, java.awt.Point[x=1,y=2], Jade, Aden]
With that said, this example produces warning as shown next,
Note: src/main/java/demo/App.java uses unchecked or unsafe operations.
Note: Recompile with -Xlint:unchecked for details.
As already discuses in the raw types section, raw types are discouraged. The above example is even worse than the previous ones. This example gives the illusion that the List
named children
only contains String
s, where it contains other types too.
Effective Java advise against usage of raw types, in Item 26: Don’t use raw types, and recommends the use of Generics, discussed next. The next item in the same chapter, Item 27: Eliminate unchecked warnings, strongly recommends getting rid of unchecked warnings too.
Diamond Operator
Java 7 provided a new operator, called the diamond operator as part of JSR 334: Small Enhancements to the JavaTM Programming Language (a.k.a project coin).
Consider the following code fragment.
final List<String> children = new ArrayList<String>();
As of Java 7, we can make use of the diamond operator, <>
, and remove the type from the initialisation, as shown next.
final List<String> children = new ArrayList<>();
Following is the complete example.
package demo;
import java.util.ArrayList;
import java.util.List;
public class App {
public static void main( final String[] args ) {
final List<String> children = new ArrayList<>();
children.add( "Jade" );
children.add( "Aden" );
System.out.printf( "Children: %s%n", children );
}
}
The compilers can use the variable declaration to determine the type of the list. In our case, the list will be of type String
and there is no need to provide it twice.
The diamond operator can be safely used anywhere where the type can be inferred from the left-hand side of the expression. Consider the following example.
package demo;
import java.util.ArrayList;
public class App {
public static void main( final String[] args ) {
final var children = new ArrayList<>();
children.add( "Jade" );
children.add( "Aden" );
System.out.printf( "Children: %s%n", children );
}
}
What is the type of the variable children
?
One can easily mistake the type of the variable children
to a List
. The variable with the name children
is an ArrayList
of type Object
.
final ArrayList<Object> children = new ArrayList<>();
Consider the following example.
package demo;
import java.util.ArrayList;
public class App {
public static void main( final String[] args ) {
final var children = new ArrayList<>();
children.add( "Jade" );
children.add( "Aden" );
/* ⚠️ We can add anything to the list */
children.add( 7 );
System.out.printf( "Children: %s%n", children );
}
}
The above example, compiles as the list can contain Object
s and not just String
s. Avoid using the diamond operator when the type cannot be deducted from the left-hand side of the expression.
Can we use generics with primitive types?
No. Generics only work with reference types.
Java 1.5 also introduced autoboxing, which simplifies the use of primitive types. In a nutshell, Java converts our primitives into reference type through the respective wrapper.
Generics work only with reference types and primitives are converted to reference types. Consider the following example.
package demo;
import java.util.ArrayList;
import java.util.List;
public class App {
public static void main( final String[] args ) {
final List<Integer> numbers = new ArrayList<>();
numbers.add( 7 );
numbers.add( 4 );
System.out.printf( "Numbers: %s%n", numbers );
}
}
The variable numbers
is a list of type Integer
(reference type) and not int
(reference type). The int
literals 7
and 4
are converted to Integer
s using the Integer.valueOf()
method.
Creating an object from a primitive takes more space as objects carry an overhead. With that said, the static factory method valueOf()
caches some values to save up memory.
Are there cases where we cannot use generics?
YES
There are some cases where we cannot use generics.
Cannot have generic static fields
⚠ The following example does not compile!!/* pending example */
Cannot create generic instance
⚠ The following example does not compile!!/* pending example */
Cannot create generic arrays
⚠ The following example does not compile!!package demo; public class App { public static void main( final String[] args ) { final String[] array = createArray(); } private static <T> T[] createArray() { /* ⚠️ cannot create generic arrays */ final T[] array = new T[0]; return array; } }
We cannot use raw types in this example, but worth noting that we cannot create generic arrays.
Cannot catch a generic exception
⚠ The following example does not compile!!/* pending example */