image

CHAPTER

4

Inheritance

imagene of the major benefits of object-oriented programming is code reuse. Programmers develop lot of code over time. If this code can be reused, they can save time and effort in developing and testing a new project. In structured programming, if the code is arranged in appropriate independent functions, it can be reused in future applications. For object-oriented programming, the inheritance feature you’ll learn about helps achieve code reuse. In the previous chapter, you learned to define classes. In this chapter, you learn how to write classes that extend the functionality of classes that already exist. You generally do so because you have already developed a few classes and you want to add some functionality to them without breaking the existing code or rewriting them entirely again. In some situations, you may even like to reuse the code developed by others and add more functionality. This is achieved with the inheritance feature of object-oriented programming, and that is what you are going to learn here.

In particular, you will learn the following:

image  What inheritance is
image  Implementing single-level inheritance in Java
image  Creating multilevel inheritance hierarchy
image  Accessing fields and methods of parent classes
image  Overriding base class methods
image  Understanding compile-time and runtime polymorphism
image  Creating and traversing a heterogeneous collection of objects
image  Learning typecasting rules for accessing a heterogeneous collection
image  Preventing method overriding and subclassing
image  Using the super keyword to access shadowed fields and methods

Why Inheritance?

Before you learn what inheritance is and how to implement it in your code, let me first give you a brief introduction to the importance of having inheritance. The major advantage of inheritance is code reuse. You will be able to use your tested code in your new applications, with desired additions, without really touching it. Even if the code has been distributed, you are able to add more functionality to your new applications without breaking the distributed code. For example, you may have been using OpenOffice for the last several years. Haven’t you upgraded it several times during this period? When an application such as OpenOffice is upgraded, the developers do not rewrite the full code for every new version. They use the code of the existing version in terms of the classes they have defined and simply add more functionality to it without touching the source program of the existing code. This is what inheritance permits you to do. Besides this, by using inheritance, you will be able to model the real-world hierarchies. For example, to develop a payroll system, you could define classes such as Person, Employee, Staff, Manager, Director, and so on, that perfectly fit into an inheritance hierarchy. Because such classes have a lot of commonality, putting them in an inheritance hierarchy will make your code more maintainable in the future. The inheritance also allows you to create a set of pluggable items with the same “look and feel”. As an example, consider the various GUI (graphical user interface) controls you use in dialog boxes of your daily applications. Most of these controls, such as text boxes, list boxes, buttons, labels, and so on, have lot of commonality in terms of their functions and their look and feel. The inheritance feature of object-oriented programming facilitates creating a set of such controls. As you read this chapter, you will learn these and several other benefits offered by inheritance. So let’s start by discussing what inheritance is.

What Is Inheritance?

All of us have observed inheritance in nature. For example, a baby inherits the characteristics of her parents, a child plant inherits the characteristics of its parent plant, and so on. We attempt to bring this feature into our software by inheriting the characteristics of the existing classes and then extending their functionality. To identify the classes, which have some commonality and thus are probable candidates for creating inheritance hierarchies, object-oriented analysis techniques provide some rules. You will learn some of these as you read this section.

Let me illustrate inheritance with a concrete real-life example. Suppose we are asked to write some assets management software. Now, what are the classifications of assets a person may possess? A person might have a bank account, a few investments in securities, and a home in which to live. There may be many more types of assets, but we will restrict our discussion to the few mentioned here. We would like to represent these assets in our software, so let’s start creating classes to represent them. The first thing that comes to mind is an Asset class, which is a common term for all the assets previously mentioned. We represent the Asset class as shown in Figure 4-1.

image

FIGURE 4-1.   The Asset class

