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

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

Using Wildcard Arguments

As useful as type safety is, sometimes it can get in the way of perfectly acceptable constructs.

Using Wildcard Arguments

 

As useful as type safety is, sometimes it can get in the way of perfectly acceptable constructs. For example, given the Stats class shown at the end of the preceding section, assume that you want to add a method called sameAvg( ) that determines if two Stats objects contain arrays that yield the same average, no matter what type of numeric data each object holds. For example, if one object contains the double values 1.0, 2.0, and 3.0, and the other object contains the integer values 2, 1, and 3, then the averages will be the same. One way to implement sameAvg( ) is to pass it a Stats argument, and then compare the average of that argument against the invoking object, returning true only if the averages are the same. For example, you want to be able to call sameAvg( ), as shown here:

 

Integer inums[] = { 1, 2, 3, 4, 5 };

 

Double dnums[] = { 1.1, 2.2, 3.3, 4.4, 5.5 };

 

Stats<Integer> iob = new Stats<Integer>(inums);

 

Stats<Double> dob = new Stats<Double>(dnums);

 

if(iob.sameAvg(dob)) System.out.println("Averages are the same.");

 

else

 

System.out.println("Averages differ.");

 

    At first, creating sameAvg( ) seems like an easy problem. Because Stats is generic and its average( ) method can work on any type of Stats object, it seems that creating sameAvg( ) would be straightforward. Unfortunately, trouble starts as soon as you try to declare a parameter of type Stats. Because Stats is a parameterized type, what do you specify for Stats’ type parameter when you declare a parameter of that type?

 

At first, you might think of a solution like this, in which T is used as the type parameter:

 

     //This won't work!

 

     //Determine if two averages are the same.

 

     boolean sameAvg(Stats<T> ob) {

 

if(average() == ob.average()) return true;

 

return false;

 

}

 

The trouble with this attempt is that it will work only with other Stats objects whose type is the same as the invoking object. For example, if the invoking object is of type Stats<Integer>, then the parameter ob must also be of type Stats<Integer>. It can’t be used to compare the average of an object of type Stats<Double> with the average of an object of type Stats<Short>, for example. Therefore, this approach won’t work except in a very narrow context and does not yield a general (that is, generic) solution.

To create a generic sameAvg( ) method, you must use another feature of Java generics: the wildcard argument. The wildcard argument is specified by the ?, and it represents an unknown type. Using a wildcard, here is one way to write the sameAvg( ) method:

 

     //Determine if two averages are the same.

 

     //Notice the use of the wildcard.

 

boolean sameAvg(Stats<?> ob) { if(average() == ob.average())

 

return true;

 

return false;

 

}

 

Here, Stats<?> matches any Stats object, allowing any two Stats objects to have their averages compared. The following program demonstrates this:

 

// Use a wildcard.

 

class Stats<T extends Number> {

 

T[] nums; // array of Number or subclass

 

     //Pass the constructor a reference to an array of type Number or subclass.

     Stats(T[] o) {

 

nums = o;

 

}

 

     //Return type double in all cases.

     double average() {

 

double sum = 0.0;

 

for(int i=0; i < nums.length; i++)

sum += nums[i].doubleValue();

return sum / nums.length;

 

}

 

     //Determine if two averages are the same.

 

     //Notice the use of the wildcard.

 

     boolean sameAvg(Stats<?> ob) {

 

if(average() == ob.average()) return true;

 

return false;

 

}

 

}

 

// Demonstrate wildcard.

class WildcardDemo {

 

public static void main(String args[]) {

Integer inums[] = { 1, 2, 3, 4, 5 };

Stats<Integer> iob = new Stats<Integer>(inums);

double v = iob.average();

System.out.println("iob average is " + v);

 

Double dnums[] = { 1.1, 2.2, 3.3, 4.4, 5.5 };

Stats<Double> dob = new Stats<Double>(dnums);

double w = dob.average();

System.out.println("dob average is " + w);

 

Float fnums[] = { 1.0F, 2.0F, 3.0F, 4.0F, 5.0F };

Stats<Float> fob = new Stats<Float>(fnums);

double x = fob.average();

System.out.println("fob average is " + x);

 

// See which arrays have same average.

System.out.print("Averages of iob and dob ");

if(iob.sameAvg(dob))

 

System.out.println("are the same."); else

 

System.out.println("differ.");

 

System.out.print("Averages of iob and fob "); if(iob.sameAvg(fob))

 

System.out.println("are the same."); else

 

System.out.println("differ.");

 

}

 

}

 

