Code reuse for AS/400 programmers has been limited to the techniques of cut and paste and COPY. But now, with Java, AS/400 development can obtain huge payoffs from the advanced code-reuse facilities of object-oriented inheritance.
The most-often-heralded benefit of Java and object-oriented programming is code reuse. But we legacy programmers already have two varieties of code reuse: cut and paste and copy code. Cut and paste (a technique used all too often) is where we take good code examples, copy that code into a new program, and then tweak it to perform the desired behavior. Copy code is where the code is copied into a source member at compile time (rather than the design time of cut and paste) using COPY statements. The use of copy code with COBOL and RPG programs is far superior to cut and paste, but it has limitations. Typically, copy code has been restricted to the definition of complex data structures; it has not been widely used for calculations.
Object-oriented programming gives us a method of code reuse far superior to cut and paste or copying codeobject-oriented encapsulation. Encapsulation, as I covered in Object-oriented Design for AS/400 Java Applications: Standard Encapsulation Strategies (MC, July 1998), is the process of placing all the properties and behaviors of an entity into one placea Java class definitionand exposing to the users of that class only the information the designer feels the users need to know about the class. As a Java programmer, when you use that class as the abstract data type in the declaration of a variable, you are, in effect, reusing the implementation of that class without touching its code. In truth, you can achieve similar results with ILE modularization techniques. But what are you supposed to do when you find a Java class (or an ILE module, for that matter) that is almost, yet not quite, what you need? Your first impulse might be to fall back into the bad habit of cutting and pasting code in the design of a completely new Java class. Dont. There is a better way: the object-oriented mechanism known as inheritance. Sorry, there is no such alternative for ILE languages.
The Roles of Inheritance
Inheritance plays an even stronger role than just code reuse. That role is abstraction. Object-oriented designers use the abstraction capabilities of inheritance to organize business entities into hierarchies of specialization.
When designers classify business entities, they seek to group them by their common characteristics or by their common behaviors. Classification is nothing new to us; its how we order knowledge. To understand the complexities and variations of things, we categorize them into clusters of generalized groups. Object-oriented programming directly supports our human nature to categorize by allowing us to build a hierarchy of classes in which specialized classes inherit the structure and behavior defined by more generalized classes.
Biologys classification of species is often used as an excellent example of classification. Carolus Linnaeus came up the hierarchical categorization of genus and species in the 1700s, to which modern biology has expanded to kingdom, phylum, class, order, family, genus, and species. Don Denoncourt is from the kingdom animalia, phylum chordata, class mammalia, order primates, family hominidae, genus Homo, species sapiens.
At the start of application design, object-oriented designers seek to discover objects that model the problem domain. Often, they discover a variety of objects that share common characteristics. Rather than redundantly implementing those common characteristics as fields and functions in each of the various classes, they are factored out as generalizations into a base class.
The object model of Figure 1 shows a Company and a Consumer class. These objects have dissimilar characteristics, but so do they have common characteristics. Those common characteristics were factored out into the Customer class. Both the Consumer and Company classes inherit the common characteristics and behaviors from the Customer class. The Unified Modeling Language (UML) notation graphically portrays inheritance with the line that descends to those two classes from the Customer class (the triangle symbol represents inheritance). The operative word here is descends. Both the Consumer and the Company classes are descendants of the Customer class. Just as with humans, the Consumer and Company classes inherit the characteristics of their parent. The difference is that a Java class can inherit from only one parent.
The Customer class is an example of the abstraction capabilities of object-oriented design. More often, however, we are not involved in the complete design of an application. What inheritance gives us at the workaday level is a code-reuse mechanism. We build on top of existing classes by developing new ones that inherit the fields and functions of a base class and adding the capabilities required by the new class.
The payoffs from object-oriented inheritance are numerous. First, we dont need to know the implementation details of a classs parent class. Second, we dont have to duplicate the efforts of writing and testing code, which provides a service similar to what is already provided for in another class. Third, inheritance allows incremental development by allowing us to introduce new code without causing bugs in existing code, such as what you might have done with the cut-and-paste techniques of RPG programming. We get a fourth payoff from inheritance when the implementation of a base class is modified (as is always done in business programming), because those changes are immediately available to derived classes without recompilation. In an RPG application, for instance, if the original section of code that has been copied numerous times requires modification, so too must all the copied versions of that code be changed. Use of RPGs COPY is less of a problem, but all of the affected programs still require recompilation. The payoffs from Javas code reuse alone should be enough reason to consider moving from RPG to Java development.
The Basics of Inheritance
All Java classes have some form of inheritance, even those classes that do not explicitly extend another class. Thats because all Java classes inherit from Javas great
grandparent of base classesObject. Figure 2 shows a UML of the BigDecimal class. BigDecimal inherits from the Number class, and the Number class inherits from Object. The Number class is a generalization of the behaviors of numbers, any numbers, be they integers, long, float, double, or even our familiar zoned and packed numbers. In fact, you will use the BigDecimal class to store those fixed decimal values that we have traditionally kept on the AS/400 as zoned or packed. The BigDecimal class extends the Number class with the following:
class BigDecimal extends Number {...}
But if a Java class does not extend another class, the Java compiler implicitly
assigns the Object class to be its parent. The following
declarations for the Number class are identical:
class Number {
// fields and functions:
}
class Number extends Object {
// fields and function:
}
The Java compiler automatically inserts extends Object to the first implementation of the Number class. The Number class isas all classes ultimately arean extension of the Object class.
A base class is also known as the parent class or superclass, and the derived class is also known as the subclass. The Number class, as a subclass, picks up all the characteristics and behaviors of its parentJavas Object class. The Number class then adds new behaviors to those derived from its Object superclass. An instance of a BigDecimal object can directly invoke functions that are a part of any of its ancestors, which includes Javas Order class:
BigDecimal money = new BigDecimal ();
Class moneyClass = money.getClass();
String str = money.toString();
When we derive a class, only those fields and functions that are qualified in the parent class with either the public or protected access specifiers are available for the implementation of a derived class.
(Remember that the UML notation for the public, protected, and private access specifiers are the +, #, and - characters, respectively.) Private fields and functions are not inherited by derived classes; they are solely for the internal implementation of their classes. When a function has an access specifier of protected, as I covered in Object-oriented Design for AS/400 Java Applications:
Standard Encapsulation Strategies (MC, July 1998), it is accessible only from within other functions of that same class or from within functions of its subclasses. The protected functions, for instance, of the Object classclone() and finalize()are inherited by the PurchaseOrder class and are available for its implementation, but they are not available to instance variables of the PurchaseOrder class. For example:
class PurchaseOrder extends Object {
PurchaseOrder () {
// internal use of a protected Object function
clone();
}
}
PurchaseOrder po = new PurchaseOrder();
// external use of Objects protected function
po.clone ();
// compile error, function inaccessible
po.finalize ();
// compile error, function inaccessible
It would be easy if this were it for inheritance. That is, you design a new class by extending an existing class, thereby inheriting all of its public and protected fields and functions. Then, you add additional fields and functions as required by the new class. Using only what I have described so far, you can define some fairly sophisticated object models. But, as always, you have to be concerned with a few other areas; namely, overriding, overloading, and constructor invocation in an inheritance tree.
Overriding
In object-oriented language, you would say that the Consumer object of Figure 1 IS-A Customer. The Consumer inherited all of the fields and functions of its parent class, so it is a Customer. But the Consumer class is also more than the Customer class because it was extended to include some additional consumer-specific functions. There are occasions, however, in which you might want to change behaviors inherited from a base class. When the inherited behaviors and characteristics of a base class are changed in a derived class, we say that the subclass IS-A-KIND-OF its base class. It is an IS-A-KIND-OF base class because you have changed it to have some but not all of the characteristics and behaviors of its base. Why would you want to do this?
You might want to change the imple-mentation of a function inherited from a base class to adapt it to the needs of a new class. For instance, the finalize() function implemented in Javas ubiquitous Object class is often reimplemented in derived classes to perform class-specific cleanup, such as closing files when an object is garbage collected (destroyed) by the Java Virtual Machine (JVM). That new, improved finalize() function is automatically invoked when an object is destroyed by the garbage collection facilities of the JVM.
Hide-and-seek
You can change the inherited behaviors of a superclass by defining a function in the subclass that has the same name, return type, order, and type of arguments as a function of its superclass. Characteristics are changed simply by defining a field that has the same name as a field of the base class. The process of replacing characteristics and behaviors of a base class in a derived class is known as overriding.
When fields or functions of a base class are overridden in a derived class (subclass), the base versions of those fields and functions are still available to the derived class; they are simply hidden by the new versions in the derived class. You can still access the base classs (parent classs) overridden fields or functions by qualifying the reference with the Java Super keyword (the reserved word Super can be thought of as a synonym for the base class name). By specifying the overridden base classs function or field name qualified by Super, you force the Java compiler to reference the parents version. The following DOS executions of the superClass and subClass classes of Figure 3 shows how a parent classs overridden fields and functionstheInt and theFuncmay be accessed.
C:>java superClass
theInt:1 theFunc():1
C:>java subClass
theInt:2 theFunc():2
super.theInt:1 super.theFunc():1
Functions are overridden far more often than attributes. The object designer may wish to extend the functionality of a given method without complicating the API by adding another public function. Frequently, the overridden method even calls the very function that it hid. A simple example is a technique often used to test the functions of a class. This process is simple: You derive a class that overrides each of the functions in the superclass, add test code such as profiling information, and then invoke the parents implementation of the function so the parent class still works as designed.
class TestProfile extends VeryComplicatedClass {
void veryComplicatedFunction() {
System.out.print(got to
veryComplicatedFunction());
super.veryComplicatedFunction();
}
...
}
Overloaded Functions
Java programmers are able to easily understand the functional interface of a well- designed class because it has descriptive function names, return types, and parameter lists. It is normal to have requirements for interfaces that are similar in function but have a variety of argument options. For this reason, Java allows you to overload a function name by defining several functions with the same name but different signatures (a signature being the combination of function name, return type, number, and type of arguments).
In RPG (or even in a poorly designed Java class), we often create a parameter list by cramming in all the potential options, but that would make it unclear which parameters were required. A well-designed Java class lets you create multiple functions that have the same name and accept the correct combinations of parameters needed.
The BigDecimal class of Figure 1 uses function overloading because it has not one but four constructor functions. (You might recall that a constructor function has the same name as its class and no return type; it is used in conjunction with Javas New operator to instantiate an object of the constructors class.) You can build an instance of a BigDecimal object with a numerical string, a double value, another BigInteger object, or a BigInteger with specification for decimal precision:
BigDecimal money1 = new BigDecimal(128.95);
BigDecimal money2 = new BigDecimal(128.95);
BigInteger aBigInt = new BigInteger(325);
BigDecimal threeHundred25Bucks =
new BigDecimal(aBigInt);
BigDecimal threeBucksAndaQuarter =
new BigDecimal(aBigInt, 2);
If you define a function in a derived class with the same function name as one in the base class (but with a different signature), its considered an overloaded function. The base classs function of the same name is not overridden and is still directly available to the derived class. To override a function of the base class, you must specify the same signature in the function of the derived class.
You override a base classs function by defining a function of the same name and signature in the derived class. You overload a function by defining a function of the same name but with a different signature. You override a base classs function to extend or modify the behavior of the derived class and to hide the base classs version. You overload functions to allow multiple methods to have the same descriptive name, but with different parameter requirements to provide an intuitive API.
If You Do Not Have a Constructor, One Will Be Appointed for You
The initial state of an object is set by its constructor function. Every object in Java has a constructor function, whether you coded one or not. If you do not code a constructor, Java creates one for you. So whats the big deal, since Java seems to handle everything for you?
Understanding class construction becomes more important when you start using inheritance. When you create an object by invoking the New operator on a classs constructor, that constructors code must call the constructor of its parent class. Why? Remember that you should not be concerned with the implementation details of parent objects. Also remember that private attributes and functions are not inherited. With that in mind, realize that a subclasss constructor should not be required to set the initial state for fields found in all levels of its inheritance tree. Thats why the constructor of the parent
class must be called. Again, Java helps out because, if you dont explicitly code a call to the parents constructor in your subclasss constructor, Java will attempt to do it for you.
Implicit Class Construction
Earlier, I showed how Javas Super keyword can be used to qualify fields and functions to be from a subclasss parent: super.parentField. So it should make sense to you when I say that the qualification of a subclasss parents constructor is super(). Now the tricky part: If the first statement in a subclasss constructor is neither a call to super() nor a call to another overloaded constructor of the same class, then Java implicitly inserts a function call to super() as the first statement. For example, say you defined the following derived class:
class MyDerivedClass extends MyParentClass {
int I; MyDerivedClass() {
I = getIValue();
}
}
The Java compiler would create the Java byte code of the class with the
inclusion of a call to MyDerivedClasss parents constructor:
MyDerivedClass() {
super();
I = getIValue();
}
Further, if you do not code a constructor, Java creates a default constructor for you that calls the classs immediate ancestors constructor. If you let Java create constructors for you, dont go looking around for the default constructor code. Just trust me that it will be in the *.CLASS byte code file. It will not be inserted into your *.JAVA source file. Nevertheless, the constructor code would look like this:
MyDerivedClass() {
Super();
}
As you can see, the Java-appointed default constructor of the derived class does nothing but invoke the constructor of the base class. The Java-created constructor always has an empty parameter list. A constructor function that has no arguments is known as a default constructor. Note that once you code your own constructor function (regardless of whether or not it takes any arguments), Java will no longer generate a default constructor for you.
Explicit Class Construction
So far, Java does it all for you. Again, you ask, Whats the big deal? Whether I define a constructor or not, Java always seems to step in and call the parents constructor for me. But Java can help only if you derive your class from a base class that has a default constructor. If you derive your class from a class that has an explicit constructor (which takes arguments) and that class does not also have a default constructor (one that takes no arguments), then Java cant help. You must explicitly code your subclass to call that parameterized constructor.
Think of it this way: The designers of the superclass are forcing you to provide the parameters that the superclass requires to properly initialize itself. No matter; this is easily done with an explicit call using the super() function passing the appropriate parameter list:
super(argument1, argument2, ...);
The Shoulders of Giants
Your initial Java development efforts may not require the sophisticated design of hierarchical classes. What those initial efforts will require is the reuse of existing classes within standard packages such as Javas Abstract Window Toolkit (AWT) and third-party classes like IBMs Java Toolbox for the AS/400. When you first start to look at the classes that make up a package, you should also look at its accompanying online hypertext
documentation. Each class within the package has its own hypertext document that lists its public fields and functions.
For you to fully understand the use of a class, you should also look at the hypertext documentation of its superclass.
For instance, AWT has a GUI class called TextField that allows the entry of characters into a Windows component. You would expect the TextField class to define a method of retrieving its text with something like a getText function. But if you pull up the Java Development Kits (JDK) hypertext documentation for AWTs TextField, you wont find a function that retrieves TextFields text. At the top of TextFields hypertext document, however, youll see the inheritance tree for TextField. If you click on its parent TextComponent class to browse that classs documentation, youll find the getText function. So TextField, by order of inheritance, also contains the getText function.
Inheritance allows us to create a new class based on an existing class without reimplementing the functionality of the existing class. We dont even need to know the implementation details of the base class. Inheritance allows us to incrementally develop Java applications by introducing new classes without causing bugs in an applications base classes. Further, Javas innovative method of handling inheritance at runtime allows modified base classes to be moved into production without the recompilation of derived classes. As you develop Java classes by using inheritance, you are able to stand on the shoulders of the talented object designers who came before us.
References
Java Design. Coad, Peter and Mark Mayfield. Upper Saddle River: Yourdon Press,
1996.
Professional Java Fundamentals. Cohen, et al. Birmingham: WROX Press, 1996. Java in a Nutshell, 2nd Edition. Flanagan, David. Sebastopol: OReilly & Associates Inc., 1997.
The Java Language Specification. James Gosling, Bill Joy, and Guy Steele. Mountain View: Addison-Wesley. 1996
int theFunc() { return 2; }
void print() {
System.out.print(theInt:+
theInt+ theFunc():+theFunc());
System.out.print( super.theInt:+
super.theInt+
super.theFunc():
+super.theFunc());
}
public static void main(String[] argv) {
new subClass().print();
}
}
Figure 3: An example of the use of Javas Super keyword to qualify a base classs fields and functions
LATEST COMMENTS
MC Press Online