Note that this figure uses Unified Modeling Language (UML) notation to represent an Asset class. Each asset has a unique identifier represented by the id field in the class definition. Similarly, each asset has a certain type, such as bank account, security, real estate, and so on. We represent this type using the type field. The prefix in the definition of each field indicates its visibility within our program code. The hyphen (-) prefix indicates the private visibility, the plus sign (+) prefix indicates a public visibility, and so on. The variables declared with private visibility are accessible only to the methods defined in the class and are not visible to the code defined in other classes. The Asset class declares a method called printDescription. The method returns a void and its name is prefixed with a + sign, indicating that the method is publicly accessible.

image

NOTE

UML defines a widely accepted notation for creating artifacts of object-oriented design.

Next, we want to add classes to represent our real-world assets. Let’s define a class to represent a bank account. As mentioned earlier, a bank account is a type of asset and would have an id and type associated with it. Each account is held with a particular bank; this information will be captured in an attribute that represents the bank name. Let’s call this field bankName. Each bank account will also contain a balance at any given time. We capture this by creating a field called balance. Thus, our BankAccount class will look like what is shown in Figure 4-2.

image

FIGURE 4-2.   BankAccount class

You can easily see that the BankAccount and Asset classes have certain characteristics in common. Because the id and type attributes (fields) are common to both classes, we will create a hierarchy such that the BankAccount class inherits these fields from the Asset class. This is represented in the UML notation shown in Figure 4-3.

image

FIGURE 4-3.   Inheriting BankAccount from the Asset class

We call this operation of extending the class functionality subclassing. We say that the BankAccount is a “subclass” of the Asset class. Alternatively, we say that BankAccount “extends” the Asset class. Another way of putting it is that BankAccount is “derived from” the Asset class. The class from which a subclass is created is called a superclass. Thus, Asset is a superclass of BankAccount. Alternatively, we say that Asset is a “base class” of BankAccount.

In Java, subclassing is achieved with the help of the extends keyword. UML notation has been used thus far to explain class inheritance. Now, you will see the class declarations for these classes (see Listing 4-1).

image

Listing 4-1   Program Snippet to Demonstrate Subclassing

image
 
image

Note that the BankAccount class definition does not contain any of the fields defined in the Asset class. These characteristics (fields and methods) are automatically made available to an object of BankAccount type. Note that the id and type fields of the Asset class are declared private. As stated earlier, private fields are not visible to the code outside the class definition. Therefore, to access these private fields, we create getter/setter methods for each field. These methods are declared public and can be called from the code outside the current class definition. To initialize these fields, we can use class constructors. How to do this is detailed in the next chapter, where we discuss the class constructors in depth.

image

NOTE

With use of the extends keyword, the subclasses will be able to inherit all the properties of the superclass except for its private properties.

image

TIP

The inheritance is captured by an “is a” relationship. After all, a BankAccount “is a” type of Asset. Examples are “BankAccount is an Asset,” “Security is an Asset,” “RealEstate is an Asset,” and so on. While designing your classes, if you find the relationship between two classes can be represented by an “is a” relationship, then these classes are good candidates for creating an inheritance hierarchy.

Defining Single-level Inheritance

When a class inherits from a single class, as in the case of BankAccount inheriting from Asset, it is called single-level inheritance or simply single inheritance. In single inheritance, you create a class that inherits the properties of another single class. You may create multiple classes that inherit from a single class. This was stated earlier when I said that bank account, security, and real estate are types of assets. Thus, all these can be represented as classes inheriting from the Asset class, as shown in Figure 4-4.

image

FIGURE 4-4.   Multiple classes inheriting the Asset class

Note that the newly defined classes Security and RealEstate both extend the Asset class and define the fields unique to each. Just like the BankAccount class, both the Security and RealEstate classes will have access to the base class fields and methods. You will learn more about this as you read the rest of the chapter.

When a class inherits from a single class, known as the parent class, as in the case of the BankAccount, Security, and RealEstate classes inheriting from Asset, it is called single inheritance. If the class itself does not inherit from any other class, we call it a top-level class.

image

NOTE

In Java, every class inherits from the Object class. Thus, Object is a top-level class.

image

CAUTION