The output is shown here:

 

iob average is 3.0

dob average is 3.3

fob average is 3.0

Averages of iob and dob differ.

Averages of iob and fob are the same.

One last point: It is important to understand that the wildcard does not affect what type of Stats objects can be created. This is governed by the extends clause in the Stats declaration. The wildcard simply matches any valid Stats object.

 

Bounded Wildcards

 

Wildcard arguments can be bounded in much the same way that a type parameter can be bounded. A bounded wildcard is especially important when you are creating a generic type that will operate on a class hierarchy. To understand why, let’s work through an example. Consider the following hierarchy of classes that encapsulate coordinates:

 

// Two-dimensional coordinates.

class TwoD {

 

int x, y;

 

TwoD(int a, int b) { x = a;

 

y = b;

 

}

 

}

 

// Three-dimensional coordinates.

class ThreeD extends TwoD {

int z;

 

ThreeD(int a, int b, int c) { super(a, b);

 

z = c;

 

}

 

}

 

// Four-dimensional coordinates.

class FourD extends ThreeD {

int t;

 

FourD(int a, int b, int c, int d) { super(a, b, c);

 

t = d;

 

}

 

}

 

At the top of the hierarchy is TwoD, which encapsulates a two-dimensional, XY coordinate. TwoD is inherited by ThreeD, which adds a third dimension, creating an XYZ coordinate. ThreeD is inherited by FourD, which adds a fourth dimension (time), yielding a four-dimensional coordinate.

Shown next is a generic class called Coords, which stores an array of coordinates:

 

// This class holds an array of coordinate objects.

class Coords<T extends TwoD> {

 

T[] coords;

 

Coords(T[] o) { coords = o; }

 

}

Notice that Coords specifies a type parameter bounded by TwoD. This means that any array stored in a Coords object will contain objects of type TwoD or one of its subclasses.

Now, assume that you want to write a method that displays the X and Y coordinates for each element in the coords array of a Coords object. Because all types of Coords objects have at least two coordinates (X and Y), this is easy to do using a wildcard, as shown here:

 

static void showXY(Coords<?> c) {

System.out.println("X Y Coordinates:");

for(int i=0; i < c.coords.length; i++)

 

System.out.println(c.coords[i].x + " " + c.coords[i].y);

 

System.out.println();

 

}

Because Coords is a bounded generic type that specifies TwoD as an upper bound, all objects that can be used to create a Coords object will be arrays of type TwoD, or of classes derived from TwoD. Thus, showXY( ) can display the contents of any Coords object.

 

However, what if you want to create a method that displays the X, Y, and Z coordinates of a ThreeD or FourD object? The trouble is that not all Coords objects will have three coordinates, because a Coords<TwoD> object will only have X and Y. Therefore, how do you write a method that displays the X, Y, and Z coordinates for Coords<ThreeD> and Coords<FourD> objects, while preventing that method from being used with Coords<TwoD> objects? The answer is the bounded wildcard argument.

A bounded wildcard specifies either an upper bound or a lower bound for the type argument. This enables you to restrict the types of objects upon which a method will operate. The most common bounded wildcard is the upper bound, which is created using an extends clause in much the same way it is used to create a bounded type.

Using a bounded wildcard, it is easy to create a method that displays the X, Y, and Z coordinates of a Coords object, if that object actually has those three coordinates. For example, the following showXYZ( ) method shows the X, Y, and Z coordinates of the elements stored in a Coords object, if those elements are actually of type ThreeD (or are derived from ThreeD):

 

 

static void showXYZ(Coords<? extends ThreeD> c) {

System.out.println("X Y Z Coordinates:");

for(int i=0; i < c.coords.length; i++)

 

System.out.println(c.coords[i].x + " " + c.coords[i].y + " " + c.coords[i].z);

 

System.out.println();

 

}

 

Notice that an extends clause has been added to the wildcard in the declaration of parameter c. It states that the ? can match any type as long as it is ThreeD, or a class derived from ThreeD. Thus, the extends clause establishes an upper bound that the ? can match. Because of this bound, showXYZ( ) can be called with references to objects of type

Coords<ThreeD> or Coords<FourD>, but not with a reference of type Coords<TwoD>. Attempting to call showXZY( ) with a Coords<TwoD> reference results in a compile-time error, thus ensuring type safety.

