System, but they may not be reproduced for publication



Yüklə 83 Mb.
Pdf görüntüsü
səhifə36/82
tarix19.04.2023
ölçüsü83 Mb.
#106251
1   ...   32   33   34   35   36   37   38   39   ...   82
Java A Beginner’s Guide, Eighth Edition ( PDFDrive )

override the method in the superclass. When an overridden method is called from
within a subclass, it will always refer to the version of that method defined by the
subclass. The version of the method defined by the superclass will be hidden. Consider
the following:


The output produced by this program is shown here:
When show( ) is invoked on an object of type B, the version of show( ) defined within
B is used. That is, the version of show( ) inside B overrides the version declared in A.
If you want to access the superclass version of an overridden method, you can do so by
using super. For example, in this version of B, the superclass version of show( ) is
invoked within the subclass’ version. This allows all instance variables to be displayed.
If you substitute this version of show( ) into the previous program, you will see the
following output:
Here, super.show( ) calls the superclass version of show( ).
Method overriding occurs only when the signatures of the two methods are identical. If
they are not, then the two methods are simply overloaded. For example, consider this
modified version of the preceding example:


The output produced by this program is shown here:
The version of show( ) in B takes a string parameter. This makes its signature
different from the one in A, which takes no parameters. Therefore, no overriding (or


name hiding) takes place.
OVERRIDDEN METHODS SUPPORT
POLYMORPHISM
While the examples in the preceding section demonstrate the mechanics of method
overriding, they do not show its power. Indeed, if there were nothing more to method
overriding than a namespace convention, then it would be, at best, an interesting
curiosity but of little real value. However, this is not the case. Method overriding forms
the basis for one of Java’s most powerful concepts: dynamic method dispatch. Dynamic
method dispatch is the mechanism by which a call to an overridden method is resolved
at run time rather than compile time. Dynamic method dispatch is important because
this is how Java implements run­time polymorphism.
Let’s begin by restating an important principle: a superclass reference variable can refer
to a subclass object. Java uses this fact to resolve calls to overridden methods at run
time. Here’s how. When an overridden method is called through a superclass reference,
Java determines which version of that method to execute based upon the type of the
object being referred to at the time the call occurs. Thus, this determination is made at
run time. When different types of objects are referred to, different versions of an
overridden method will be called. In other words, it is the type of the object being
referred to (not the type of the reference variable) that determines which version of an
overridden method will be executed. Therefore, if a superclass contains a method that is
overridden by a subclass, then when different types of objects are referred to through a
superclass reference variable, different versions of the method are executed.
Here is an example that illustrates dynamic method dispatch:


The output from the program is shown here:
This program creates a superclass called Sup and two subclasses of it, called Sub1 and


Sub2. Sup declares a method called who( ), and the subclasses override it. Inside the
main( ) method, objects of type Sup, Sub1, and Sub2 are declared. Also, a reference
of type Sup, called supRef, is declared. The program then assigns a reference to each
type of object to supRef and uses that reference to call who( ). As the output shows,
the version of who( ) executed is determined by the type of object being referred to at
the time of the call, not by the class type of supRef.
Ask the Expert
Q
: Overridden methods in Java look a lot like virtual functions in C++.
Is there a similarity?
A
: Yes. Readers familiar with C++ will recognize that overridden methods in Java
are equivalent in purpose and similar in operation to virtual functions in C++.
WHY OVERRIDDEN METHODS?
As stated earlier, overridden methods allow Java to support run­time polymorphism.
Polymorphism is essential to object­oriented programming for one reason: it allows a
general class to specify methods that will be common to all of its derivatives, while
allowing subclasses to define the specific implementation of some or all of those
methods. Overridden methods are another way that Java implements the “one
interface, multiple methods” aspect of polymorphism. Part of the key to successfully
applying polymorphism is understanding that the superclasses and subclasses form a
hierarchy that moves from lesser to greater specialization. Used correctly, the
superclass provides all elements that a subclass can use directly. It also defines those
methods that the derived class must implement on its own. This allows the subclass the
flexibility to define its own methods, yet still enforces a consistent interface. Thus, by
combining inheritance with overridden methods, a superclass can define the general
form of the methods that will be used by all of its subclasses.
Applying Method Overriding to TwoDShape
To better understand the power of method overriding, we will apply it to the
TwoDShape class. In the preceding examples, each class derived from TwoDShape
defines a method called area( ). This suggests that it might be better to make area( )