You may be wondering whether it is possible to inherit from multiple classes. C++ allows you to inherit from multiple classes. However, Java does not support multiple inheritance. This means a Java class cannot simultaneously inherit the characteristics of two or more Java classes. Java has interfaces, which provide a sort of multiple inheritance. Interfaces are discussed in Chapter 6.

Capturing Multilevel Inheritance

We will now extend our single-level class hierarchy to multiple levels. A BankAccount inherits from Asset. A bank account can be one of two types: savings or checking. Therefore, we can say the following:

image  A bank account “is an” asset.
image  A savings account “is a” type of bank account.
image  A checking account “is a” type of bank account.

Thus, we could add two more classes, called SavingsAccount and CheckingAccount, to our class hierarchy to represent these additional real-life entities. This is shown in the UML diagram given in Figure 4-5.

image

FIGURE 4-5.   Illustrating multilevel inheritance on bank assets

The savings account draws monthly interest, but the bank does not pay any interest on the checking account. Thus, the SavingsAccount class defines a field called interestRate, whereas the CheckingAccount does not. Likewise, CheckingAccount has a unique field or attribute called overdraftLimit, which is missing in the SavingsAccount class. To get a better grasp of these inheritance hierarchies, we will now discuss a small program that illustrates the various concepts covered so far.

Writing a Multilevel Inheritance program

The application discussed in this section is based on the asset management classes you have studied so far. We will now look at the concepts of multilevel inheritance with the help of program code. For this purpose, we will use the asset hierarchy discussed previously. The complete program that demonstrates multilevel inheritance is shown in Listing 4-2.

image

Listing 4-2   Asset Management Application

image
 
image
 
image

Here, we have first created an Asset class:

image

image

Note that the Asset class is not declared public because the Java compiler has a restriction of allowing only one public class declaration in a source file.

image

TIP

To create multiple public classes in your application, declare each class in a separate file, compile all the source files, and place the generated .class files in the CLASSPATH. The runtime will be able to locate these class files whenever called for in your application. If you are using NetBeans or some other IDE, create a project and add as many public classes as you want. Each public class will be put into a separate file under the project.

The Asset class declares two fields, which are private to the class definition. To access these variables, we provide the corresponding getter/setter methods.

image

NOTE

Object-oriented design guidelines suggest that all class fields should be declared private and one should provide getter/setter (also called accessor/mutator) methods to access these private data members. Also, note that the information that the Asset class holds is sensitive and must be declared private by virtue of its nature. In the previous chapter, when the Point class was introduced, we did not mark its fields private because we had not talked about private/public modifiers yet.

Besides the two fields, we declare a printDescription method in the class definition that prints the values of these fields on the user console.

The BankAccount class inherits the Asset class:

image

image

The BankAccount class declares three fields; we once again provide the appropriate getter/ setter methods for each field and a printDescription method to print the values of these fields on the console. Next, we declare a SavingsAccount class that inherits BankAccount:

image

image

The SavingsAccount class, as discussed earlier, has the unique characteristic of an interest rate that does not apply to a checking account. Therefore, we declare a field to represent this interest rate and a corresponding setter method to set its value. Note that a getter method was not created for this field because we are not going to need it. The printDescription method, like in the earlier cases, prints the value of the class field.

Likewise, we create a CheckingAccount class that derives from BankAccount:

image

image

The checking account has the unique characteristic of an overdraft limit, which is represented by the overdraftLimit field. Like in earlier cases, we provide the setter method for this field and a printDescription method.

Our next task is to write a test application that uses these classes. We do so with the following declaration:

image

image

Note that this class is declared public, although in reality this is not necessary (you will understand why after you study Java packages and member visibility rules in Chapter 5). We define two variables that hold references to the SavingsAccount and CheckingAccount types, as follows:

image

image

tomSavingsAccount will be used for holding an instance of the SavingsAccount class. As the name suggests, this may be for representing the real-life savings account of a customer named Tom. Next, we declare another variable of the CheckingAccount type, which may represent a real-life business account of iVision, Inc.

