Document directory
- Taste the charm of Java subtype Polymorphism
Taste the charm of Java subtype Polymorphism
Posted on 2004-11-04 by yo2 unknown-2
The word "polymorphism (polymorphism)" comes from Greek, meaning "multiple forms ". Most Java programmers regard polymorphism as a capability of objects so that they can call the correct method version. Even so, this implementation-oriented view leads to the magic of polymorphism, better than simply looking at polymorphism as a pure concept.
Polymorphism in Java is always subtype polymorphism. Almost mechanically produces some polymorphism, so that we do not consider the type issues involved. This article studies a type-Oriented Object viewpoint, and analyzes how to separate the behavior that an object can express from the behavior that the object will show. Aside from the fact that polymorphism in Java comes from the concept of inheritance, we can still feel that interfaces in Java are shared implementations of a group of objects without public code.
Polymorphism Classification
Polymorphism is a common concept in object-oriented languages. Although we often confuse polymorphism, there are actually four different types of polymorphism. Before starting the formal discussion of subtype polymorphism, let's take a look at the polymorphism in common object-oriented systems.
Luca Cardelli and Peter Wegner (author of the article On Understanding Types, Data processing action, and Polymorphism, reference resource link) divide Polymorphism into two categories-specific and general-four sub-categories: mandatory, reloaded, and included parameters. Their structure is as follows:
In such a system, polymorphism shows various forms of ability. Generic polymorphism references a large number of objects of the same structure type and they share common features. Specific polymorphism involves a small part of objects without the same features. Four polymorphism types can be described as follows:
Mandatory: an implicit type conversion method.
Overload: Use a flag as multiple meanings.
Parameter: provides the same operation for different types of parameters.
Include: Abstract operations of class inclusion relationships.
I will briefly introduce these polymorphism before talking about subtype polymorphism.
Forced Polymorphism
Forced polymorphism implicitly converts a parameter to a type that the compiler deems correct in some way to avoid errors. In the following expressions, the compiler must determine what the binary operator '+' should do:
2.0 + 2.0
2.0 + 2
2.0 + 2
The first expression adds the operands of two double values. This is specifically stated in Java.
The second expression adds the double and int types. This operation is not clearly defined in Java. However, the compiler implicitly converts the second operand to the double type and performs an addition of the double type. It is very convenient for programmers. Otherwise, a compilation error will be thrown, or the programmer is forced to explicitly convert int to double.
The third expression adds double to a String. Such operations are not defined in Java. Therefore, the compiler converts the double type to the String type and concatenates them.
Forced polymorphism also occurs in method calls. Assume that the class Derived inherits the class Base. Class C has a method and its prototype is m (Base). In the following code, the compiler implicitly converts the Derived class object derived to the Base class object. This implicit conversion enables the m (Base) method to use all the parameters that can be converted to the Base class.
C c = new C ();
Derived derived = new Derived ();
C. m (derived );
In addition, implicit forced conversion can avoid the trouble of type conversion and reduce compilation errors. Of course, the compiler will still give priority to verifying the object types that conform to the definition.
High-load Polymorphism
The overload allows identical operators or methods to represent completely different meanings. '+' Has two meanings in the above program: the sum of two double-type numbers; the two strings are connected. There are also integer addition, long integer, and so on. The overloading of these operators depends on the compiler's selection based on the context. In the past, the compiler implicitly converted the operand to a type that fully complies with the operator. Although Java explicitly supports overloading, it does not support user-defined operator overloading.
Java supports User-Defined Function overloading. A class can have methods with the same name. These methods can have different meanings. In these overload methods, the number of parameters must be different, and the parameter types at the same location are different. These differences can help the compiler differentiate methods of different versions.
The compiler uses this unique representation of features to represent different methods, which is more effective than using a name. Therefore, all the polymorphism behaviors can be compiled.
Both forced and overloaded polymorphism are classified as specific polymorphism because these polymorphism are in a specific sense. These transferred polymorphism features bring great convenience to programmers. Forced polymorphism eliminates troublesome types and compilation errors. Heavy-load polymorphism is like a piece of sugar. It is convenient for programmers to express different methods with the same name.
Parameter Polymorphism
Parameter polymorphism allows many types to be abstracted into a single representation. For example, a List abstract class describes a group of objects with the same features and provides a general template. You can specify a type to reuse this abstract class. These parameters can be of any user-defined type, and a large number of users can use this abstract class. Therefore, parameter polymorphism is undoubtedly the most powerful polymorphism.
At first glance, the abstract class above seems to be a function of java. util. List. However, Java does not actually support true parameter polymorphism of the security type style, which is also java. util. list and java. other collection classes of util use the original java. lang. object write reason (refer to my article A Primordial Interface? For more details ). Java's single inheritance method solves some problems, but does not play all the functions of parameter polymorphism. Eric Allen has a wonderful article "Behold the Power of Parametric Polymorphism" describing the general Java type requirements, we recommend that you Add Generic Types to the Java Programming Language to Sun's Java specification requirements # document 000014. (Refer to the Resource Link)
Inclusion Polymorphism
Polymorphism is implemented through the value type and the Set's inclusion relationship. In many object-oriented languages including Java, the inclusion relationship is subtype. Therefore, Java's inclusion polymorphism is a subtype polymorphism.
In the early days, the polymorphism mentioned by Java developers was specifically the subtype polymorphism. From a type-oriented perspective, we can see the powerful features of subtype polymorphism. We will discuss this issue carefully in the following articles. For the sake of conciseness, polymorphism in the following sections refers to inclusion polymorphism.
Type-oriented view
The UML class diagram in Figure 1 provides a simple inheritance relationship between classes and types to facilitate the interpretation of polymorphism. The model contains five types, four classes, and one interface. Although UML is called a class diagram, I regard it as a Type diagram. As described in Thanks Type and Gentle Class, each Class and interface is a user-defined Type. Each rectangle in an independent implementation (such as type orientation) represents a type. From the implementation method, four types Use the class structure, and one uses the interface structure.
Figure 1: UML class diagram of the Demo code
The following code implements each user-defined data type, and I write the implementation very easily.
/* Base. java */
Public class Base
{
Public String m1 ()
{
Return Base. m1 ();
}
Public String m2 (String s)
{
Return Base. m2 (+ s + );
}
}
/* IType. java */
Interface IType
{
String m2 (String s );
String m3 ();
}
/* Derived. java */
Public class Derived
Extends Base
Implements IType
{
Public String m1 ()
{
Return Derived. m1 ();
}
Public String m3 ()
{
Return Derived. m3 ();
}
}
/* Derived2.java */
Public class Derived2
Extends Derived
{
Public String m2 (String s)
{
Return Derived2.m2 (+ s + );
}
Public String m4 ()
{
Return Derived2.m4 ();
}
}
/* Separate. java */
Public class Separate
Implements IType
{
Public String m1 ()
{
Return Separate. m1 ();
}
Public String m2 (String s)
{
Return Separate. m2 (+ s + );
}
Public String m3 ()
{
Return Separate. m3 ();
}
}
With such a type declaration and class definition, Figure 2 describes Java commands from the conceptual point of view.
Derived2 derived2 = new Derived2 ();
Figure 2: references on the Derived2 object
The object derived2 is defined in the preceding section. It belongs to the Derived2 class. The top layer of Figure 2 describes Derived2 reference as a set window, although the Derived2 object under it is visible. Here, a hole is left for each operation of the Derived2 type. Each operation of the Derived2 object maps the appropriate code, as described in the above Code. For example, the Derived2 object maps the m1 () method defined in Derived. The m1 () method of the Base class is also overloaded. The referenced variable of a Derived2 does not have access to the overloaded m1 () method in the Base class. However, this does not mean that this method cannot be called using the super. m1 () method. This code is not suitable for variables referenced by derived2. Other operation Mappings of Derived2 also indicate code execution for each type of operation.
Since you have a Derived2 object, you can reference it with any Derived2 type variable. As shown in 1, Derived, Base, and IType are both Base classes of derived2. Therefore, the reference of the Base class is very useful. Figure 3 describes the concepts of the following statements.
Base base Base = derived2;
Figure 3: Base class references attached to the Derived2 object
Although the reference of the Base class no longer needs to access m3 () and m4 (), it does not change any feature and operation ing of its Derived2 object. Whether the variable derived2 or base, the Code executed by calling m1 () or m2 (String) is the same.
String tmp;
// Derived2 reference (Figure 2)
Tmp = derived2.m1 (); // tmp is Derived. m1 ()
Tmp = derived2.m2 (Hello); // tmp is Derived2.m2 (Hello)
// Base reference (Figure 3)
Tmp = base. m1 (); // tmp is Derived. m1 ()
Tmp = base. m2 (Hello); // tmp is Derived2.m2 (Hello)
The two references call the same behavior because the Derived2 object does not know which method to call. The object only knows when to call it. It is executed in the sequence of inheritance implementation. This order determines that the Derived2 object calls the m1 () method in Derived and calls the m2 (String) method in derived2. The result depends on the type of the object, rather than the type of the reference.
However, it does not mean that the effects of using derived2 and base references are exactly the same. As shown in 3, only operations of the Base type can be seen in the Base reference. Therefore, although Derived2 maps Methods m3 () and m4 (), the variable base cannot access these methods.
String tmp;
// Derived2 reference (Figure 2)
Tmp = derived2.m3 (); // tmp is Derived. m3 ()
Tmp = derived2.m4 (); // tmp is Derived2.m4 ()
// Base reference (Figure 3)
Tmp = base. m3 (); // Compile-time error
Tmp = base. m4 (); // Compile-time error
The Derived2 objects in the runtime maintain the ability to accept the m3 () and m4 () methods. The Type restriction prevents Base references from calling these methods during compilation. The type check in the compilation phase is like a set of armor, ensuring that the runtime objects can only interact with the correct operations. In other words, a type defines the boundaries between objects.
Polymorphism dependence
Type consistency is the core of polymorphism. For each reference on the object, the static type checker should confirm that such attachment is consistent with the object hierarchy. When a successful reference is attached to another different object, interesting polymorphism occurs. (Strictly speaking, object types refer to the definition of classes .) You can also attach several different references to the same object. Before starting a more interesting scenario, let's take a look at the reason why there is no polymorphism in the scenario.
Multiple references are attached to one object.
The example in figures 2 and 3 shows that two or more references are attached to an object. Although the Derived2 object retains the variable type after being attached, the function of the Base type reference attachment in Figure 3 is reduced. Conclusion: attaching the reference of a base class to the object of the derived class reduces its capability.
How can I choose a solution to reduce the object capability for development? This option is indirect. Assume that a reference named ref is attached to an object of the class containing the following method:
Public String poly1 (Base base)
{
Return base. m1 ();
}
Calling poly (Base) with a Derived2 parameter meets the parameter type check:
Ref. poly1 (derived2 );
Method call attaches a local Base type variable to an introduced object. Therefore, although this method only accepts parameters of the Base type, the Derived2 object is still allowed. Therefore, you do not have to select a solution for function loss. From the situation that the human eye sees when using the Derived2 object, the attachment of the Base type reference causes the function loss. However, from the execution point of view, each input poly1 (Base) parameter is considered as a Base object. The executor does not care that multiple references direct to the same object. Instead, it only transmits the reference pointing to another object to the method. Inconsistent types of these objects are not the main issue. The executor only needs to find an appropriate implementation for the runtime object. The Type-oriented view demonstrates the enormous capability of polymorphism.
References attached to multiple objects
Let's take a look at the polymorphism in poly1 (Base. The following code creates three objects and transmits them to poly1 (Base) through reference ):
Derived2 derived2 = new Derived2 ();
Derived derived = new Derived ();
Base base Base = new Base ();
String tmp;
Tmp = ref. poly1 (derived2); // tmp is Derived. m1 ()
Tmp = ref. poly1 (derived); // tmp is Derived. m1 ()
Tmp = ref. poly1 (base); // tmp is Base. m1 ()
The implementation code of poly1 (Base) is to call the m1 () method of the passed parameters. Figure 3 and figure 4 show the type-Oriented Architecture used when three class objects are passed to the method.
Figure 4: point the Base reference to the Derived class and Base Object
Note the m1 ing of m1 () in each graph. In Figure 3, m1 () calls the code of the Derived class; the comment in the code above indicates that ploy1 (Base) calls Derived. m1 (). In Figure 4, the Derived object still calls the m1 () method of the Derived class. Finally, in figure 4, the m1 () called by the Base object is the code defined in the Base class.
What is the charm of polymorphism? Let's take a look at the poly1 (Base) code. It can accept any parameter that belongs to the Base class category. However, when he receives a Derived2 object, it actually calls the method of the Derived version. When you derive other classes from the Base class, such as Derived, Derived2, and poly1 (Base), you can accept these parameters and choose to call the appropriate method. Polymorphism allows you to extend its usage after completing poly1 (Base.
This looks amazing. The basic understanding shows the internal working principle of polymorphism. In the type-oriented view, the Code implemented by the underlying object is non-substantive. It is important that the type checker selects the appropriate code for each reference during compilation to implement its method. Polymorphism allows developers to use the type-oriented viewpoint without considering the implementation details. This helps to separate types from implementations (the actual use is to separate interfaces from implementations ).
Object Interface
Polymorphism depends on the separation of types and implementations, and is used to separate interfaces and implementations. However, the following points seem to confuse the Java keyword interface.
More importantly, let developers understand the phrase "the interface to an object. Typically, according to the context, this phrase refers to the methods defined in all object classes, to make all objects public. This kind of view tends to be implementation-centered. Compared with the type-oriented view, we pay more attention to the ability of objects during runtime. In Figure 3, the Object surface of the reference panel is marked as Derived2 Object. This Panel lists all available methods for the Derived2 object. But to understand polymorphism, we must be freed from the implementation level, and pay attention to the panel marked as Base Reference in the type-oriented perspective. In this sense, the type of the referenced variable specifies the surface of an object. This is just a surface, not an interface. Under the principle of Type consistency, we can use the type-oriented viewpoint to attach multiple references to an object. I have no definite understanding of the phrase "interface to an object.
In the concept of type, the interface to an object refers references the case where the maximum possibility of Type orientation is 2. Narrowing down the point of reference of a base class to the same object-3. The concept of Type allows people to get the essentials of separating objects from each other in detail. Compared with an object interface, the type-oriented view encourages people to use the reference of an object. The reference type specifies the interaction between objects. When you consider what an object can do, you only need to understand its type, instead of considering its implementation details.
Java Interface
The polymorphism mentioned above uses the child-type relationship established by the class inheritance relationship. Java interfaces Also support user-defined types. Correspondingly, the Java interface mechanism starts the multi-state behavior established on the type hierarchy. Assume that a reference variable named ref points to a class object that contains the following methods:
Public String poly2 (IType iType)
{
Return iType. m3 ();
}
To understand the polymorphism in poly2 (IType), the following code creates two objects from different classes and passes them to poly2 (IType) respectively ):
Derived2 derived2 = new Derived2 ();
Separate separate = new Separate ();
String tmp;
Tmp = ref. poly2 (derived2); // tmp is Derived. m3 ()
Tmp = ref. poly2 (separate); // tmp is Separate. m3 ()
The above code is similar to the discussion about polymorphism in poly1 (Base. The implementation code of poly2 (IType) is to call the m3 () method of the local version of each object. As in the past, the comments of the Code indicate the CString type results returned by each call. Figure 5 shows the conceptual structure of calling poly2 (IType) twice:
Figure 5: IType reference pointing to Derived2 and Separate objects
Methods The similarities of polymorphism behavior in poly1 (Base) and poly2 (IType) can be seen from the perspective. By improving our understanding on the Implementation Layer, we can see the skills of these two sections of code. The base class references the class that is passed as a parameter and calls the object method according to the type restriction. The reference neither knows nor cares which piece of code is executed. The child-type relationship check during compilation ensures that the passed object has the ability to select the appropriate implementation code when called.
However, they have an important difference in the Implementation Layer. In the poly1 (Base) Example (figure 3 and figure 4), the class inheritance structure of the Base-Derived-Derived2 provides conditions for the establishment of the Child-type relationship and determines which code the method calls. In the poly2 (IType) Example (5), a completely different dynamic occurs. Derived2 and Separate do not share any implementation layers, but they demonstrate polymorphism through reference of IType.
This kind of polymorphism makes the Functional Significance of Java interfaces very obvious. The UML class diagram in Figure 1 shows that Derived is a child type of Base and IType. Java implements multi-type inheritance by completely separating the implementation details from the type definition method, and there is no annoying problem caused by multi-inheritance prohibited by Java. Classes that are completely out of the Implementation hierarchy can be grouped by Java interface implementation. In Figure 1, the interface IType, Derived, Separate, and other child types of this type should be grouped into one group.
According to this completely different implementation-level classification method, Java interface mechanism becomes more convenient, even if there is no shared implementation or rewrite method. As shown in figure 5, an IType reference accesses the m3 () method of the Derived2 and Separate objects using the polymorphism method.
Explore object interfaces again
Note the dering method of the Derived2 and Separate objects in Figure 5 to m1. As mentioned above, each object's interface contains method m1 (). However, there is no way to use these two objects to make method m1 () show polymorphism. It is not enough for each object to possess an m1 () method. There must be a type that can operate the m1 () method. Through this type, you can see the object. These objects seem to share the m1 () method, but polymorphism is impossible without a common base class. Looking at polymorphism through the interface of the object, this concept will be mixed up.
Conclusion
From the sub-type polymorphism established by the object-oriented polymorphism described in the full text, you can clearly understand this type-oriented viewpoint. If you want to understand the idea of subtype polymorphism, you should shift your attention from the implementation details to the type. Type divides objects into groups and manages interfaces of these objects. The inheritance hierarchy of types determines the type relationships required to implement polymorphism.
Interestingly, the implementation details do not affect the hierarchy of subtype polymorphism. The type determines the method called by the object, and the Implementation determines how the object executes this method. That is to say, the type indicates the responsibility, while the implementation is the specific implementation. After separating the implementation from the type, we seem to see the two parts dancing together. The type determines the name of his partner and dance, and the implementation is the designer of dance movements.