Here is an entire program that demonstrates the actions of a bounded wildcard argument:

 

     //Bounded Wildcard arguments.

 

     Two-dimensional coordinates.

 

     class TwoD {

 

int x, y;

 

TwoD(int a, int b) { x = a;

 

y = b;

 

}

 

}

 

// Three-dimensional coordinates.

class ThreeD extends TwoD {

int z;

 

ThreeD(int a, int b, int c) { super(a, b);

 

z = c;

 

}

 

}

 

// Four-dimensional coordinates.

class FourD extends ThreeD {

int t;

 

FourD(int a, int b, int c, int d) { super(a, b, c);

 

t = d;

 

}

 

}

 

// This class holds an array of coordinate objects.

class Coords<T extends TwoD> {

 

T[] coords;

 

Coords(T[] o) { coords = o; }

 

}

 

// Demonstrate a bounded wildcard.

class BoundedWildcard {

 

static void showXY(Coords<?> c) {

System.out.println("X Y Coordinates:");

for(int i=0; i < c.coords.length; i++)

 

System.out.println(c.coords[i].x + " " + c.coords[i].y);

 

System.out.println();

 

}

 

static void showXYZ(Coords<? extends ThreeD> c) {

 System.out.println("X Y Z Coordinates:");

for(int i=0; i < c.coords.length; i++)

System.out.println(c.coords[i].x + " " + c.coords[i].y + " " + c.coords[i].z);

 

System.out.println();

 

}

 

static void showAll(Coords<? extends FourD> c) {

 System.out.println("X Y Z T Coordinates:");

for(int i=0; i < c.coords.length; i++)

 

System.out.println(c.coords[i].x + " " + c.coords[i].y + " " + c.coords[i].z + " " + c.coords[i].t);

System.out.println();

 

}

 

public static void main(String args[]) { TwoD td[] = {

 

new TwoD(0, 0), new TwoD(7, 9), new TwoD(18, 4), new TwoD(-1, -23)

};

 

Coords<TwoD> tdlocs = new Coords<TwoD>(td);

 

System.out.println("Contents of tdlocs.");

showXY(tdlocs); // OK, is a TwoD

 

     showXYZ(tdlocs); // Error, not a ThreeD

 

     showAll(tdlocs); // Error, not a FourD

 

// Now, create some FourD objects.

FourD     fd[] = {

new  FourD(1, 2, 3, 4),

new  FourD(6, 8, 14, 8),

new  FourD(22, 9, 4, 9),

new  FourD(3, -2, -23, 17)

};  

Coords<FourD> fdlocs = new Coords<FourD>(fd);

 

System.out.println("Contents of fdlocs."); // These are all OK.

 

showXY(fdlocs);

 

showXYZ(fdlocs);

 

showAll(fdlocs);

 

}

 

}

 

The output from the program is shown here:

 

Chapter 14   Generics 355

 

 

 

 

 

Part I

 

 

Contents of tdlocs. X Y Coordinates:

 

0 0

 

7 9

 

18 4 -1 -23

 

Contents of fdlocs. X Y Coordinates:

 

1 2

 

6 8

 

22 9

 

3 -2

X Y       Z Coordinates:

1 2 3        

6 8 14       

22   9    4        

3 -2 -23 

X Y Z T Coordinates:

1 2 3 4      

6 8 14    8   

22   9    4    9   

3 -2 -23  17

Notice these commented-out lines:

 

     showXYZ(tdlocs); // Error, not a ThreeD

 

     showAll(tdlocs); // Error, not a FourD

 

Because tdlocs is a Coords(TwoD) object, it cannot be used to call showXYZ( ) or showAll( ) because bounded wildcard arguments in their declarations prevent it. To prove this to yourself, try removing the comment symbols, and then attempt to compile the program. You will receive compilation errors because of the type mismatches.

 

In general, to establish an upper bound for a wildcard, use the following type of wildcard expression:

 

<? extends superclass>

 

where superclass is the name of the class that serves as the upper bound. Remember, this is an inclusive clause because the class forming the upper bound (that is, specified by superclass) is also within bounds.

 

You can also specify a lower bound for a wildcard by adding a super clause to a wildcard declaration. Here is its general form:

 

<? super subclass>

 

In this case, only classes that are superclasses of subclass are acceptable arguments. This is an inclusive clause.

 


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


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