Chapter: Java The Complete Reference - The Java Language - Generics

Study Material, Lecturing Notes, Assignment, Reference, Wiki description explanation, brief detail

Generic Class Hierarchies - Java

Generic classes can be part of a class hierarchy in just the same way as a non-generic class. Thus, a generic class can act as a superclass or be a subclass.

Generic Class Hierarchies

 

Generic classes can be part of a class hierarchy in just the same way as a non-generic class. Thus, a generic class can act as a superclass or be a subclass. The key difference between generic and non-generic hierarchies is that in a generic hierarchy, any type arguments needed by a generic superclass must be passed up the hierarchy by all subclasses. This is similar to the way that constructor arguments must be passed up a hierarchy.

 

Using a Generic Superclass

 

Here is a simple example of a hierarchy that uses a generic superclass:

 

// A simple generic class hierarchy.

 

class Gen<T> {

 

T ob;

 

Gen(T o) { ob = o;

 

}

 

// Return ob.

T getob() { return ob;

 

}

 

}

 

// A subclass of Gen.

 

class Gen2<T> extends Gen<T> { Gen2(T o) {

 

super(o);

 

}

 

}

 

In this hierarchy, Gen2 extends the generic class Gen. Notice how Gen2 is declared by the following line:

 

class Gen2<T> extends Gen<T> {

 

The type parameter T is specified by Gen2 and is also passed to Gen in the extends clause. This means that whatever type is passed to Gen2 will also be passed to Gen. For example, this declaration,

 

Gen2<Integer> num = new Gen2<Integer>(100);

 

passes Integer as the type parameter to Gen. Thus, the ob inside the Gen portion of Gen2 will be of type Integer.

Notice also that Gen2 does not use the type parameter T except to support the Gen superclass. Thus, even if a subclass of a generic superclass would otherwise not need to be generic, it still must specify the type parameter(s) required by its generic superclass.

 

Of course, a subclass is free to add its own type parameters, if needed. For example, here is a variation on the preceding hierarchy in which Gen2 adds a type parameter of its own:

 

 

// A subclass can add its own type parameters.

class Gen<T> {

 

T ob; // declare an object of type T

 

//Pass the constructor a reference to an object of type T.

Gen(T o) { ob = o;

 

}

 

// Return ob.

T getob() { return ob;

 

}

 

}

 

     //A subclass of Gen that defines a second type parameter, called V.

 

class Gen2<T, V> extends Gen<T> { V ob2;

 

Gen2(T o, V o2) { super(o);

 

ob2 = o2;

 

}

 

V getob2() { return ob2;

 

}

 

}

 

// Create an object of type Gen2.

class HierDemo {

 

public static void main(String args[]) {

 

// Create a Gen2 object for String and Integer. Gen2<String, Integer> x =

 

new Gen2<String, Integer>("Value is: ", 99);

 

System.out.print(x.getob());

 

System.out.println(x.getob2());

 

}

 

}

 

Notice the declaration of this version of Gen2, which is shown here:

 

class Gen2<T, V> extends Gen<T> {

 

Here, T is the type passed to Gen, and V is the type that is specific to Gen2. V is used to declare an object called ob2, and as a return type for the method getob2( ). In main( ), a Gen2 object is created in which type parameter T is String, and type parameter V is Integer. The program displays the following, expected, result:

 

Value is: 99

 

A Generic Subclass

 

It is perfectly acceptable for a non-generic class to be the superclass of a generic subclass. For example, consider this program:

 

    //A non-generic class can be the superclass of a generic subclass.

 

    //A non-generic class.

 

class NonGen { int num;

 

NonGen(int i) { num = i;

 

}

 

int getnum() { return num;

 

}

 

}

 

// A generic subclass.

 

class Gen<T> extends NonGen {

 

T ob; // declare an object of type T

 

