© Vaskaran Sarcar 2020
V. SarcarInteractive Object-Oriented Programming in Javahttps://doi.org/10.1007/978-1-4842-5404-2_12

12. Generic Programming

Vaskaran Sarcar1 
(1)
Bangalore, Karnataka, India
 

Generic programming is an important concept in Java. It was introduced in JDK 5, and since then it has become an integral part of Java programming. The power of generic programming is enormous. It will make your program type-safe and flexible. Generics are often used with collections frameworks. So, once you are familiar with Java collections, you will be better able to use generics. This chapter provides a brief overview of generics.

To help you understand the power of generics, I’ll start with a non-generic program and then write a generic program. Later, we’ll do a comparative analysis to discover the advantages of generic programming.

Compare Generic Programs with Non-Generic Programs

Let’s start with a non-generic program to start the analysis.

Demonstration 1

Consider the following non-generic program and the output.
package java2e.chapter12;
class MyNonGenericClass {
      public int showInteger(int i) {
             return i;
      }
      public String showString(String s1) {
             return s1;
      }
}
class Demonstration1 {
      public static void main(String[] args) {
             System.out.println("***Demonstration-1.A non-generic program example***");
             MyNonGenericClass nonGenericOb = new MyNonGenericClass();
             System.out.println("showInteger returns : " + nonGenericOb.showInteger(25));
             System.out.println("showString returns : " + nonGenericOb.showString("A non-generic method is called."));
      }
}
Output:
***Demonstration-1.A non-generic program example***
showInteger returns : 25
showString returns : A non-generic method is called.
Now, consider a generic program. Before you start, go through the following points about generic programming in Java:
  • In generic programming, you are actually dealing with parameterized types. You will often notice the use of generic classes, generic interfaces, or generic methods in a generic program.

  • Angle brackets <> are used to create generic types.

  • In your generic program, you can define a class with placeholders for the type of its methods, fields, parameters, etc. At a later stage, these placeholders are replaced with the particular type.

  • Prior to JDK5, there was no concept of generic programming in Java. So, in earlier days, to make a generalized class, interface, or method, programmers needed to consider the Object class. Since the Object class is the ultimate super class, an object reference can refer to any subtype object. So, casting was often required to get back the actual type. As a result, type-safety was a big concern prior to the generics era.

  • JLS11 says that “it is a compile-time error if a generic class is a direct or indirect subclass of Throwable.” This restriction is important because JVM’s catch mechanism is compatible only with non-generic classes.

  • Experts often suggest the use of generic programming instead of its non-generic counterpart.

Let’s start with the following program.

Demonstration 2

Go through the associated comments for a better understanding of the code.
package java2e.chapter12;
//A generic class
//T is a type parameter.It will be replaced by the real type when you //initialize the actual object.
class MyGenericClass<T> {
// A generic method
// The following method's return type is T. It also accepts
// a T type argument.
      public T show(T value) {
             return value;
      }
}
public class Demonstration2 {
      public static void main(String[] args) {
             System.out.println("***Demonstration-2.A generic program example***");
             // Creating a MyGenericClass<Integer> type object.
             MyGenericClass<Integer> myGenericClassIntOb = new MyGenericClass<Integer>();
             System.out.println("The method show returns the integer value : " + myGenericClassIntOb.show(100));
             // Creating a MyGenericClass<String> type object.
             MyGenericClass<String> myGenericClassStringOb = new MyGenericClass<String>();
             System.out.println("The method show returns the string value :
                         "+ myGenericClassStringOb.show("A generic method is called."));
             // Creating a MyGenericClass<Double> type object.
             MyGenericClass<Double> myGenericClassDoubleOb = new MyGenericClass<Double>();
             System.out.println("The method show returns the double value : " + myGenericClassDoubleOb.show(100.5));
      }
}
Output:
 ***Demonstration-2.A generic program example***
The method show returns the integer value : 100
The method show returns the string value : A generic method is called.
The method show returns the double value : 100.5
Let’s now do a comparative analysis of Demonstration 1 and Demonstration 2. You have seen the following characteristics:
  • For non-generic methods, you need to specify methods like showInteger() and showString() to handle the particular data type. But in the generic version, the method show() is sufficient. In general, there are fewer lines of code in generic versions (i.e., code size is smaller).