We now write a main method where the program execution always begins:

image

image

We first create an instance of the AssetMgmt class. Once an instance is created, we can invoke the instance methods on this object.

image

NOTE

A class declaration can contain both static and nonstatic methods. The static methods can be invoked without creating an instance of the class. The nonstatic methods can be invoked only on an object reference. The static keyword is discussed in Chapter 6.

On the newly created manager object, we invoke the two methods createAssets and printAllAssets. As the names suggest, the first method creates a few assets and the second method lists those objects on the console.

image

image

We declare the createAssets method to be private because we know for sure that this method need not be invoked by any code outside the class definition.

image

NOTE

It is generally considered a good practice not to expose the code to the outside world unless you truly want to do so.

In the method definition, we first create an instance of SavingsAccount class:

image

image

We now invoke several set methods on the created object to set the values of various fields of the class:

image

image

Note that the setId and setType methods belong to the parent class Asset. These methods are public and therefore can be invoked by the SavingsAccount object we created.

image

NOTE

Because SavingsAccount is a subclass of Asset, the setId and setType methods are automatically inherited in the subclass even if they were not declared public. You learn the member visibility rules when we discuss packages in Chapter 5.

These methods set the two fields of the Asset object that is automatically created whenever you create a SavingsAccount object.

image

NOTE

Whenever you create an object of a subclass, its parent class object is automatically created. You learn the object-creation process in depth in the next chapter.

You set the values of three fields—bank name, account number, and balance—by calling the corresponding setter methods of the BankAccount class. Once again, note that the BankAccount object has been created for us automatically during the creation of the subclass object. Finally, we set the interest rate by calling the setInterestRate method of the SavingsAccount class itself.

Likewise, we create one more asset of the CheckingAccount type and set its fields by calling the appropriate setter methods of the various classes in the inheritance hierarchy.

Now we look at the printAllAssets method, which is also declared with a private modifier. The method prints the descriptions of the assets by calling the corresponding printDescription method on each object:

image

image

The two printDescription methods in the preceding statements need further explanation. If you look at the printDescription method of the SavingsAccount class, you’ll see that it contains the following two statements:

image

image

The second statement obviously prints the interest rate field to the console. The first statement uses the super keyword to access the super class object and invokes the printDescription method of this super object.

image

NOTE

You can access any of the super class fields and methods from a subclass object by using the super keyword to reference the super class object’s fields and methods.

If you look up the printDescription method of the BankAccount class, which is a super class of SavingsAccount, you will find another call, super.printDescription. This call invokes the printDescription method of its super class, which is the Asset class. Thus, by using the super keyword, you are able to request each of the inherited objects to print their own descriptions. When you run the application, you will see the following output:

image

image

Note how all the details of each account are printed to the console.

polymorphism

In the code example described in the previous section, you might have noticed that we used the same name, printDescription, for defining a method in each of the three classes defined in the inheritance hierarchy. This is called polymorphism in the object-oriented paradigm. The word polymorphism comes from Greek and means “having many forms.” In our case, the printDescription method declared in our classes—that is, Asset, BankAccount, and SavingsAccount (also CheckingAccount)—has different forms depending on the class to which it belongs.

Whenever we use the same method name across a class hierarchy, we say that the method is overridden in a subclass. This feature is called method overriding. Now, how does the compiler know which version of the method to call? The compiler decides this by looking at the object reference on which the method is invoked. Thus, when we invoke the printDescription method on an object of type SavingsAccount, the compiler first searches for this method within this class. If it’s found, the compiler simply invokes this method. Otherwise, it looks for this method in its super class. If the super class provides the method definition, the compiler calls its implementation. Otherwise, it searches up the hierarchy until the method declaration is found. If any of the classes in the hierarchy, including the base class, do not define the method, the compiler finally gives a compile-time error. Thus, the compiler statically binds the method to an object that defines it. This is called early binding.