     //Pass the constructor a reference to an object of type T.

 

Gen(T o, int i) { super(i);

 

ob = o;

}

// Return ob.

T getob() {  

return ob;   

}

}

// Create a Gen    object.

class HierDemo2    {

public static void main(String args[]) {

// Create a   Gen object for String.

Gen<String>   w = new Gen<String>("Hello", 47);

System.out.print(w.getob() + " "); System.out.println(w.getnum());

 

}

 

}

The output from the program is shown here:

 

Hello 47

 

In the program, notice how Gen inherits NonGen in the following declaration:

 

class Gen<T> extends NonGen {

 

Because NonGen is not generic, no type argument is specified. Thus, even though Gen declares the type parameter T, it is not needed by (nor can it be used by) NonGen. Thus, NonGen is inherited by Gen in the normal way. No special conditions apply.

 

Run-Time Type Comparisons Within a Generic Hierarchy

 

Recall the run-time type information operator instanceof that was described in Chapter 13. As explained, instanceof determines if an object is an instance of a class. It returns true if an object is of the specified type or can be cast to the specified type. The instanceof operator can be applied to objects of generic classes. The following class demonstrates some of the type compatibility implications of a generic hierarchy:

 

// Use the instanceof operator with a generic class hierarchy.

class Gen<T> {

 

T ob;

 

Gen(T o) { ob = o;

 

}

 

// Return ob.

T getob() { return ob;

 

}

 

}

 

// A subclass of Gen.

 

class Gen2<T> extends Gen<T> { Gen2(T o) {

 

super(o);

 

}

 

}

 

     //Demonstrate run-time type ID implications of generic class hierarchy.

 

class HierDemo3 {

 

public static void main(String args[]) {

 

     //Create a Gen object for Integers.

     Gen<Integer> iOb = new Gen<Integer>(88);

 

//Create a Gen2 object for Integers.

Gen2<Integer> iOb2 = new Gen2<Integer>(99);

 

 

//Create a Gen2 object for Strings.

 

Gen2<String> strOb2 = new Gen2<String>("Generics Test");

 

     //See if iOb2 is some form of Gen2.

     if(iOb2 instanceof Gen2<?>)

 

System.out.println("iOb2 is instance of Gen2");

 

     //See if iOb2 is some form of Gen.

 

if(iOb2 instanceof Gen<?>)

System.out.println("iOb2 is instance of Gen");

 

System.out.println();

 

     See if strOb2 is a Gen2. if(strOb2 instanceof Gen2<?>)

 

System.out.println("strOb2 is instance of Gen2");

 

     See if strOb2 is a Gen.

 

if(strOb2 instanceof Gen<?>)

System.out.println("strOb2 is instance of Gen");

 

System.out.println();

 

     //See if iOb is an instance of Gen2, which it is not.

     if(iOb instanceof Gen2<?>)

 

System.out.println("iOb is instance of Gen2");

 

     //See if iOb is an instance of Gen, which it is.

     if(iOb instanceof Gen<?>)

 

System.out.println("iOb is instance of Gen");

 

     //The following can't be compiled because generic type info does not exist at run time.

 

     if(iOb2 instanceof Gen2<Integer>)

 

     System.out.println("iOb2 is instance of Gen2<Integer>");

 

}

 

}

 

The output from the program is shown here:

 

iOb2 is instance of Gen2

iOb2 is instance of Gen

 

strOb2 is instance of Gen2

strOb2 is instance of Gen

 

iOb is instance of Gen

In this program, Gen2 is a subclass of Gen, which is generic on type parameter T. In main( ), three objects are created. The first i s iOb, which is an object of type Gen<Integer>. The second is iOb2, which is an instance of Gen2<Integer>. Finally, strOb2 is an object of type Gen2<String>.

Then, the program performs these instanceof tests on the type of iOb2:

 

     See if iOb2 is some form of Gen2. if(iOb2 instanceof Gen2<?>)

 

System.out.println("iOb2 is instance of Gen2");

 

