System, but they may not be reproduced for publication



Yüklə 83 Mb.
Pdf görüntüsü
səhifə58/82
tarix19.04.2023
ölçüsü83 Mb.
#106251
1   ...   54   55   56   57   58   59   60   61   ...   82
Java A Beginner’s Guide, Eighth Edition ( PDFDrive )

class class­name { // ...
Here is the full syntax for declaring a reference to a generic class and creating a generic
instance:
BOUNDED TYPES
In the preceding examples, the type parameters could be replaced by any class type.
This is fine for many purposes, but sometimes it is useful to limit the types that can be


passed to a type parameter. For example, assume that you want to create a generic class
that stores a numeric value and is capable of performing various mathematical
functions, such as computing the reciprocal or obtaining the fractional component.
Furthermore, you want to use the class to compute these quantities for any type of
number, including integers, floats, and doubles. Thus, you want to specify the type of
the numbers generically, using a type parameter. To create such a class, you might try
something like this:
Unfortunately, NumericFns will not compile as written because both methods will
generate compile­time errors. First, examine the reciprocal( ) method, which
attempts to return the reciprocal of num. To do this, it must divide 1 by the value of
num. The value of num is obtained by calling doubleValue( ), which obtains the
double version of the numeric object stored in num. Because all numeric classes, such
as Integer and Double, are subclasses of Number, and Number defines the
doubleValue( ) method, this method is available to all numeric wrapper classes. The
trouble is that the compiler has no way to know that you are intending to create
NumericFns objects using only numeric types. Thus, when you try to compile


NumericFns, an error is reported that indicates that the doubleValue( ) method is
unknown. The same type of error occurs twice in fraction( ), which needs to call both
doubleValue( ) and intValue( ). Both calls result in error messages stating that
these methods are unknown. To solve this problem, you need some way to tell the
compiler that you intend to pass only numeric types to T. Furthermore, you need some
way to ensure that only numeric types are actually passed.
To handle such situations, Java provides bounded types. When specifying a type
parameter, you can create an upper bound that declares the superclass from which all
type arguments must be derived. This is accomplished through the use of an extends
clause when specifying the type parameter, as shown here:
<T extends superclass>
This specifies that T can be replaced only by superclass, or subclasses of superclass.
Thus, superclass defines an inclusive, upper limit.
You can use an upper bound to fix the NumericFns class shown earlier by specifying
Number as an upper bound, as shown here:


The output is shown here:


Notice how NumericFns is now declared by this line:
Because the type T is now bounded by Number, the Java compiler knows that all
objects of type T can call doubleValue( ) because it is a method declared by
Number. This is, by itself, a major advantage. However, as an added bonus, the
bounding of T also prevents nonnumeric NumericFns objects from being created. For
example, if you remove the comments from the line at the end of the program, and then
try re­compiling, you will receive compile­time errors because String is not a subclass
of Number.
Bounded types are especially useful when you need to ensure that one type parameter is
compatible with another. For example, consider the following class called Pair, which
stores two objects that must be compatible with each other:
Notice that Pair uses two type parameters, T and V, and that V extends T. This means
that V will either be the same as T or a subclass of T. This ensures that the two
arguments to Pair’s constructor will be objects of the same type or of related types. For
example, the following constructions are valid:
However, the following is invalid:


In this case, String is not a subclass of Number, which violates the bound specified by
Pair.
USING WILDCARD ARGUMENTS
As useful as type safety is, sometimes it can get in the way of perfectly acceptable
constructs. For example, given the NumericFns class shown at the end of the
preceding section, assume that you want to add a method called absEqual( ) that
returns true if two NumericFns objects contain numbers whose absolute values are
the same. Furthermore, you want this method to be able to work properly no matter
what type of number each object holds. For example, if one object contains the Double
value 1.25 and the other object contains the Float value –1.25, then absEqual( )
would return true. One way to implement absEqual( ) is to pass it a NumericFns
argument, and then compare the absolute value of that argument against the absolute
value of the invoking object, returning true only if the values are the same. For example,
you want to be able to call absEqual( ), as shown here:
At first, creating absEqual( ) seems like an easy task. Unfortunately, trouble starts as
soon as you try to declare a parameter of type NumericFns. What type do you specify
for NumericFns’ type parameter? At first, you might think of a solution like this, in
which T is used as the type parameter:


Here, the standard method Math.abs( ) is used to obtain the absolute value of each
number, and then the values are compared. The trouble with this attempt is that it will
work only with other NumericFns objects whose type is the same as the invoking
object. For example, if the invoking object is of type NumericFns, then the
parameter ob must also be of type NumericFns. It can’t be used to
compare an object of type NumericFns, for example. Therefore, this
approach does not yield a general (i.e., generic) solution.
To create a generic absEqual( ) 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 absEqual(
) method:
Here, NumericFns matches any type of NumericFns object, allowing any two
NumericFns objects to have their absolute values compared. The following program
demonstrates this:


The output is shown here:
In the program, notice these two calls to absEqual( ):


In the first call, iOb is an object of type NumericFns and dOb is an object
of type NumericFns. However, through the use of a wildcard, it is possible
for iOb to pass dOb in the call to absEqual( ). The same applies to the second call, in
which an object of type NumericFns is passed.
One last point: It is important to understand that the wildcard does not affect what type
of NumericFns objects can be created. This is governed by the extends clause in the
NumericFns declaration. The wildcard simply matches any valid NumericFns
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
method that is designed to operate only on objects that are subclasses of a specific
superclass. To understand why, let’s work through a simple example. Consider the
following set of classes:
Here, class A is extended by classes B and C, but not by D.
Next, consider the following very simple generic class:


Gen takes one type parameter, which specifies the type of object stored in ob. Because
T is unbounded, the type of T is unrestricted. That is, T can be of any class type.
Now, suppose that you want to create a method that takes as an argument any type of
Gen object so long as its type parameter is A or a subclass of A. In other words, you
want to create a method that operates only on objects of Gen<type>, where type is
either A or a subclass of A. To accomplish this, you must use a bounded wildcard. For
example, here is a method called test( ) that accepts as an argument only Gen objects
whose type parameter is A or a subclass of A:
The following class demonstrates the types of Gen objects that can be passed to test( ).


In main( ), objects of type A, B, C, and D are created. These are then used to create
four Gen objects, one for each type. Finally, four calls to test( ) are made, with the last
call commented out. The first three calls are valid because w, w2, and w3 are Gen
objects whose type is either A or a subclass of A. However, the last call to test( ) is
illegal because w4 is an object of type D, which is not derived from A. Thus, the
bounded wildcard in test( ) will not accept w4 as an argument.
In general, to establish an upper bound for a wildcard, use the following type of
wildcard expression:
superclass>
Ask the Expert
Q
: Can I cast one instance of a generic class into another?
A
: Yes, you can cast one instance of a generic class into another, but only if the
two are otherwise compatible and their type arguments are the same. For example,


assume a generic class called Gen that is declared like this:
Next, assume that x is declared as shown here:
Then, this cast is legal
because x is an instance of Gen. But, this cast
is not legal because x is not an instance of Gen.
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 (specified by

Yüklə 83 Mb.

Dostları ilə paylaş:
1   ...   54   55   56   57   58   59   60   61   ...   82




Verilənlər bazası müəlliflik hüququ ilə müdafiə olunur ©genderi.org 2024
rəhbərliyinə müraciət

    Ana səhifə