Raw Types
Raw types are considered bad programming practice as of Java 1.5 and should be avoided. These are introduced so that the reader understands what these are and why raw types are discouraged.
Table of contents
Raw Types
Consider a list of names, 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 children = new ArrayList();
children.add( "Jade" );
children.add( "Aden" );
System.out.printf( "Children: %s%n", children );
}
}
The above example makes use of raw types, where we create a List
without providing any hints to Java about the type of content of the List
. Our List
will contain names, represented by the String
type. This example will print the following.
Children: [Jade, Aden]
Before Java 1.5, we had no means to indicate to Java what’s the content of the List
. This was quite annoying as we could not use the output of the list without casting it. 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 children = new ArrayList();
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 );
}
}
The above example does not compile. The list may contain anything and thus Java has no way to be certain that the given element is of type String
.
src/main/java/demo/App.java:12: error: incompatible types: Object cannot be converted to String
final String firstChild = children.get( 0 );
^
Despite the fact that we know what the content of the list is, we have to type cast the result to the required type, as shown in the following fragment.
final String firstChild = (String) 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 children = new ArrayList();
children.add( "Jade" );
children.add( "Aden" );
System.out.printf( "Children: %s%n", children );
final String firstChild = (String) children.get( 0 );
System.out.printf( "%s is the first child%n", firstChild );
}
}
While casting elements was annoying this was not the main problem with raw types. Consider the following example.
ClassCastException
!! package demo;
import java.util.ArrayList;
import java.util.List;
public class App {
public static void main( final String[] args ) {
final List children = new ArrayList();
children.add( "Jade" );
children.add( "Aden" );
/* ⚠️ We can add anything to a raw-typed list */
/* ⚠️ Added an integer as the first element of the list */
children.add( 0, 7 );
System.out.printf( "Children: %s%n", children );
final String firstChild = (String) children.get( 0 );
System.out.printf( "%s is the first child%n", firstChild );
}
}
In the above example, we are adding a new element of type Integer
in the list as the first element. Later on, we are retrieving the same first element and casting it into a String
. This will cause a ClassCastException
to be thrown, as shown next.
Children: [7, Jade, Aden]
Exception in thread "main" java.lang.ClassCastException: class java.lang.Integer cannot be cast to class java.lang.String (java.lang.Integer and java.lang.String are in module java.base of loader 'bootstrap')
at demo.App.main(App.java:19)
Raw types are quite error prone as the compiler cannot help us here. As of Java 1.5, the Java compiler produces warnings whenever raw types are used.
Note: src/main/java/demo/App.java uses unchecked or unsafe operations.
Note: Recompile with -Xlint:unchecked for details.
We can see the warnings by adding the -Xlint:unchecked
flag to the Java compiler within the build.gradle
file, as shown next.
tasks.withType(JavaCompile).each {
it.options.deprecation = true
it.options.compilerArgs.add('-Xlint:unchecked')
}
We can have more than one Java compiler flag. For example, when using Java preview features, we also add the --enable-preview
flag as shown next.
tasks.withType(JavaCompile).each {
it.options.deprecation = true
it.options.compilerArgs.add('-Xlint:unchecked')
it.options.compilerArgs.add('--enable-preview')
}
Compiling the previous example with the -Xlint:unchecked
flag will show all warnings, as shown next.
$ ./gradlew clean build
> Task :compileJava
src/main/java/demo/App.java:10: warning: [unchecked] unchecked call to add(E) as a member of the raw type List
children.add( "Jade" );
^
where E is a type-variable:
E extends Object declared in interface List
src/main/java/demo/App.java:11: warning: [unchecked] unchecked call to add(E) as a member of the raw type List
children.add( "Aden" );
^
where E is a type-variable:
E extends Object declared in interface List
2 warnings
BUILD SUCCESSFUL in 2s
10 actionable tasks: 10 executed
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.
Are there cases where we must use raw types?
YES
There are two cases where generics are not supported and instead, we need to use the raw types.
The
class
literal⚠ The following example does not compile!!package demo; import java.util.List; public class App { public static void main( final String[] args ) { /* ⚠️ class literals can only be accessed through the raw type!! */ final Object type = List<String>.class; System.out.printf( "The type is %s%n", type ); } }
The Java language specification does not permit the use of parameterized types JLS-15.8.2.
package demo; import java.util.List; public class App { public static void main( final String[] args ) { final Object type = List.class; System.out.printf( "The type is %s%n", type ); } }
The
instanceof
operator⚠ 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 Object list = new ArrayList<String>(); /* ⚠️ the instanceof operator only works with raw types!! */ if ( list instanceof List<String> ) { System.out.println( "It's a list" ); } } }
The
instanceof
operator works with the raw types only.package demo; import java.util.ArrayList; import java.util.List; public class App { public static void main( final String[] args ) { final Object list = new ArrayList<String>(); if ( list instanceof List ) { System.out.println( "It's a list" ); } } }