     //See if iOb2 is some form of Gen.

 

 

if(iOb2 instanceof Gen<?>)

System.out.println("iOb2 is instance of Gen");

 

As the output shows, both succeed. In the first test, iOb2 is checked against Gen2<?>. This test succeeds because it simply confirms that iOb2 is an object of some type of Gen2 object. The use of the wildcard enables instanceof to determine if iOb2 is an object of any type of Gen2. Next, iOb2 is tested against Gen<?>, the superclass type. This is also true because iOb2 is some form of Gen, the superclass. The next few lines in main( ) show the same sequence (and same results) for strOb2.

 

Next, iOb, which is an instance of Gen<Integer> (the superclass), is tested by these lines:

 

     See if iOb is an instance of Gen2, which it is not.

     if(iOb instanceof Gen2<?>)

 

System.out.println("iOb is instance of Gen2");

 

     //See if iOb is an instance of Gen, which it is.

     if(iOb instanceof Gen<?>)

 

System.out.println("iOb is instance of Gen");

 

The first if fails because iOb is not some type of Gen2 object. The second test succeeds because iOb is some type of Gen object.

Now, look closely at these commented-out lines:

 

     //The following can't be compiled because

 

     //generic type info does not exist at run time.

 

     if(iOb2 instanceof Gen2<Integer>)

 

     System.out.println("iOb2 is instance of Gen2<Integer>");

 

As the comments indicate, these lines can’t be compiled because they attempt to compare iOb2 with a specific type of Gen2, in this case, Gen2<Integer>. Remember, there is no generic type information available at run time. Therefore, there is no way for instanceof to know if iOb2 is an instance of Gen2<Integer> or not.

 

Casting

 

You can cast one instance of a generic class into another only if the two are otherwise compatible and their type arguments are the same. For example, assuming the foregoing program, this cast is legal:

 

(Gen<Integer>) iOb2 // legal

 

because iOb2 includes an instance of Gen<Integer>. But, this cast:

(Gen<Long>) iOb2 // illegal is not legal because iOb2 is not an instance of Gen<Long>.

Overriding Methods in a Generic Class

 

A method in a generic class can be overridden just like any other method. For example, consider this program in which the method getob( ) is overridden:

 

// Overriding a generic method in a generic class.

class Gen<T> {

 

T ob; // declare an object of type T

 

     //Pass the constructor a reference to an object of type T.

 

Gen(T o) { ob = o;

 

}

 

// Return ob. T getob() {

 

System.out.print("Gen's getob(): " ); return ob;

 

}

 

}

 

// A subclass of Gen that overrides getob().

class Gen2<T> extends Gen<T> {

 

Gen2(T o) { super(o);

 

}

 

// Override getob().

T getob() {

 

System.out.print("Gen2's getob(): "); return ob;

 

}

 

}

 

// Demonstrate generic method override.

class OverrideDemo {

 

public static void main(String args[]) {

 

     //Create a Gen object for Integers.

     Gen<Integer> iOb = new Gen<Integer>(88);

 

     //Create a Gen2 object for Integers.

 

     Gen2<Integer> iOb2 = new Gen2<Integer>(99);

 

     Create a Gen2 object for Strings.

 

Gen2<String> strOb2 = new Gen2<String> ("Generics Test");

 

System.out.println(iOb.getob());

 

System.out.println(iOb2.getob());

 

System.out.println(strOb2.getob());

 

}

The output is shown here:

 

Gen's getob(): 88

 

Gen2's getob(): 99

 

Gen2's getob(): Generics Test

 

As the output confirms, the overridden version of getob( ) is called for objects of type Gen2, but the superclass version is called for objects of type Gen.


Study Material, Lecturing Notes, Assignment, Reference, Wiki description explanation, brief detail


Copyright © 2018-2020 BrainKart.com; All Rights Reserved. Developed by Therithal info, Chennai.