image

NOTE

Connecting a method call to a method body is called binding. When binding is performed before the program is run (by the compiler and linker, if there is one), it’s called early binding. When the binding is performed at the runtime, it is called late binding.

In our code, we called the printDescription method on an object of type SavingsAccount. This results in calling the printDescription method defined within the SavingsAccount class and not the one defined in its super class. This feature is called compile-time polymorphism. The compiler resolves which implementation to call at the compile time, thus the name. We also have a feature called runtime polymorphism, whereby the Java runtime decides during program execution which implementation to call. You learn about runtime polymorphism in the next section.

Creating a Heterogeneous Collection of Objects

In the previous example, we created two assets and printed their descriptions by calling the overridden printDescription method on each. What if we have hundreds of such assets? Maintaining references to many such objects and calling printDescription on each one of them would be really tedious. You learned about arrays in a previous chapter—so why not create an array of assets? In our discussion of arrays, I said that an array consists of homogenous objects; so how can we put the references to different object types in a single array? Well, this is possible, as you’ll see shortly. We call this a heterogeneous collection of objects. You learn how to create this type of array and how to traverse the elements of such an array in the program example that follows.

A program That Demonstrates a Heterogeneous Collection

A few classes have been added to our earlier class diagram in Figure 4-5 to include more types of assets. The modified class diagram is shown in Figure 4-6.

image

FIGURE 4-6.   Class diagram for a portfolio management system

The program in Listing 4-3 implements these classes. The portfolioManager class defines the main method. It creates a heterogeneous collection of different asset types and demonstrates how to traverse its elements to print a description of each asset and how to compute the net worth of the entire portfolio.

image

Listing 4-3   Portfolio Management System

image
 
image
 
image
 
image
 
image

The program first defines the various classes for representing different asset types. We will not discuss these class definitions because they are identical to the asset classes used in the previous example. So let’s jump to the main program—the portfolioManager class. This class declares an array of the Asset type, as follows:

image

image

We restrict the number of assets to five. You can create an array of a larger size if you want to store more assets, or better yet you can create an array that grows dynamically. You will learn how to create dynamic arrays in Chapter 16. In the main method, we create an instance of the portfolioManager class and call its createAssets method:

image

image

In the createAssets method, we first create an asset of the SavingsAccount type and set its various fields by calling its setter methods and all those of the superclasses:

image

image

After the SavingsAccount object is initialized, we copy its reference into the tomAssets array at index 0:

image

image

Note that this assignment is valid. The tomSavingsAccount represents an object of a class that is a subclass of Asset. You are allowed to copy the references of subclass objects into a collection of the superclass type. Likewise, we will create the various types of assets and assign their references to the elements of the tomAssets array.

After the assets are created, the main method calls its printAllAssets method; so let’s look at its definition. The printAllAssets method iterates through the elements of the array by using a foreach loop and calls the printDescription on each element of the array:

image

image

When you call the printDescription method on the asset object, the runtime looks up the object type that the asset currently holds. During the first iteration, the asset refers to the object of type SavingsAccount—remember the array element at index 0 holds the reference to the tomSavingsAccount object. Therefore, the printDescription method defined in the SavingsAccount class would be called. During the second iteration, the asset refers to an object of type CheckingAccount and therefore the printDescription method defined in the CheckingAccount class would be called, and so on. Thus, the runtime calls an appropriate version of the overridden printDescription method, depending on the object type it refers to. This is called runtime polymorphism—the runtime decides which implementation to call.

Finally, the main method calls the printNetWorth method, which computes the total value of all the assets in the heterogeneous collection and prints it to the console:

image

image

The method iterates through the entire collection, calling the overridden getNetWorth method on each object; it adds the value returned by each object in the loop and finally prints the total on the console.

To understand further how the runtime decides which implementation of the method to call, look the diagram shown in Figure 4-7.

image

FIGURE 4-7.   Understanding runtime polymorphism