  • Inside main() in Demonstration 1, you encounter a compile-time error if you add the following line of code (also shown in Figure 12-1):

System.out.println("showDouble returns : " + nonGenericOb.showDouble(15.9));
../images/433474_2_En_12_Chapter/433474_2_En_12_Fig1_HTML.jpg
Figure 12-1

A compile-time error in a non-generic program

The error message is self-explanatory. You know that you did not define something like the showDouble(double d) method for the type MyNonGenericClass. To avoid this error, you may need to include an additional method in the class MyNonGenericClass, as follows (notice the method showDouble() in bold):
class MyNonGenericClass {
      public int showInteger(int i) {
             return i;
      }
      public String showString(String s1) {
             return s1;
      }
      public double showDouble(double d) {
             return d;
      }
}

The code size of MyNonGenericClass is increased with this addition. You needed to increase the code size because you needed to process a different data type, double.

Now, turn your attention to Demonstration 2, where you get the double data type without modifying MyGenericClass. As a result, you can conclude that the generic version is more flexible and may require fewer lines of code.

Apart from this, consider another useful scenario. Suppose, in Demonstration 2, in the side main() method, you add one more line of code, like the following:
myGenericClassIntOb.show(125.7);//Error

You will receive a compile-time error now. This is because myGenericIntOb is of type MyGenericClass<Integer>, so the compiler can check whether you are properly passing an Integer argument in the show() method. In this way, you can promote type-safety in your code through generic programming.

You may also wonder why I am not writing something like the following:
System.out.println(myGenericClassIntOb.show(new Integer(100))); // Also ok but no additional benefit
instead of
System.out.println(myGenericClassIntOb.show(100));

It is because Java can perform the autoboxing to encapsulate from an int to the corresponding wrapper class Integer.

Note

Remember, the process of converting a primitive type into an object of the corresponding wrapper class is termed autoboxing. For example, int to Integer, double to Double, float to Float etc. You learned about wrapper classes in Chapter 3.

For a quick review, once again notice the following line of code in Demonstration 2:
MyGenericClass<Integer> myGenericClassIntOb = new MyGenericClass<Integer>();

You need to use similar syntax when you write generic programs in corresponding places. You can observe that the type Integer is specified within the angle brackets after MyGenericClass, and Integer is the type argument that you are passing to the type MyGenericClass. In a similar way, you use MyGenericClass with different types. In this context, you must go through the upcoming statements carefully.

It is also important to note that you are passing the class type; i.e., an Integer argument. But if you pass any primitive datatype, for example, an int, you’ll receive a compile-time error. The following declaration is NOT legal in generic programming in Java:
// Primitive types are NOT allowed here.
// It must be a reference type.
MyGenericClass<int> myGenericClassIntOb2 = new MyGenericClass<int>();
In Eclipse, you will notice an error for this code (as shown in Figure 12-2): Syntax error, insert “Dimensions” to complete ReferenceType. Here is a snapshot from Eclipse IDE.
../images/433474_2_En_12_Chapter/433474_2_En_12_Fig2_HTML.jpg
Figure 12-2

Syntax errors when you pass a primitive datatype instead of a reference type

A wrapper class can hold primitive datatypes as objects. So, when you need to pass a primitive datatype in a case like this, you first wrap it in the equivalent wrapper type and proceed (as shown in Demonstration 2).

Points To Remember

It may appear that different versions of MyGenericClass truly exist. But the Java compiler actually removes all this generic type information and performs the necessary cast to make the code behave like this. This removal process is termed erasure. In actuality, there is only one version of MyGenericClass that exists for Demonstration 2. You’ll learn about erasures shortly.

Demonstration 3

Consider the following program. This demonstration is presented to show how a generic program can perform better than a non-generic program. It should be noted that in this demonstration, I have used the legacy ArrayList (or you can say the non-generic version of ArrayList), which is bad practice and is not recommended. It is presented only for the purpose of comparison with a generic program.
package java2e.chapter12;
import java.util.ArrayList;
import java.util.List;
public class Demonstration3 {
      public static void main(String[] args) {
             System.out.println("***Demonstration-3.A bad practice.Using a lagacy ArrayList and encountering a runtime error.***");
             // BAD practice.Following line of code is using a legacy //ArrayList
             List myList = new ArrayList();
             myList.add(10);
             myList.add(20);
             myList.add("Invalid");// No compile-time error when you use //legacy ArrayList
             // Printing the contents of the ArrayList
             System.out.println("Here is the contents of the ArrayList:");
             for (int i = 0; i < myList.size(); i++) {
                    System.out.println(myList.get(i));
             }
             // Picking last element in the ArrayList
             int lastElement = (int) myList.get(myList.size() - 1);
             System.out.println("Adding 1 to last element and printing");
             System.out.println(++lastElement);// Run-time error
      }
}
The program will not raise any compile-time errors, but you will receive a runtime error that says the following:
***Demonstration-3.A bad practice.Using a lagacy ArrayList and encountering a runtime error.***
Here is the contents of the ArrayList:
10
20
Invalid
Exception in thread "main" java.lang.ClassCastException: java.lang.String cannot be cast to java.lang.Integer
      at java2e.chapter12.Demonstration3.main(Demonstration3.java:21)

This is because the third element (i.e., myList [2] in the ArrayList) is not an integer (it is a string). During compile time, you did not encounter any issues, because it was stored as an object. So, you can see that type-safety is a major concern with a non-generic program.

Demonstration 4

One of the notable characteristics of this example is that you can see the use of lambda expressions, which were introduced in Java 8. Since they are not mandatory for this program, I have placed one in the commented block. I have included this because in similar examples, you may notice the use of lambda expressions in different places.

Let’s quickly review what a lambda expression is and why it is important. One of the main goals of using a lambda expression is that you can treat it as a function that need not be a part of a class. Here is a sample code for a lambda expression:
(int a, int b) -> {return (a + b);}
It is a lambda expression that has two parameters and a return statement. It is possible to use lambda expressions without a parameter. A lambda expression can exist without a return statement too. For example, here is a lambda expression that does not accept any parameters and doesn’t have any return statements:
() -> System.out.println("Lambda expression without a  return statement");

Using a lambda expression, you can make your code compact and easily readable. Java lambda expression is treated as a function, so the compiler does not create a .class file for it.

Now, consider the updated program in Demonstration 4, where you use generic programming. The key changes are shown in bold.
package java2e.chapter12;
import java.util.ArrayList;
import java.util.List;
public class Demonstration4 {
      public static void main(String[] args) {
             System.out.println("***Demonstration-4.Use Generics to promote type-safety and avoid runtime error***");
             ArrayList<Integer> myList = new ArrayList<Integer>();
             myList.add(10);
             myList.add(20);
             // Compile time error when you use ArrayList<Integer>//myList.add("Invalid");
             //Printing the contents of the ArrayList
             System.out.println("Here is the contents of the ArrayList:");
                    for (int i = 0; i < myList.size(); i++) {
                    System.out.println(myList.get(i));
             }
                    /*
                     for (int myInt : myList) { System.out.println(myInt); }
                     System.out.print("Printing the elements using lambda expression: ");
                     //Or, use the enhanced for loop with lambda expression
                     myList.forEach((myInt) -> System.out.println(myInt));
                     */
                    //Picking last element in the ArrayList.
                    //No casting is required now.
                    int lastElement=myList.get( myList.size()-1);
                    System.out.println("Adding 1 to last element and printing");
                    System.out.println(++lastElement);//No runtime error
      }
}
This time, you’ll catch the bug much earlier because it is caught at compile time (Figure 12-3). You can see the compile-time error message, which clearly says that you are using a String instead of an Integer.
../images/433474_2_En_12_Chapter/433474_2_En_12_Fig3_HTML.jpg
Figure 12-3

You can catch an error early in a generic program

In this case, since the error is caught during compile time, you do not need to wait until runtime to get this error, which is always better. Once you comment out the following line:
myList.add("Invalid");
you can receive the intended output, as follows:
***Demonstration-4.Use Generics to promote type-safety and avoid runtime error***
Here is the contents of the ArrayList:
10
20
Adding 1 to last element and printing
21
By comparing Demonstration 3 and Demonstration 4, you can say the following:
  • To avoid runtime errors, you should prefer the generic version of code to the non-generic version.

  • No casting is required now. Notice the following line of code:

int lastElement=myList.get( myList.size()-1);
  • Lastly, you can see a commented block of code, which is presented for reference purposes only. In different programs, you may notice one of these variations of a for loop:

 /*
 for (int myInt : myList) { System.out.println(myInt); }
 System.out.print("Printing the elements using lambda expression: ");
 // Or, use the enhanced for loop with lambda expression
 myList.forEach((myInt) -> System.out.println(myInt));
*/
  • Since you are using the concept of generics, you can print the elements of the ArrayList in a better way. In general, you’ll see these versions of code when you traverse and print elements in an ArrayList (or other collection objects).

So, you can surely conclude that the generic version of ArrayList is more flexible and usable than the non-generic version of ArrayList. The same concept is applicable for other collection objects and similar kinds of programming.

Wildcard Types in Generic Programming

In upcoming discussions you’ll notice that sometimes you may need to put restrictions on a particular type in generic programming. There are two common ways to implement such constraints—one approach is to use wildcards, and another approach is to use bounded type parameters. Here, I’ll start with wildcards.

In generic programming, a wildcard is represented with the question mark (?). It denotes an unknown type. It can be used when you have partial knowledge about the type parameter.

Wildcards can be bounded or unbounded. Bounded wildcards can be used to set either an upper bound or a lower bound for a type argument. Let’s start the discussion with an upper-bound wildcard

Upper-bound Wildcard

To begin with, consider the following code segment. In this segment, Vehicle is the super class, and it has two subclasses: Bus and Rocket. Each of these classes has a constructBody() method to construct the particular instances. All of these classes also maintain a counter to track how many instances of these types are created. Here is the code segment:.
class Vehicle {
      static int basicVehicleCount;
      // Construct some basic structure of an individual vehicle
      public void constructBody() {
             basicVehicleCount++;
             System.out.println("One basic structure is formed.No of basic structure ="+ basicVehicleCount);
      }
}
class Bus extends Vehicle {
      static int busCount;
      @Override
      public void constructBody() {
             busCount++;
             System.out.println("Bus completed.It can move on road now. The bus count=" + busCount);
      }
}
class Rocket extends Vehicle {
      static int rocketCount;
      @Override
      public void constructBody() {
             rocketCount++;
             System.out.println("Rocket constructed.It can move into space now. The rocket count=" + rocketCount);
      }
}
Suppose you have stored these vehicles (Vehicle, Bus, or Rocket) in a collection—say, in an ArrayList. Now you want to invoke the constructBody() method for each instance in the ArrayList. So, you may start with code like the following:
//May NOT work in this case
      public static void constructBody(List<Vehicle> vehicleList) {
             System.out.println(" Here is the vehicle list for you : ");
             vehicleList.forEach((vehicle) -> vehicle.constructBody());
      }
This segment of code can work when you have an ArrayList<Vehicle> type. But interestingly, it will not work when you apply it to either the ArrayList<Bus> or ArrayList<Rocket> types. From the compiler’s point of view, ArrayList<Bus> or ArrayList<Rocket> are different from ArrayList<Vehicle>, though Vehicle is the supertype of Bus and Rocket (you can refer to the “One Final Suggestion” section at the end of the chapter). In this case, the compiler will suggest you introduce methods like
static void constructAllVehicles(ArrayList<Rocket> rockets){//some code}
or
static void constructAllVehicles(ArrayList<Bus> buses) {//some code}.
If you follow those suggestions, the compiler will further complain with different errors, like those in Figure 12-4.
../images/433474_2_En_12_Chapter/433474_2_En_12_Fig4_HTML.jpg
Figure 12-4

Eclipse IDE snapshot of some compile-time errors in a generic program

So, in short, you need to do something special to handle this situation. One possible solution is to use the wildcard type, like the following (shown in bold).
// Construct all vehicles in the list
//public static void constructBody(List<Vehicle> vehicleList) {
public static void constructAllVehicles(List<? extends Vehicle> vehicleList) {
             System.out.println(" Here is the vehicle list for you : ");
             vehicleList.forEach((vehicle) -> vehicle.constructBody());
      }

Here is the full demonstration for you.

Demonstration 5

In this demonstration, some portions are in bold to highlight important lines of code in the program.
package java2e.chapter12;
import java.util.ArrayList;
import java.util.List;
class Vehicle {
      static int basicVehicleCount;
      // Construct some basic structure of an individual vehicle
      public void constructBody() {
             basicVehicleCount++;
             System.out.println("One basic structure is formed.No of basic structure ="+ basicVehicleCount);
      }
}
class Bus extends Vehicle {
      static int busCount;
      @Override
      public void constructBody() {
             busCount++;
             System.out.println("Bus completed.It can move on road now. The bus count=" + busCount);
      }
}
class Rocket extends Vehicle {
      static int rocketCount;
      @Override
      public void constructBody() {
             rocketCount++;
             System.out.println("Rocket constructed.It can move into space now. The rocket count=" + rocketCount);
      }
}
class Demonstration5 {
      public static void main(String[] args) {
             System.out.println("***Demonstration-5.Use of Wildcard types in generic programming.***");
             //One Vehicle object
             Vehicle vehicle1=new Vehicle();
             // Three Bus objects
             Bus bus1 = new Bus();
             Bus bus2 = new Bus();
             Bus bus3 = new Bus();
             // Two Rocket objects
             Rocket rocket1 = new Rocket();
             Rocket rocket2 = new Rocket();
             // List of anytype of vehicles.Vehicle type or its subtypes //can be added.
             ArrayList<Vehicle> vehicles = new ArrayList<Vehicle>();
             // Adding one vehicle,one bus and one rocket in the list
             vehicles.add(vehicle1);
             vehicles.add(bus1);//ok
             vehicles.add(rocket1);//ok
             constructAllVehicles(vehicles);// ok
             // List of specific vehicles(buses) only
             ArrayList<Bus> buses = new ArrayList<Bus>();
             // Adding three buses in the list
             buses.add(bus1);
             //error: cannot add a rocket to a bus list
             // buses.add(rocket1);
             buses.add(bus2);
             buses.add(bus3);
             // error if you do not use wildcard in the method
             constructAllVehicles(buses);
             // List of specific vehicles(rockets) only
             ArrayList<Rocket> rockets = new ArrayList<Rocket>();
             // Adding two rockets in the list
             rockets.add(rocket1);
             rockets.add(rocket2);
             // error if you do not use wildcard in the method
             constructAllVehicles(rockets);
      }
      // Construct all vehicles in the list
      // public static void constructBody(List<Vehicle> vehicleList) {
      public static void constructAllVehicles(List<? extends Vehicle> vehicleList) {
             System.out.println(" Here is the vehicle list for you : ");
             vehicleList.forEach((vehicle) -> vehicle.constructBody());
      }
}
Output:
***Demonstration-5.Use of Wildcard types in generic programming.***
Here is the vehicle list for you :
One basic structure is formed.No of basic structure =1
Bus completed.It can move on road now. The bus count=1
Rocket constructed.It can move into space now. The rocket count=1
Here is the vehicle list for you :
Bus completed.It can move on road now. The bus count=2
Bus completed.It can move on road now. The bus count=3
Bus completed.It can move on road now. The bus count=4
Here is the vehicle list for you :
Rocket constructed.It can move into space now. The rocket count=2
Rocket constructed.It can move into space now. The rocket count=3

You can see that List<Vehicle> is more restrictive than List<? extends Vehicle>. Here, you are relaxing the restriction by using wildcards. As a result, the constructAllVehicles(List<? extends Vehicle> vehicleList) can be applied with both the ArrayList<Vehicle> and ArrayList<any subtype of Vehicle> (i.e., with ArrayList<Bus> and ArrayList<Rocket> in this example). So, the ? extends Vehicle syntax simply helps the compiler to match the type Vehicle or any subtype of Vehicle. This is why we say that the extends clause sets an upper bound when you use it with a wildcard.

Points To Remember

It should be noted that extends in this context is used in a general sense, which can mean either extends in classes or implements in interfaces.

Lower-bound Wildcard

You have just learned that when you use the expression <? extends Vehicle> in the argument of a method, you can invoke the method for the Vehicle class or any of its subclasses. So, the Vehicle class here acts as the upper bound. Similarly, you can set the lower bound of a wildcard when you use the expression <? super Vehicle>. In this case, the acceptable arguments are Vehicle and its super class. So, you can roughly interpret the expression <? super T> as “either the class T or any super class of T.

Demonstration 6

Let’s consider the following example:
package chapter12.testcodes;
import java.util.ArrayList;
import java.util.List;
class Vehicle {
      // Construct some basic structure of an individual vehicle
      public void constructBody() {
             System.out.println("One basic structure is formed.");
      }
}
class Bus extends Vehicle {
      static int busCount;
      @Override
      public void constructBody() {
             busCount++;
             System.out.println("Bus completed.It can move on road now. The bus count=" + busCount);
      }
}
class Rocket extends Vehicle {
      static int rocketCount;
      @Override
      public void constructBody() {
             rocketCount++;
             System.out.println("Rocket constructed.It can move into space now. The rocket count=" + rocketCount);
      }
}
public class TestCodeDemonstration6 {
      public static void main(String[] args) {
             System.out.println("***Demonstration-6.Use of lower-bound wildcard types in generic programming.***");
             //Two Vehicle objects
             Vehicle vehicle1=new Vehicle();
             Vehicle vehicle2=new Vehicle();
             // Two Bus objects
             Bus bus1 = new Bus();
             Bus bus2 = new Bus();
             // Two Rocket objects
             Rocket rocket1 = new Rocket();
             Rocket rocket2 = new Rocket();
             // List of vehicles
             ArrayList<Vehicle> vehicles = new ArrayList<Vehicle>();
             // Adding two vehicles in the list
             vehicles.add(vehicle1);
             vehicles.add(vehicle2);
             // Adding two buses in the list
             vehicles.add(bus1);
             vehicles.add(bus2);
             constructAllVehicles(vehicles);//ok
             // List of rockets
             ArrayList<Rocket> rockets = new ArrayList<Rocket>();
             // Adding two rockets in the list
             rockets.add(rocket1);
             rockets.add(rocket2);
             //constructAllVehicles(rockets);// Error: Not applicable for //ArrayList<Rocket> when you use the lower bound wildcard
      }
      // Construct all vehicles in the list
      public static void constructAllVehicles(List<? super Bus> vehicleList) {
             System.out.println(" Here is the vehicle list for you : ");
             //Compile-time error:Add cast to vehicle
             //vehicleList.forEach((vehicle) ->  vehicle.constructBody());
             /*
             //Runtime error:Vehicle cannot be cast to Bus
             //vehicleList.forEach((vehicle) -> ((Bus) vehicle).constructBody());
             */
             vehicleList.forEach((bus) -> ((Vehicle) bus).constructBody());//Ok
      }
}
Output:
***Demonstration-6.Use of lower-bound wildcard types in generic programming.***
Here is the vehicle list for you :
One basic structure is formed.
One basic structure is formed.
Bus completed.It can move on road now. The bus count=1
Bus completed.It can move on road now. The bus count=2

You can see that <? super Bus> in the method argument helps you to call the method with ArrayList<Bus> and ArrayList<Vehicle> because Vehicle is the supertype of Bus. But you cannot use the method when you use ArrayList<Rocket>, because Rocket is not a supertype of Bus.

Unbounded Wildcard

Wildcards can be unbounded. You can use the concept of unbounded wildcards when you use just the wildcard character (?). Let’s modify the method constructAllVehicles() in Demonstration 6 as follows:
      //The use of an unbounded wildcard
      public static void constructAllVehicles(List<?> vehicleList) {
             System.out.println(" Here is the vehicle list for you : ");
             vehicleList.forEach((anyVehicle) -> ((Vehicle) anyVehicle).constructBody());//Ok
      }
Here, List<?> is used to denote a list of unknown types. You can also uncomment the following code in Demonstration 6, as follows:
constructAllVehicles(rockets);// Error: Not applicable for //ArrayList<Rocket> when you use the lower-bound wildcard
This time there is no compile-time error, and you will receive the following output:
***Demonstration-6.Use of lower-bound wildcard types in generic programming.***
Here is the vehicle list for you :
One basic structure is formed.
One basic structure is formed.
Bus completed.It can move on road now. The bus count=1
Bus completed.It can move on road now. The bus count=2
Here is the vehicle list for you :
Rocket constructed.It can move into space now. The rocket count=1
Rocket constructed.It can move into space now. The rocket count=2

Notice the bold lines of the output. You can see that now Rocket objects can also invoke the constructAllVehicles(List<?> vehicleList) method.

Points To Remember

You can use generic types, which can contain wildcards as parameter types, fields, or local variables, but not as a type argument for generic methods’ invocation. They should not be used for generic class instance creation or supertypes. You can refer to Demonstration 6A for a better understanding.

Q&A Session

12.1 Are List<?> and List<? extends Object> the same?

No. Let’s examine a case. Consider the following code segment:
class Vehicle1Test {
      @Override
      public String toString() {
             return "Vehicle1Test type.";
      }
}
class Sub1VehicleTest extends Vehicle1Test {
      @Override
      public String toString() {
             return "Sub1VehicleTest type.";
      }
}
Now, you can write a method that is something like the following:
public static void addElementsVersion2(List<Object> mylist) {
             mylist.add(new Vehicle1Test());// ok
             mylist.add(new Sub1VehicleTest());// ok
             mylist.add(null);// ok
      }
But notice the commented code in the following method:
public static void addElementsVersion1(List<?> mylist) {
             // mylist.add(new Vehicle1Test());// error
             // mylist.add(new Sub1VehicleTest());// error
             mylist.add(null);// ok
      }

In this example, you can add any object type or a subtype into List<Object>, but in case of List<?>, you can only add null. Java language specification (Jls 11 @ section 4.7) further tells us that List<?> is a reifiable type but List<? extends Object> is not.

12.2 What do you mean by reifiable types?

The type whose information is completely available during runtime is called a reifiable type. (You will later learn that some type information is erased during compile time, so it is possible that the complete type information is not available during runtime.)

According to the language specification, a type is reifiable if and only if one of the following holds:
  • It refers to a non-generic class or interface type declaration.

  • It is a parameterized type in which all type arguments are unbounded wildcards.

  • It is a raw type.

  • It is a primitive type.

  • It is an array type whose element type is reifiable.

  • It is a nested type where, for each type T separated by a ".", T itself is reifiable.

12.3 Then what is the use of List <?>?

Sometimes you may just want to iterate through your collection. For example, consider the following program and output:
package chapter12.testcodes;
import java.util.ArrayList;
import java.util.List;
class Vehicle1Test {
      @Override
      public String toString() {
             return "Vehicle1Test type.";
      }
}
class Sub1VehicleTest extends Vehicle1Test {
      @Override
      public String toString() {
             return "Sub1VehicleTest type.";
      }
}
class Test1 {
      public static void main(String[] args) {
             System.out.println("***A sample test.An use of List<?>***");
             Vehicle1Test vehicle1 = new Vehicle1Test();
             Vehicle1Test vehicle2 = new Sub1VehicleTest();
             List<Object> vehicles = new ArrayList<Object>();
             vehicles.add(vehicle1);// ok
             vehicles.add(vehicle2);// ok
             printElements(vehicles);// ok.An example of use //List<?> in a //method argument
      }
      public static void addElementsVersion1(List<?> mylist) {
             // mylist.add(new Vehicle1Test());// error
             // mylist.add(new Sub1VehicleTest());// error
             mylist.add(null);// ok
      }
      public static void addElementsVersion2(List<Object> mylist) {
             mylist.add(new Vehicle1Test());// ok
             mylist.add(new Sub1VehicleTest());// ok
             mylist.add(null);// ok
      }
      public static void printElements(List<?> mylist) {
             mylist.forEach(element -> System.out.println(element));
      }
}
Output:
***A sample test.An use of List<?>***
Vehicle1Test type.
Sub1VehicleTest type.

12.4 What is a raw type?

You will see a discussion of raw types in Demonstration 10.

12.5 Can you present some valid and invalid statements showing when you use wildcards in your program?

Demonstration 6A can help you. Here, you can analyze different case studies.

Demonstration 6A

Review the program and the supporting comments for your understanding.
package chapter12.testcodes;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
class Sample {
      //Case Study-1:Wildcards in fields
      List<?>  myList;  //valid
      //?  aField;  //Invalid
      //Case Study-2:Wildcards in method parameter
      //Syntax error for ?
      //Invalid
       //public void invalidMethodWithWildCardParameter(? methodParameter) {  
              //Some code
      // }
      //The following method is valid.
      public void validMethodWithWildCardParameter(List<?> myParameter) {
              System.out.println("The validMethodWithWildCardParameter(List<?> myParameter) is a valid method.");
             }
      //Case Study-3:Wildcards in return type
      //Error: Return type for the method is missing
      // private ? methodWithWildCardReturnType() {//Invalid
              //return null;
      //    }
}
//Case Study-4:Wildcards in supertype
//Error: A supertype may not specify any wild card
//public class SubList implements List<?>{ //Invalid
      //Some code
//}
class Test2 {
      public static void main(String[] args) {
        System.out.println("***Demonstration 6A.Some case study with wildcards***");
      Sample sample=new Sample();
      //Case Study-5:Wildcards in local variable
      List<?> myList = Arrays.asList(12,27,39);//Valid
      System.out.println("Original list :" + myList);
      Collections.reverse(myList);
      System.out.println("Reversed List:"+myList);
      sample.validMethodWithWildCardParameter(myList);
      }
}
Here is the output from Demonstration 6A.
***Demonstration 6A.Some case study with wildcards***
Original list :[12, 27, 39]
Reversed List:[39, 27, 12]
The validMethodWithWildCardParameter(List<?> myParameter) is a valid method.

Bounded Type Parameter

You’ll learn shortly that wildcards cannot solve all your problems efficiently. There is another option, called bounded type parameters. They help you to restrict the types that you can use as the type arguments in parameterized types.

Let’s begin with a very simple use case. Assume that you are dealing with some integers and doubles, and you want to make a generic class that should have a method to calculate the sum of these values. You understand the following:
  • You can create a method with the return type double to serve your purpose.

  • You are using generics, so you need to consider a wrapper class for int and double. Integer is the wrapper for int, and Double is the wrapper for double.

  • Integer and Double are subclasses of the Number class, which has methods like intValue(), longValue(), floatValue(), doubleValue(), and byteValue(). Let’s see from Eclipse IDE what the method doubleValue() does. Figure 12-5 is a snapshot from Eclipse.
    ../images/433474_2_En_12_Chapter/433474_2_En_12_Fig5_HTML.jpg
    Figure 12-5

    A snapshot of the doubleValue() method details from Eclipse IDE

  • Since doubleValue() is an abstract method, its concrete subclasses must implement this method. Now, let’s check how the Integer class implements the doubleValue() method. Here it is:

/**
 * Returns the value of this {@code Integer} as a {@code double}
 * after a widening primitive conversion.
 * @jls 5.1.2 Widening Primitive Conversions
 */
public double doubleValue() {
       return (double)value;
}
  • Now, let’s also check how the Double class implements the doubleValue() in the Number class:

/**
 * Returns the {@code double} value of this {@code Double} object.
 * @return the {@code double} value represented by this object
 */
public double doubleValue() {
      return value;
}

From these definitions, it is obvious that in your case, you can use the doubleValue() method . Now you can write the following program.

Demonstration 7

Let’s compile and run the program and then analyze the output.
package java2e.chapter12;
//A generic class
//T is a type parameter.It will be replaced by the real type when you //initialize the actual object.
class GenericDemo7Class<T extends Number> {
      T firstNumber, secondNumber;
      GenericDemo7Class(T firstNumber, T secondNumber) {
             this.firstNumber = firstNumber;
             this.secondNumber = secondNumber;
      }
      // Always returning a double value
      public double displaySum() {
             //using the library method doubleValue()
             return firstNumber.doubleValue() + secondNumber.doubleValue();
      }
}
class Demonstration7 {
      public static void main(String[] args) {
             System.out.println("***Demonstration-7.A typical use of bounded type parameter.*** ");
             GenericDemo7Class<Double> doubleOb = new GenericDemo7Class<Double>(2.5, 5.7);
             System.out.println("2.5+5.7=" + doubleOb.displaySum());
             GenericDemo7Class<Integer> intOb = new GenericDemo7Class<Integer>(2, 7);
             System.out.println("2+7=" + intOb.displaySum());
             //GenericDemo7Class<String> stringOb=new GenericDemo7Class<String>("hello","world!");
             // Bound mismatch error if you use class GenericDemo7Class<T extends Number>
             //System.out.println( "2+7=" +stringOb.displaySum());
      }
      }
Output:
***Demonstration-7.A typical use of bounded type parameter.***
2.5+5.7=8.2
2+7=9.0
You can see that the program is compiled and run successfully. Now, consider the following points:
  • Let’s see what happens when you use <T> instead of <T extends Number> in the prior demonstration, as follows:

//class GenericDemo7Class<T extends Number> {
class GenericDemo7Class<T> {
This time, you’ll encounter the compile-time error as follows (shown in Figure 12-6): The method doubleValue() is undefined for the type T.
../images/433474_2_En_12_Chapter/433474_2_En_12_Fig6_HTML.jpg
Figure 12-6

The compile-time error says doubleValue() is undefined for the type T

The compiler is raising this concern because in this case, it is not sure whether you will use a true number or not. But when you use <T extends Number>, you are telling the compiler that you will always pass a Number type, not any other types.
  • You can see some commented lines in Demonstration 7. If you uncomment the following line

GenericDemo7Class<String> stringOb = new GenericDemo7Class<String>("hello","world!");
you’ll receive compile-time errors for bound mismatches. Figure 12-7 is the Eclipse IDE snapshot for that.
../images/433474_2_En_12_Chapter/433474_2_En_12_Fig7_HTML.jpg
Figure 12-7

Bound mismatch errors

So, you can see that when you use <T extends Number> instead of <T>, you cannot pass anything other than the Number type.

Points To Remember

In generic programming, <T extends YourSuperClass> says that T can be replaced by either YourSuperClass or any subclass of YourSuperClass. This approach helps you to provide an inclusive upper bound. Using this approach, you are promoting type safety in your program.

It should be noted that, like wildcards, extends in this context is used in a general sense, which means it either extends the class or implements the interface.

You can use both the class type and the interface type as bounds. But there is an important restriction. You may remember that in Java programming, your class can extend from another class, and it can implement multiple interfaces. The same rule applies here. At the same time, you also need to mention class type before the interface type(s). When you create a bound with a class type and the interface type(s), you use the & operator, as follows:
<T extends ClassName & FirstInterfaceName & SecondInterfaceName>
Let’s assume you have the following code segment:
class Demo8AClass {
      //Some code
}
class Demo8BClass {
      //Some code
}
interface Interface8ADemo {
      //Some code
}
interface Interface8BDemo {
      //Some code
}
class ImplementorInterface8ADemo implements Interface8ADemo{
      //Some code
}

Demonstration 8

For the prior code segment, Demonstration 8 presents some samples of valid and invalid statements for your reference. Here invalid statements are marked with the comment //Error and valid statements are marked with //Ok.
//class GenericDemo8Class<T extends Demo8AClass & Demo8BClass> {//Error
//class GenericDemo8Class<T extends  Interface8ADemo & Demo8AClass & Interface8BDemo> {//Error
//class GenericDemo8Class<T extends  ImplementorInterface8ADemo & Interface8ADemo & Interface8BDemo> {//Ok
class GenericDemo8Class<T extends Demo8AClass & Interface8ADemo & Interface8BDemo> {//Ok
}

Q&A Session

12.6 How do wildcards differ from bounded type parameters ?

It depends on your implementation. In certain situations, bounded type parameters can promote better readability and safety. It’ll be helpful to remember the syntax for them. Any wildcard can have only one bound.
  • For upper bound, you use: ? extends SuperType

  • For lower bound, you use: ? super SubType

On the other hand, you can associate multiple bounds with a type parameter. So, you have seen the following statements before.

When you create a bound with a class type and the interface type(s), you use the & operator, as follows:
<T extends ClassName & FirstInterfaceName & SecondInterfaceName>

Demonstration 8 shows some of the usage of this.

Erasures

Prior to Demonstration 3, I stated that it may appear that different versions of MyGenericClass exist. But the Java compiler actually removes all this generic type information and performs the necessary cast to make the code behave like this. This removal process is termed erasure. So, in actuality, only one version of MyGenericClass exists for Demonstration 2. You’ll go through a detailed discussion of this topic now.

In short, for a parameterized class, there is only one compiled class file. For example, suppose you use ArrayList<Double>, ArrayList<Integer>, or ArrayList<String> in your program. In this case, the type parameters help you to raise compile-time errors if you try to store any unwanted type of object in your container. So, you promote type safety at compile time. But though you use different type parameters in your generic data structures, all these parameterized types use the same compiled class. It is because all the type information is erased at runtime. The process is a little bit complex, but most of the time you do not need to deal with them directly.

As per the Oracle Java documentation, type erasures can work in the following ways:
  • All type parameters in generic types will be replaced with their bounds. For an unbounded type, it will be replaced by Object. As a result, the generated bytecode will contain the ordinary classes, interfaces, and methods.

  • To preserve type safety, type casts will be inserted.

  • It can generate bridge methods to preserve polymorphism in extended generic types.

So, here is the bottom line: Type erasure ensures that you will not create different classes for different parameterized types in the compiled code.

Demonstration 9

Let’s start with a simple case study. Here, I’ll compile the code and then decompile it. To make it simple and straightforward, I am using the javac and javap commands, respectively. I have decompiled this code in a different place in my machine, and this is why I have intentionally removed the package statement in the following demonstration.
import java.util.List;
import java.util.ArrayList;
class Demonstration9 {
      public static void main(String[] args) {
             System.out.println("***Demonstration-9.Examine the type erasures.***");
             List<Integer> myIntList = new ArrayList<Integer>();
             myIntList.add(10);
             myIntList.add(20);
             //myIntList.add("Invalid");//error
             int firstNumber=myIntList.get(0);
             System.out.println("First number is :"+ firstNumber);
             int secondNumber=myIntList.get(1);
             System.out.println("Second number is :"+ secondNumber);
             List<String> myStrList = new ArrayList<String>();
             myStrList.add("Hello");
             myStrList.add(" world !");
             //myStrList.add(30);//error
             String firstString=myStrList.get(0);
             System.out.println("First String is :"+ firstString);
             String secondString=myStrList.get(1);
             System.out.println("Second String is :"+ secondString);
      }
}
Output:
***Demonstration-9.Examine the type erasures.***
First number is :10
Second number is :20
First String is :Hello
Second String is : world !
The output is straightforward and is not important in the upcoming analysis. Let’s decompile the class file (that you got after the compilation process). Here are the snapshots for your reference. It’s a big snap, so I am presenting it in three parts: Figure 12-8, Figure 12-9, and Figure 12-10.
../images/433474_2_En_12_Chapter/433474_2_En_12_Fig8_HTML.jpg
Figure 12-8

Partial snapshot of the decompiled Demonstration9.class (Part I)

../images/433474_2_En_12_Chapter/433474_2_En_12_Fig9_HTML.jpg
Figure 12-9

Partial snapshot of the decompiled Demonstration9.class (Part II)

../images/433474_2_En_12_Chapter/433474_2_En_12_Fig10_HTML.jpg
Figure 12-10

Partial snapshot of the decompiled Demonstration9.class (Part III)

Here are the important points in these snapshots that I want to highlight:
  • Both line number 8 and line number 118 ensure that, once compiled, you get a non-parameterized version of ArrayList. So, when you run your program, those types’ info will not be available.

  • Notice how the type casts are added in line numbers 47, 86, 155, and 194. In lines 47 and 86, you can see the presence of Integer, and in lines 155 and 194, you can see the presence of String.

Raw Types

When you refer to a generic type without specifying the type parameter, you create a raw type. For example, in Demonstration 2, we used the following line of code:
MyGenericClass<Double> myGenericClassDoubleOb = new MyGenericClass<Double>();
But instead of this, if we write something like the following, we create a raw type of MyGenericClass<T>:
// Creating a raw type of MyGenericClass<T>
MyGenericClass rawOb = new MyGenericClass();

So, MyGenericClass is a raw type of MyGenericClass<T>.

The raw types were used to support the legacy code in the pre-generic era. To support backward compatibility, you can assign a parameterized type to a raw type, but when you do the reverse, you will get a warning message like the following:
Type safety: The expression of type MyGenericClass needs unchecked conversion to conform to MyGenericClass<Double>
This is because the compiler does not have sufficient information to ensure the type-safety. Here are some code segments with supporting comments for your reference:
// Creating a MyGenericClass<Double> type object.
MyGenericClass<Double> doubleOb = new MyGenericClass<Double>();
// Creating a raw type of MyGenericClass<T>
MyGenericClass rawOb = new MyGenericClass();
// To support backward compatibility, you can assign a parameterized type //to a raw type
rawOb = doubleOb;// Ok
// But if you assign a raw type to a parameterized type, there is a //warning message.
doubleOb = rawOb;// Warning message
It is also important to note that when you use raw types with parameterized types, you need to concentrate on casting, and you may compromise type safety. For example, in the following code segment, notice that no casting is necessary for d1, but before you use d2, you need to type-cast properly:
// Creating a MyGenericClass<Double> type object.
MyGenericClass<Double> doubleOb = new MyGenericClass<Double>();
double d1 = doubleOb.show(100.5);
// Creating a raw type of MyGenericClass<T>
MyGenericClass rawOb = new MyGenericClass();
doubleOb = rawOb;// Warning message
double d2 = (double) rawOb.show(200.5);// type casting is required

In general, you should try to avoid the use of raw types. Raw types can also create runtime errors.

Demonstration 10

Demonstration 10 is presented here to summarize the prior discussions:
package java2e.chapter12;
//class MyGenericClass<T>  is defined in Demonstration2
class Demonstration10 {
      public static void main(String[] args) {
             System.out.println("***Demonstration-10.Case study with raw types.***");
             // Creating a MyGenericClass<Double> type object.
             MyGenericClass<Double> doubleOb = new MyGenericClass<Double>();
             double d1 = doubleOb.show(100.5);
             System.out.println("The method show returns the double value : " + d1);
             // Creating a raw type of MyGenericClass<T>
             MyGenericClass rawOb = new MyGenericClass();
             // To support backward compatibility, you can assign a parameterized type //to a  raw type
             //rawOb = doubleOb;// Ok
             // But if you assign a raw type to a parameterized type, there //is a warning message
             doubleOb = rawOb;// Warning message
             double d2 = (double) rawOb.show(200.5);// type casting is //required
             System.out.println("The value in d2 is: " + d2);
             // No compile-time error but it'll cause runtime error
             //int i3 = (int) rawOb.show(200.5);
             //System.out.println("The value in i3 is: " + i3);
      }
}
Output:
***Demonstration-10.Case study with raw types.***
The method show returns the double value : 100.5
The value in d2 is: 200.5

Q&A Session

12.7 Are interfaces raw types?

No. JLS11 confirms that a non-generic class or an interface is not a raw type.

12.8 “Raw types can also create runtime errors ”—can you please elaborate?

Notice the following few lines in Demonstration 10. You will get runtime errors.
             // No compile-time error but it'll cause runtime error
             // int i3 = (int) rawOb.show(200.5);
             // System.out.println("The value in i3 is: " + i3);
If you uncomment the last two lines and compile the program, there is no error. But when you run the program, you will get the following error messages:
***Demonstration-10.Case study with raw types.***
The method show returns the double value : 100.5
The value in d2 is: 200.5
Exception in thread "main" java.lang.ClassCastException: java.lang.Double cannot be cast to java.lang.Integer
      at java2e.chapter12.Demonstration10.main(Demonstration10.java:23)

Note

JLS 11 says the following: “The use of raw types is allowed only as a concession to compatibility of legacy code. The use of raw types in code written after the introduction of generics into the Java programming language is strongly discouraged. It is possible that future versions of the Java programming language will disallow the use of raw types.”

Type Inference Using Diamond Operator

You can make use of a pair of angle brackets (called the diamond operator) to replace the type arguments that are required to invoke the generic class constructor as long as the compiler can infer it properly. For example, if you append the following lines of code to the prior demonstration, there is no warning message for you:
//JDK7 onwards, you can use a short syntax using diamond operator
MyGenericClass<Double> doubleOb2 = new MyGenericClass<>();
doubleOb=doubleOb2;//No warning message
But you can remember that, in demonstration 10, when you used the following line:
doubleOb = rawOb;

there was a warning message for you. So, you can use the diamond operator to type less (i.e., you can shorten long declaration statements), and you can also ensure that you are NOT creating a raw type. Since JDK7, this functionality has been available in Java. Lastly, though you can use this concept in method calls, Oracle suggests you primarily use the diamond operator for variable declarations. Personally, I like full-syntax declarations for better readability and understanding, and it allows me to execute my code properly prior to JDK7.

Applying Inheritance

You can apply the concept of inheritance in your generic program; i.e., you can subtype a generic class by extending it, or you can subtype an interface by implementing it. The only restriction is that you should not vary the argument.

Demonstration 11

For example, consider Demonstration 11 with output.
package java2e.chapter12;
//class MyGenericClass<T>  is defined in Demonstration2
//Compile-time error
//class SubClass<V> extends MyGenericClass<T> {
//The following declaration is fine
class SubClass<T> extends MyGenericClass<T> {
      //Some code
}
class Demonstration11 {
      public static void main(String[] args) {
             System.out.println("***Demonstration-11.Inheritance in Generic Programming.***");
             SubClass<Integer>  subInt = new SubClass<Integer>();
             System.out.println("The method show returns the interger value : " + subInt.show(200));
      }
}
Output:
***Demonstration-11.Inheritance in Generic Programming.***
The method show returns the interger value : 200
The previous output is obvious. But notice the commented line:
//class SubClass<V> extends MyGenericClass<T> {

If you use this line of code, you will receive a compile-time error. This is because, as per the language construct, you should not vary the argument. Your subclass must pass the type argument that is needed in its super class.

When you create a subtype, you can also add a subclass-specific method. For example, in this modified example, SubClass introduces a new type parameter and a subclass-specific method. Let’s go through the following program and its modified output.
package java2e.chapter12;
//class MyGenericClass<T>  is defined in Demonstration2
//For modified program
class SubClass<T,V> extends MyGenericClass<T> {
      //Subclass-specific method
      public V subMethod(V value) {
             return value;
      }
}
class Demonstration11 {
      public static void main(String[] args) {
             //System.out.println("***Demonstration-11.Inheritance in Generic Programming.***");
             System.out.println("***Demonstration-11 Modified.Inheritance in Generic Programming.***");
             //For modified program
             SubClass<Integer, String>  subInt = new SubClass<Integer,String>();
             System.out.println("The method show returns the integer value : " + subInt.show(200));
             System.out.println("The subMethod returns : " + subInt.subMethod("It is ok!"));
      }
}
Modified output:
***Demonstration-11 Modified.Inheritance in Generic Programming.***
The method show returns the integer value : 200
The subMethod returns : It is ok!

Bridge Method

An interesting situation may occur where the complier needs to add a method to a class. This method is called a bridge method . In general, you do not need to deal with this case directly.

To understand the scenario, let’s go through the following code segment:
class GenericClass12<T> {
      public void show(T value) {
             System.out.println("Inside parent class.The value is:"+value);
      }
}
class SubClass12 extends GenericClass12<Integer> {
      @Override
      public void show(Integer value) {
             System.out.println("Inside Child Class.The value is:"+value);
      }
}
Have you noticed the important characteristics? Let’s analyze.
  • The derived class Subclass12 extends from the Integer specific version of GenericClass12; i.e., GenericClass12<Integer>.

  • The derived class SubClass12 also overrides the parent method show().

Though this type of coding is allowed, a problem may arise when type erasure comes into play. Once type erasure performs its job, the expected form of the show() method in GenericClass12 is as follows:
public void show(Object value){//other code..}
And the expected form of the show() method in SubClass12 is:
public void show()(Integer value){//other code}
So, after the type erasure’s action, the method signatures do not match. To handle the situation and preserve polymorphism, the compiler generates a bridge method in Subclass12, with the preceding signature that can call the Integer-specific version. So, the new method show() will appear in the derived class (i.e., in SubClass12) as follows:
public void show(Object value) {
      show((Integer) value);
}

Demonstration 12

Now, consider Demonstration 12 and its corresponding output. Then go through the analysis (after the output section) to experience the presence of the generated bridge method.
package java2e.chapter12;
class GenericClass12<T> {
      public void show(T value) {
             System.out.println("Inside parent class.The value is:"+value);
      }
}
class SubClass12 extends GenericClass12<Integer> {
      @Override
      public void show(Integer value) {
             System.out.println("Inside Child Class.The value is:"+value);
      }
}
class Demonstration12 {
      public static void main(String[] args) {
             System.out.println("***Demonstration-12.Bridge Method in Generic Programming.***");
             // Creating a MyGenericClass<Integer> type object.
             GenericClass12<Integer> parentOb = new GenericClass12<Integer>();
             parentOb.show(100);
             // A SubClass12 object
             SubClass12 childOb = new SubClass12();
             childOb.show(300);
             //Object ob=(int)400;
             //childOb.show(ob);//Error
             //Using Polymorphism
             System.out.println("Using Ploymorphism :" );
             parentOb=childOb;
             parentOb.show(500);
      }
}
Output:
***Demonstration-12.Bridge Method in Generic Programming.***
Inside parent class.The value is:100
Inside Child Class.The value is:300
Using Ploymorphism :
Inside Child Class.The value is:500
When I used the javap command , I could see that both methods were present in SubClass12 like in Figure 12-11. (The precise output may vary based on your Java version.)
../images/433474_2_En_12_Chapter/433474_2_En_12_Fig11_HTML.jpg
Figure 12-11

Snapshot of the decompiled SubClass12.class

12.9 Why is the bridge method needed?

You have already seen that after type erasure’s action, the method signatures in the parent class and its child class did not match, and so it affects the concept of polymorphism. To address these issues, the bridge method is useful.

In this context, notice the last few lines of code in Demonstration 12. This portion of code demonstrates how the concept of polymorphism is preserved.

Important Restrictions in Generic Programming

There are many restrictions associated with generic programming. You’ll become familiar with them only upon practice. Let’s finish the chapter with a discussion of some common restrictions.

Don’t Instantiate Generic Types with Primitive Types

In Demonstration 2, you saw that the following declaration is NOT legal in generic programming in Java. So, the following code segment
//Erroneous code segment-1
//Primitive types are NOT allowed here.It must be a reference type.
MyGenericClass<int> myGenericClassIntOb2 = new MyGenericClass<int>();
will raise a compile-time error: Syntax error, insert "Dimensions" to complete ReferenceType.

Your Generic Class Cannot Subclass Directly or Indirectly from Throwable

JLS11 confirms that JVM’s catch mechanism is compatible with the non-generic classes only. So, the following code segment
//Erroneous code segment-2
class CustomException<T> extends Throwable{  }

will raise a compile-time error: The generic class CustomException<T> may not subclass java.lang.Throwable.

You Cannot Overload a Method Where the Formal Parameter Types of Each Overload Are Erased to the Same Raw Type

The following code segments
//Erroneous code segment-3
class OverloadRestriction {
public void printMe(List<Integer> intList) {//Some code }
public void printMe(List<String> strList) {//Some code }
}
will raise two compile-time errors:
Erasure of method printMe(List<Integer>) is the same as another method in type OverloadRestriction
and
Erasure of method printMe(List<String>) is the same as another method in type OverloadRestriction

Static Field Type Parameter Is Not Allowed in Your Generic Class

The following code segments
//Erroneous code segment-4
class MyDevice<T> {
      private static T operatingSystem;// 4.1 Compile-time error
      // 4.2 Compile-time error
      /* public static T getOperatingSystem() {
             // some code
      }*/
}

will raise a compile-time error: Cannot make a static reference to the non-static type T.

You get the same error if you try to use a static method, which is shown in commented lines.

You Cannot Instantiate the Type Parameters In Your Generic Class

The following code segments
//Erroneous code segment-5
class GenericClass<T> {
      T genericObject;
      GenericClass() {
             //5.Compile-time error
             genericObject = new T();
      }
}

will raise a compile-time error: Cannot instantiate the type T.

One Final Suggestion

In this chapter, you have gone through the fundamentals of generic programming. But ultimate mastery will come upon repeated practice. Before I finish this chapter, I suggest you take note that when you write your code you should pay special attention to the subtype in generic programming.

Consider an example. Suppose there are two concrete types—TypeA and TypeB. GenericClass<TypeA> is in no way related to GenericClass<TypeB> regardless of whether TypeA and TypeB are related. Object is the common parent for both GenericClass<TypeA> and GenericClass<TypeB>.

For example, you have used the Integer class several times in various examples. But if you go through its original definition, you’ll see this:
public final class Integer extends Number implements Comparable<Integer> {
//some code
}

This basically says that the Integer class extends from the Number class. But following the prior suggestion, you can conclude that there is no relationship between GenericClass<Number> and GenericClass<Integer>.

Summary

This chapter discussed the following:
  • What is a generic program?

  • Why are generics important in Java?

  • What are the advantages of generic programming over non-generic programming?

  • How can you use wildcards in generic programming?

  • What are the different types of wildcards and how do you use them?

  • What is a bounded type parameter? How is it different from wildcards?

  • How can you use bounded type parameters in your generic program?

  • What is an erasure and how does it work?

  • What is a bridge method? How does it work?

  • Why are bridge methods helpful?

  • What is a raw type?

  • How can you use the diamond operator and make the syntax short?

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.117.119.206