part of the TwoDShape class, allowing each subclass to override it, defining how the
area is calculated for the type of shape that the class encapsulates. The following
program does this. For convenience, it also adds a name field to TwoDShape. (This
makes it easier to write demonstration programs.)



The output from the program is shown here:


Let’s examine this program closely. First, as explained, area( ) is now part of the
TwoDShape class and is overridden by Triangle and Rectangle. Inside
TwoDShape, area( ) is given a placeholder implementation that simply informs the
user that this method must be overridden by a subclass. Each override of area( )
supplies an implementation that is suitable for the type of object encapsulated by the
subclass. Thus, if you were to implement an ellipse class, for example, then area( )
would need to compute the area( ) of an ellipse.
There is one other important feature in the preceding program. Notice in main( ) that
shapes is declared as an array of TwoDShape objects. However, the elements of this
array are assigned Triangle, Rectangle, and TwoDShape references. This is valid
because, as explained, a superclass reference can refer to a subclass object. The
program then cycles through the array, displaying information about each object.
Although quite simple, this illustrates the power of both inheritance and method
overriding. The type of object referred to by a superclass reference variable is
determined at run time and acted on accordingly. If an object is derived from
TwoDShape, then its area can be obtained by calling area( ). The interface to this
operation is the same no matter what type of shape is being used.
USING ABSTRACT CLASSES
Sometimes you will want to create a superclass that defines only a generalized form that
will be shared by all of its subclasses, leaving it to each subclass to fill in the details.
Such a class determines the nature of the methods that the subclasses must implement
but does not, itself, provide an implementation of one or more of these methods. One
way this situation can occur is when a superclass is unable to create a meaningful
implementation for a method. This is the case with the version of TwoDShape used in
the preceding example. The definition of area( ) is simply a placeholder. It will not
compute and display the area of any type of object.
As you will see as you create your own class libraries, it is not uncommon for a method
to have no meaningful definition in the context of its superclass. You can handle this
situation in two ways. One way, as shown in the previous example, is to simply have it
report a warning message. While this approach can be useful in certain situations—such
as debugging—it is not usually appropriate. You may have methods which must be
overridden by the subclass in order for the subclass to have any meaning. Consider the
class Triangle. It is incomplete if area( ) is not defined. In this case, you want some
way to ensure that a subclass does, indeed, override all necessary methods. Java’s
solution to this problem is the abstract method.