The diagram in Figure 4-7 illustrates three situations. Each situation depicts three objects of type Asset, BankAccount, and SavingsAccount. Each object has a method called printDescription. Therefore, the method is overridden in the class hierarchy. The asset is a reference to these objects that invokes the printDescription method on each. The asset is of type Asset, which is the base class in the hierarchy. Now, consider the situation in Part A of the diagram. The asset in this situation is holding a reference to the SavingsAccount type. Therefore, when we execute the code asset.printDescription, it will invoke the printDescription method defined in the SavingsAccount class. Now, consider Part B. In this case, the asset refers to the BankAccount object type and therefore the statement asset.printDescription will invoke the method defined in the BankAccount class. Finally, in Part C, the asset holds the reference to the Asset class and therefore the statement asset.printDescription will invoke the method defined in the Asset class.

image

NOTE

When your invocation starts in a superclass, the subclass implementation is not called. However, when an invocation starts in a subclass, the invoked method can invoke a superclass implementation by using the super keyword, as shown in the earlier program examples.

Although we discussed this earlier, I’ll reiterate for the current context: If the referred object does not implement the called method, the program flow automatically falls to the superclass implementation. Thus, if the SavingsAccount does not define a printDescription method and if the asset refers to an object of the SavingsAccount type, as shown in Part A of the diagram, the runtime will invoke the corresponding implementation in the BankAccount class, and if this, too, does not define the called method, the runtime will execute the corresponding method in the base class Asset. This is known as late binding because the method to be invoked is bound to a particular object at runtime.

Detecting the Object Type

In the program shown in the previous section, you saw how to create and traverse a heterogeneous collection. Now, what if you want to detect the type of element held by a specific location in the collection? To understand the need for doing so, consider a situation where the banks have raised the interest rate on all savings accounts to 4.5 percent from the current rate of 3 percent. Now we need to find all the objects in our heterogeneous collection that hold a reference to the SavingsAccount type. Therefore, we need a way to detect the type of object held by each element of the array. This is done with the help of the instanceof operator, as illustrated in the following code snippet:

image

image

The raiseSavingsInterest method iterates through all the elements of the collection, checking each retrieved object for its type. We use the instanceof operator to check whether the variable on its left side is of the type mentioned on its right side. If yes, it returns true; otherwise, it returns false. Once we locate an object of type SavingsAccount, we call the setInterestRate method on the located object to set the new interest rate. However, this requires a typecast on the asset variable. Note that asset is of type Asset and that SavingsAccount is a subclass of Asset. Typecasting from a superclass to a subclass does not happen automatically. Therefore, an explicit typecast to the SavingsAccount type is required before the desired set method can be called. After performing the desired operation, we break the loop because in our case it is not necessary to iterate through the rest of the records in the array—we know that we have only one SavingsAccount in our entire Asset collection.

Now, consider a situation where we want to compare the given reference with multiple types in an inheritance hierarchy. For this, we may create a loop as follows:

image

image

This is not going to give the desired results—but why? We compare the desired reference first with the base class type. Because any subclass object is of the type base class too, this comparison will always return true. This means your code will never execute the two else if clauses. Now, modify the code as shown here:

image

image

This time, we first compare the asset variable with the SavingsAccount type. If this comparison is found to be true, the comparison to the superclasses BankAccount and Asset will not be performed.

image

NOTE

The general rule is that if you want to detect an object type in an inheritance hierarchy, start with the lowermost subclass and then move up the hierarchy to the base class.

Typecasting Rules on Inheritance Hierarchies

As shown in the earlier examples, a typecast to a superclass is implicit; however, a typecast to a subclass must be explicit. Here’s a summary of the typecasting rules for inheritance hierarchies:

image  Casting “up” the class hierarchy is always permitted. This means you may typecast a subclass variable to its superclass without using the cast operator. In other words, a cast from a subclass to a superclass is implicit.

image

NOTE

