|
|
Optimize with a SATA RAID Storage Solution
Range of capacities as low as $1250 per TB. Ideal if you currently rely on servers/disks/JBODs
Polymorphism in Java is invariably subtype polymorphism. Closely examining the mechanisms that generate that variety of polymorphic behavior requires that we discard our usual implementation concerns and think in terms of type. This article investigates a type-oriented perspective of objects, and how that perspective separates what behavior an object can express from how the object actually expresses that behavior. By freeing our concept of polymorphism from the implementation hierarchy, we also discover how Java interfaces facilitate polymorphic behavior across groups of objects that share no implementation code at all.
Polymorphism is a broad object-oriented term. Though we usually equate the general concept with the subtype variety, there are actually four different kinds of polymorphism. Before we examine subtype polymorphism in detail, the following section presents a general overview of polymorphism in object-oriented languages.
Luca Cardelli and Peter Wegner, authors of "On Understanding Types, Data Abstraction, and Polymorphism," (see Resources for link to article) divide polymorphism into two major categories -- ad hoc and universal -- and four varieties: coercion, overloading, parametric, and inclusion. The classification structure is:
|-- coercion
|-- ad hoc --|
|-- overloading
polymorphism --|
|-- parametric
|-- universal --|
|-- inclusion
In that general scheme, polymorphism represents an entity's capacity to have multiple forms. Universal polymorphism refers to a uniformity of type structure, in which the polymorphism acts over an infinite number of types that have a common feature. The less structured ad hoc polymorphism acts over a finite number of possibly unrelated types. The four varieties may be described as:
I will briefly discuss each variety before turning specifically to subtype polymorphism.
Coercion represents implicit parameter type conversion to the type expected by a method or an operator, thereby avoiding type
errors. For the following expressions, the compiler must determine whether an appropriate binary + operator exists for the types of operands:
2.0 + 2.0 2.0 + 2 2.0 + "2"
The first expression adds two double operands; the Java language specifically defines such an operator.
However, the second expression adds a double and an int; Java does not define an operator that accepts those operand types. Fortunately, the compiler implicitly converts the second
operand to double and uses the operator defined for two double operands. That is tremendously convenient for the developer; without the implicit conversion, a compile-time error would
result or the programmer would have to explicitly cast the int to double.
The third expression adds a double and a String. Once again, the Java language does not define such an operator. So the compiler coerces the double operand to a String, and the plus operator performs string concatenation.
Coercion also occurs at method invocation. Suppose class Derived extends class Base, and class C has a method with signature m(Base). For the method invocation in the code below, the compiler implicitly converts the derived reference variable, which has type Derived, to the Base type prescribed by the method signature. That implicit conversion allows the m(Base) method's implementation code to use only the type operations defined by Base:
C c = new C(); Derived derived = new Derived(); c.m( derived );
Again, implicit coercion during method invocation obviates a cumbersome type cast or an unnecessary compile-time error. Of course, the compiler still verifies that all type conversions conform to the defined type hierarchy.
Overloading permits the use of the same operator or method name to denote multiple, distinct program meanings. The + operator used in the previous section exhibited two forms: one for adding double operands, one for concatenating String objects. Other forms exist for adding two integers, two longs, and so forth. We call the operator overloaded and rely on the compiler to select the appropriate functionality based on program context. As previously noted, if necessary,
the compiler implicitly converts the operand types to match the operator's exact signature. Though Java specifies certain
overloaded operators, it does not support user-defined overloading of operators.
Java does permit user-defined overloading of method names. A class may possess multiple methods with the same name, provided that the method signatures are distinct. That means either the number of parameters must differ or at least one parameter position must have a different type. Unique signatures allow the compiler to distinguish between methods that have the same name. The compiler mangles the method names using the unique signatures, effectively creating unique names. In light of that, any apparent polymorphic behavior evaporates upon closer inspection.
Both coercion and overloading are classified as ad hoc because each provides polymorphic behavior only in a limited sense. Though they fall under a broad definition of polymorphism, these varieties are primarily developer conveniences. Coercion obviates cumbersome explicit type casts or unnecessary compiler type errors. Overloading, on the other hand, provides syntactic sugar, allowing a developer to use the same name for distinct methods.
Parametric polymorphism allows the use of a single abstraction across many types. For example, a List abstraction, representing a list of homogeneous objects, could be provided as a generic module. You would reuse the abstraction
by specifying the types of objects contained in the list. Since the parameterized type can be any user-defined data type,
there are a potentially infinite number of uses for the generic abstraction, making this arguably the most powerful type of
polymorphism.