An abstract method is created by specifying the abstract type modifier. An abstract
method contains no body and is, therefore, not implemented by the superclass. Thus, a
subclass must override it—it cannot simply use the version defined in the superclass. To
declare an abstract method, use this general form:
abstract type name(parameter­list
As you can see, no method body is present. The abstract modifier can be used only on
instance methods. It cannot be applied to static methods or to constructors.
A class that contains one or more abstract methods must also be declared as abstract by
preceding its class declaration with the abstract modifier. Since an abstract class does
not define a complete implementation, there can be no objects of an abstract class.
Thus, attempting to create an object of an abstract class by using new will result in a
compile­time error.
When a subclass inherits an abstract class, it must implement all of the abstract
methods in the superclass. If it doesn’t, then the subclass must also be specified as
abstract. Thus, the abstract attribute is inherited until such time as a complete
implementation is achieved.
Using an abstract class, you can improve the TwoDShape class. Since there is no
meaningful concept of area for an undefined two­dimensional figure, the following
version of the preceding program declares area( ) as abstract inside TwoDShape,
and TwoDShape as abstract. This, of course, means that all classes derived from
TwoDShape must override area( ).




As the program illustrates, all subclasses of TwoDShape must override area( ). To
prove this to yourself, try creating a subclass that does not override area( ). You will
receive a compile­time error. Of course, it is still possible to create an object reference
of type TwoDShape, which the program does. However, it is no longer possible to
declare objects of type TwoDShape. Because of this, in main( ) the shapes array has
been shortened to 4, and a TwoDShape object is no longer created.
One last point: Notice that TwoDShape still includes the showDim( ) and
getName( ) methods and that these are not modified by abstract. It is perfectly
acceptable—indeed, quite common—for an abstract class to contain concrete methods
which a subclass is free to use as is. Only those methods declared as abstract need be
overridden by subclasses.
USING FINAL
As powerful and useful as method overriding and inheritance are, sometimes you will
want to prevent them. For example, you might have a class that encapsulates control of
some hardware device. Further, this class might offer the user the ability to initialize
the device, making use of private, proprietary information. In this case, you don’t want
users of your class to be able to override the initialization method. Whatever the reason,
in Java it is easy to prevent a method from being overridden or a class from being
inherited by using the keyword final.
final Prevents Overriding


final Prevents Overriding
To prevent a method from being overridden, specify final as a modifier at the start of
its declaration. Methods declared as final cannot be overridden. The following
fragment illustrates final:
Because meth( ) is declared as final, it cannot be overridden in B. If you attempt to do
so, a compile­time error will result.
final Prevents Inheritance
You can prevent a class from being inherited by preceding its declaration with final.
Declaring a class as final implicitly declares all of its methods as final, too. As you
might expect, it is illegal to declare a class as both abstract and final since an abstract
class is incomplete by itself and relies upon its subclasses to provide complete
implementations.
Here is an example of a final class:
As the comments imply, it is illegal for B to inherit A since A is declared as final.
Using final with Data Members


In addition to the uses of final just shown, final can also be applied to member
variables to create what amounts to named constants. If you precede a class variable’s
name with final, its value cannot be changed throughout the lifetime of your program.
You can, of course, give that variable an initial value. For example, in 
Chapter 6
a
simple error­management class called ErrorMsg was shown. That class mapped a
human­readable string to an error code. Here, that original class is improved by the
addition of final constants which stand for the errors. Now, instead of passing
getErrorMsg( ) a number such as 2, you can pass the named integer constant
DISKERR.
Notice how the final constants are used in main( ). Since they are members of the
ErrorMsg class, they must be accessed via an object of that class. Of course, they can
also be inherited by subclasses and accessed directly inside those subclasses.
As a point of style, many Java programmers use uppercase identifiers for final


constants, as does the preceding example. But this is not a hard and fast rule.
Ask the Expert
Q
: Can final member variables be made static? Can final be used on
method parameters and local variables?
A
: The answer to both is Yes. Making a final member variable static lets you
refer to the constant through its class name rather than through an object. For
example, if the constants in ErrorMsg were modified by static, then the
println( ) statements in main( ) could look like this:
Declaring a parameter final prevents it from being changed within the method.
Declaring a local variable final prevents it from being assigned a value more than
once.
THE OBJECT CLASS
Java defines one special class called Object that is an implicit superclass of all other
classes. In other words, all other classes are subclasses of Object. This means that a
reference variable of type Object can refer to an object of any other class. Also, since
arrays are implemented as classes, a variable of type Object can also refer to any array.
Object defines the following methods, which means that they are available in every
object:


The methods getClass( ), notify( ), notifyAll( ), and wait( ) are declared as final.
You can override the others. Several of these methods are described later in this book.
However, notice two methods now: equals( ) and toString( ). The equals( ) method
compares two objects. It returns true if the objects are equivalent, and false otherwise.
The toString( ) method returns a string that contains a description of the object on
which it is called. Also, this method is automatically called when an object is output
using println( ). Many classes override this method. Doing so allows them to tailor a
description specifically for the types of objects that they create.
One last point: Notice the unusual syntax in the return type for getClass( ). This
relates to Java’s generics feature. Generics allow the type of data used by a class or
method to be specified as a parameter. Generics are discussed in 
Chapter 13
.

Yüklə 83 Mb.

Dostları ilə paylaş:
1   ...   32   33   34   35   36   37   38   39   ...   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ə