How the compiler resolves methods on inheritance hierarchies was described earlier; due to this, developers do not need to worry about how method calls are resolved.

image  Casting “downward” must be explicit. When typecasting a variable of a superclass to its subclass, you must do so explicitly by using the cast operator. The cast object type is checked at runtime. If the runtime does not find an object of the cast type during program execution, a runtime exception is generated. Exceptions are discussed in Chapter 8.
image  The compiler must be satisfied with the cast. If you try to cast an object of one type to another that does not fit in the inheritance hierarchy, the compiler will definitely complain about the invalid cast. The casting is always permitted within the classes belonging to a single-inheritance hierarchy but not across two different hierarchies.

Preventing Method Overriding

When you create an inheritance hierarchy, sometimes you may want to ensure that the methods in your classes are not overridden by their subclasses. This is achieved with the use of the final keyword in front of the method name. For example, in our class hierarchy, you could make the getId method final so that no subclass can override its implementation. To make it final, you would use the following declaration:

image

image

When you do so, the subclasses SavingsAccount and CheckingAccount will not be able to override the definition of the getId method in their implementations. The attempt to override the final method will be detected at compile time and your code will not compile.

Preventing Subclassing

Sometimes, you may want to make sure your classes cannot be further subclassed. For example, it makes perfect sense not to allow anybody to subclass the SavingsAccount or CheckingAccount class. You do this by declaring the class itself as final.

image

TIP

JDK libraries declare several classes, such as String, Math, Boolean, Double, Integer, Float, Long, Short, StringBuffer, System, Void, Character, Byte, and so on, as final. Therefore, you will not be able to extend these classes in your applications.

To make our SavingsAccount class “final,” just add the final keyword in its declaration, as shown here:

image

image

Now, the compiler will give a compilation error if somebody tries to subclass this class further.

image

TIP

So far you have seen the use of method overriding. An overridden method has the same signature as its corresponding base class method. So the question is, can we override the fields of a class the way we override a method? Yes, we can indeed do so. In such situations, to access the superclass variable, also known as the shadowed variable or simply a member, we would use the same super keyword we used for calling the superclass overridden method. Thus, a superclass variable can be accessed with the syntax super.variableName. You would very rarely use this feature because in your own created class hierarchies you would certainly avoid using the same name for fields in different classes to avoid ambiguity.

Summary

The inheritance feature of object-oriented programming allows for code reuse. Java supports inheritance with the help of the extends keyword. In this chapter, you learned to extend the functionality of the existing classes using the inheritance feature. When a class is extended from another class, we say that the newly defined class is a subclass of the existing class and that the existing class is a superclass of the newly created class. Java supports single inheritance but not multiple inheritance, as other languages such as C++ do. However, the single-level inheritance may be extended to multiple levels, to any depth. An inherited class inherits the properties of its parent that have been declared using the public access modifier. You will study the implications of other access modifiers in upcoming chapters. Thus, its children inherit all the public methods and attributes of the base class. A child can override the definition of its parent’s method. This is called method overriding or compile time polymorphism.

You may also declare an array of objects of the base type and assign elements of subclasses to it. This is called a heterogeneous collection because the objects that an array holds differ in their types. You can traverse the elements of such a heterogeneous collection by using a reference variable of the base class type. When you invoke a method on an object referred by the element of the array, the runtime resolves the object reference and calls an appropriate method, depending on the object type. This is known as runtime polymorphism.

It is possible to detect the type of object that a heterogeneous array element refers to by using the instanceof operator. You may need to provide an appropriate typecast when you use a reference of the base element type to access elements of the subclass type. When a subclass reference is used for accessing the elements of the base class type, no cast is required—it is implicit. However, whenever a base class variable type accesses an object of the subclass type, an explicit cast is required. You can prevent method overriding and subclassing with the use of the final keyword. In the next chapter, you will learn more features of inheritance—the object-creation process; the use of the super, this, and final keywords; and the member visibility rules.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.119.248.149