Generic Methods using Parameterized Types

This section provides a tutorial example on generic methods that use parameterized types. Type argument inference tests are provided on the generic method, singletonList(), from the java.util.Collections class.

In this section, I want to continue to test on type argument inference on generic method that uses type parameters in parameterized types. Here is my first tutorial example on testing generic method, "<T> List<T> singletonList(T o)", defined in the Collections class.

/* GenericMethodSingletonList.java
 - Copyright (c) 2014, HerongYang.com, All Rights Reserved.
 */
import java.util.*;
class GenericMethodSingletonList {
   public static void main(String[] a) {
      testSingletonList();
   }
   public static void testSingletonList() {
      java.io.PrintStream o = System.out;
      o.println("Testing: <T> List<T> singletonList(T o)");

      String s = "777";
      List<String> ls = Collections.singletonList(s);
      String e = ls.get(0);
      o.println("   Class name of ls: "+ls.getClass().getName());
      o.println("   First element of ls: "+e);

      // Test 1. Compilation error - inconvertible types
      // List<String> ls1 = Collections.singletonList((Object) s);
      
      // up casting to the wildcard parameterized type
      List<?> lw = ls;
      
      // up casting to a supper interface
      Collection<String> cs = ls;

      // Test 2. Comipler error - inconvertible types
      // List<Integer> li1 = (List<Integer>) ls; 
      // Collection<Integer> ci1 = (Collection<Integer>) ls;
      
      // Test 3. Comipler warning - unchecked or unsafe operations
      // List<Integer> li2 = (List<Integer>) (List<?>) ls;
      // o.println("   Class name of li2: "+li2.getClass().getName());

      // Test 4. Runtime exception - cannot be cast
      // Integer e2 = li2.get(0);
      // o.println("   First element of li2: "+e2);
   }
}

Compile and run the example, you will get:

Testing: <T> List<T> singletonList(T o)
   Class name of ls: java.util.Collections$SingletonList
   First element of ls: 777

Looking at the statement where the generic method is invoked (List<String> ls = Collections.singletonList(s);), here is my guesses on how the compiler did the type argument inference to determine the actual type of the type parameter, T, and did the type checking on the statement:

But if you uncomment "Test 1." statements, you will get a compilation error in JDK 1.8. This error tells me that the compiler is using the resulting type of the input argument expression to determine the actual type of the type parameter, T, not using the object type of what s is referring to.

GenericMethodSingletonList.java:20: error: incompatible types: 
   inference variable T has incompatible bounds
      List<String> ls1 = Collections.singletonList((Object) s);
                                                  ^
    equality constraints: String
    lower bounds: Object
  where T is a type-variable:
    T extends Object declared in method <T>singletonList(T)
1 error

Note that JDK 1.7 gives slightly different error message on "Test 1.":

GenericMethodSingletonList.java:20: error: incompatible types
      List<String> ls1 = Collections.singletonList((Object) s);
                                                  ^
  required: List<String>
  found:    List<Object>
1 error

If you uncomment "Test 2." statements, you will get more compilation error in JDK 1.8, because casting from List<String> to List<Integer> or Collection<Integer> is not allowed.

GenericMethodSingletonList.java:29: error: incompatible types: 
   List<String> cannot be converted to List<Integer>
      List<Integer> li1 = (List<Integer>) ls;
                                          ^
GenericMethodSingletonList.java:30: error: incompatible types: 
   List<String> cannot be converted to Collection<Integer>
      Collection<Integer> ci1 = (Collection<Integer>) ls;
                                                      ^
2 errors

Note that JDK 1.7 gives slightly different error message on "Test 2.":

GenericMethodSingletonList.java:29: error: inconvertible types
      List<Integer> li1 = (List<Integer>) ls;
                                          ^
  required: List<Integer>
  found:    List<String>
GenericMethodSingletonList.java:30: error: inconvertible types
      Collection<Integer> ci1 = (Collection<Integer>) ls;
                                                      ^
  required: Collection<Integer>
  found:    List<String>
2 errors

If you uncomment "Test 3." statements, you will get a compilation warning, not an error, with the "-Xlint" option: This tells me that the compiler is not smart enough to stop me doing inconvertible casting with double cast operations.

GenericMethodSingletonList.java:33: warning: [unchecked] unchecked 
   cast
      List<Integer> li2 = (List<Integer>) (List<?>) ls;
                                          ^
  required: List<Integer>
  found:    List<CAP#1>
  where CAP#1 is a fresh type-variable:
    CAP#1 extends Object from capture of ?
1 warning

If you uncomment "Test 3." and "Test 4." statements, you will get a runtime exception, because li2.get(0) will return a String, even li2 is declared as a List<Integer>

Testing: <T> List<T> singletonList(T o)
   Class name of ls: java.util.Collections$SingletonList
   First element of ls: 777
   Class name of li2: java.util.Collections$SingletonList
Exception in thread "main" java.lang.ClassCastException: 
   java.lang.String cannot be cast to java.lang.Integer
        at GenericMethodSingletonList.testSingletonList
        (GenericMethodSingletonList.java:37)
        at GenericMethodSingletonList.main
        (GenericMethodSingletonList.java:7)

But why there is no runtime exception on the statement where the root cause is located: (List<Integer> li2 = (List<Integer>) (List<?>) ls;)?

The answer is that Java actually allows casting from (List<String> to (List<Integer> at runtime. Because they are really objects from the same class List<T> which is really List<Object> in the compiled bytecode. This called "Type Erasure".

Last update: 2014.

Table of Contents

 About This Book

 Installing JDK 1.8 on Windows

 Execution Process, Entry Point, Input and Output

 Primitive Data Types and Literals

 Bits, Bytes, Bitwise and Shift Operations

 Managing Bit Strings in Byte Arrays

 Reference Data Types and Variables

 StringBuffer - The String Buffer Class

 System Properties and Runtime Object Methods

 Generic Classes and Parameterized Types

Generic Methods and Type Inference

 What Is a Generic Method?

 Comparing Generic Method with Non-Generic Method

 Non-Generic Method Example - maxNonGeneric()

 Generic Method Example - maxGeneric()

 Generic Methods in java.util.Collections Class

 Testing Generic Methods in Collections Class

 What Is Type Argument Inference?

 Type Argument Inference by Parameter List

 Type Argument Inference by Return Value

Generic Methods using Parameterized Types

 Parameterized Type as Generic Method Return Type

 Lambda Expressions and Method References

 Execution Threads and Multi-Threading Java Programs

 ThreadGroup Class and "system" ThreadGroup Tree

 Synchronization Technique and Synchronized Code Blocks

 Deadlock Condition Example Programs

 Garbage Collection and the gc() Method

 Outdated Tutorials

 References

 PDF Printing Version