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.
Related Topics
Privacy Policy, Terms and Conditions, DMCA Policy and Compliant
Copyright © 2018-2023 BrainKart.com; All Rights Reserved. Developed by Therithal info, Chennai.