1. Overview
1.概述
All Object-Oriented Programming (OOP) languages are required to exhibit four basic characteristics: abstraction, encapsulation, inheritance, and polymorphism.
所有面向对象的编程(OOP)语言都需要表现出四个基本特征:抽象、封装、继承和多态性。
In this article, we cover two core types of polymorphism: static or compile-time polymorphism and dynamic or runtime polymorphism. Static polymorphism is enforced at compile time while dynamic polymorphism is realized at runtime.
在这篇文章中,我们将介绍多态性的两种核心类型。静态或编译时多态性和动态或运行时多态性。静态多态性是在编译时实现的,而动态多态性是在runtime实现的。
2. Static Polymorphism
2.静态多态性
According to Wikipedia, static polymorphism is an imitation of polymorphism which is resolved at compile time and thus does away with run-time virtual-table lookups.
根据维基百科,静态多态性是对多态性的模仿,它在编译时解决,因此不需要运行时虚拟表的查找。
For example, our TextFile class in a file manager app can have three methods with the same signature of the read() method:
例如,我们在文件管理器应用程序中的TextFile类可以有三个具有相同签名的read()方法。
public class TextFile extends GenericFile {
//...
public String read() {
return this.getContent()
.toString();
}
public String read(int limit) {
return this.getContent()
.toString()
.substring(0, limit);
}
public String read(int start, int stop) {
return this.getContent()
.toString()
.substring(start, stop);
}
}
During code compilation, the compiler verifies that all invocations of the read method correspond to at least one of the three methods defined above.
在代码编译过程中,编译器会验证所有对read方法的调用是否对应于上述三种方法中的至少一种。
3. Dynamic Polymorphism
3.动态多态性
With dynamic polymorphism, the Java Virtual Machine (JVM) handles the detection of the appropriate method to execute when a subclass is assigned to its parent form. This is necessary because the subclass may override some or all of the methods defined in the parent class.
通过动态多态性,Java虚拟机(JVM)在子类被分配到其父类的形式时,会处理检测适当的方法来执行。这是必要的,因为子类可能会覆盖父类中定义的部分或全部方法。
In a hypothetical file manager app, let’s define the parent class for all files called GenericFile:
在一个假设的文件管理器应用中,让我们为所有的文件定义一个父类,叫做GenericFile。
public class GenericFile {
private String name;
//...
public String getFileInfo() {
return "Generic File Impl";
}
}
We can also implement an ImageFile class which extends the GenericFile but overrides the getFileInfo() method and appends more information:
我们也可以实现一个ImageFile类,它扩展了GenericFile,但重写了getFileInfo()方法并附加了更多的信息。
public class ImageFile extends GenericFile {
private int height;
private int width;
//... getters and setters
public String getFileInfo() {
return "Image File Impl";
}
}
When we create an instance of ImageFile and assign it to a GenericFile class, an implicit cast is done. However, the JVM keeps a reference to the actual form of ImageFile.
当我们创建 ImageFile 的实例并将其分配给 GenericFile 类时,我们做了一个隐式转换。然而,JVM会保留对ImageFile的实际形式的引用。
The above construct is analogous to method overriding. We can confirm this by invoking the getFileInfo() method by:
上述结构类似于方法覆盖。我们可以通过调用getFileInfo()方法来确认这一点。
public static void main(String[] args) {
GenericFile genericFile = new ImageFile("SampleImageFile", 200, 100,
new BufferedImage(100, 200, BufferedImage.TYPE_INT_RGB)
.toString()
.getBytes(), "v1.0.0");
logger.info("File Info: \n" + genericFile.getFileInfo());
}
As expected, genericFile.getFileInfo() triggers the getFileInfo() method of the ImageFile class as seen in the output below:
正如预期的那样,genericFile.getFileInfo()触发了ImageFile类的getFileInfo()方法,正如下面的输出所见。
File Info:
Image File Impl
4. Other Polymorphic Characteristics in Java
4.Java中的其他多态性特征
In addition to these two main types of polymorphism in Java, there are other characteristics in the Java programming language that exhibit polymorphism. Let’s discuss some of these characteristics.
除了Java中的这两种主要的多态性外,在Java编程语言中还有其他表现出多态性的特征。让我们来讨论其中的一些特性。
4.1. Coercion
4.1.胁迫
Polymorphic coercion deals with implicit type conversion done by the compiler to prevent type errors. A typical example is seen in an integer and string concatenation:
多态联合处理由编译器完成的隐式类型转换,以防止类型错误。一个典型的例子见于整数和字符串的连接。
String str = “string” + 2;
4.2. Operator Overloading
4.2.操作符重载
Operator or method overloading refers to a polymorphic characteristic of same symbol or operator having different meanings (forms) depending on the context.
操作符或方法重载是指同一符号或操作符根据上下文具有不同含义(形式)的多态特性。
For example, the plus symbol (+) can be used for mathematical addition as well as String concatenation. In either case, only context (i.e. argument types) determines the interpretation of the symbol:
例如,加号(+)可用于数学加法,也可用于String串联。在这两种情况下,只有上下文(即参数类型)决定了该符号的解释。
String str = "2" + 2;
int sum = 2 + 2;
System.out.printf(" str = %s\n sum = %d\n", str, sum);
Output:
输出。
str = 22
sum = 4
4.3. Polymorphic Parameters
4.3.多态参数
Parametric polymorphism allows a name of a parameter or method in a class to be associated with different types. We have a typical example below where we define content as a String and later as an Integer:
参数多态性允许一个类中的参数或方法的名称与不同的类型相关。我们有一个典型的例子,我们把content定义为String,后来又定义为Integer。
public class TextFile extends GenericFile {
private String content;
public String setContentDelimiter() {
int content = 100;
this.content = this.content + content;
}
}
It’s also important to note that declaration of polymorphic parameters can lead to a problem known as variable hiding where a local declaration of a parameter always overrides the global declaration of another parameter with the same name.
还需要注意的是,多态参数的声明会导致一个被称为变量隐藏的问题,即一个参数的局部声明总是覆盖另一个同名的参数的全局声明。
To solve this problem, it is often advisable to use global references such as this keyword to point to global variables within a local context.
为了解决这个问题,通常建议使用全局引用,如this关键字,在局部上下文中指向全局变量。
4.4. Polymorphic Subtypes
4.4.多态的亚型
Polymorphic subtype conveniently makes it possible for us to assign multiple subtypes to a type and expect all invocations on the type to trigger the available definitions in the subtype.
多态子类型方便地使我们有可能将多个子类型分配给一个类型,并期望对该类型的所有调用都能触发子类型中的可用定义。
For example, if we have a collection of GenericFiles and we invoke the getInfo() method on each of them, we can expect the output to be different depending on the subtype from which each item in the collection was derived:
例如,如果我们有一个GenericFiles 的集合,并且我们对其中的每一个调用getInfo()方法,我们可以期望输出结果是不同的,这取决于集合中的每一个项目是由哪个子类型衍生出来的。
GenericFile [] files = {new ImageFile("SampleImageFile", 200, 100,
new BufferedImage(100, 200, BufferedImage.TYPE_INT_RGB).toString()
.getBytes(), "v1.0.0"), new TextFile("SampleTextFile",
"This is a sample text content", "v1.0.0")};
for (int i = 0; i < files.length; i++) {
files[i].getInfo();
}
Subtype polymorphism is made possible by a combination of upcasting and late binding. Upcasting involves the casting of inheritance hierarchy from a supertype to a subtype:
子类型的多态性是通过上播和晚期绑定的组合来实现的。上投涉及到从超类型到子类型的继承层次的铸造。
ImageFile imageFile = new ImageFile();
GenericFile file = imageFile;
The resulting effect of the above is that ImageFile-specific methods cannot be invoked on the new upcast GenericFile. However, methods in the subtype override similar methods defined in the supertype.
上述的结果是,ImageFile-特定的方法不能在新的上位GenericFile上被调用。然而,子类型中的方法会覆盖超类型中定义的类似方法。
To resolve the problem of not being able to invoke subtype-specific methods when upcasting to a supertype, we can do a downcasting of the inheritance from a supertype to a subtype. This is done by:
为了解决在上传到超类型时无法调用子类型的特定方法的问题,我们可以做一个从超类型到子类型的继承的下传。这可以通过以下方式实现。
ImageFile imageFile = (ImageFile) file;
Late binding strategy helps the compiler to resolve whose method to trigger after upcasting. In the case of imageFile#getInfo vs file#getInfo in the above example, the compiler keeps a reference to ImageFile‘s getInfo method.
后期绑定 策略帮助编译器解决在上传后触发谁的方法。在上例中imageFile#getInfo与file#getInfo的情况下,编译器保持对ImageFile的getInfo方法的引用。
5. Problems With Polymorphism
5.多态性的问题
Let’s look at some ambiguities in polymorphism that could potentially lead to runtime errors if not properly checked.
让我们来看看多态性中的一些模糊之处,如果不正确检查,有可能导致运行时错误。
5.1. Type Identification During Downcasting
5.1.浇注过程中的类型识别
Recall that we earlier lost access to some subtype-specific methods after performing an upcast. Although we were able to solve this with a downcast, this does not guarantee actual type checking.
回想一下,我们之前在执行上播后失去了对一些子类型特定方法的访问。虽然我们能够用下拉法解决这个问题,但这并不能保证实际的类型检查。
For example, if we perform an upcast and subsequent downcast:
例如,如果我们进行了一次上播和随后的下播。
GenericFile file = new GenericFile();
ImageFile imageFile = (ImageFile) file;
System.out.println(imageFile.getHeight());
We notice that the compiler allows a downcast of a GenericFile into an ImageFile, even though the class actually is a GenericFile and not an ImageFile.
我们注意到,编译器允许将GenericFile下移为ImageFile,尽管该类实际上是一个GenericFile而不是ImageFile。
Consequently, if we try to invoke the getHeight() method on the imageFile class, we get a ClassCastException as GenericFile does not define getHeight() method:
因此,如果我们试图在imageFile类上调用getHeight()方法,我们会得到一个ClassCastException,因为GenericFile并没有定义getHeight()方法。
Exception in thread "main" java.lang.ClassCastException:
GenericFile cannot be cast to ImageFile
To solve this problem, the JVM performs a Run-Time Type Information (RTTI) check. We can also attempt an explicit type identification by using the instanceof keyword just like this:
为了解决这个问题,JVM执行了一个运行时类型信息(RTTI)检查。我们也可以通过使用instanceof关键字来尝试明确的类型识别,就像这样。
ImageFile imageFile;
if (file instanceof ImageFile) {
imageFile = file;
}
The above helps to avoid a ClassCastException exception at runtime. Another option that may be used is wrapping the cast within a try and catch block and catching the ClassCastException.
上述做法有助于避免在运行时出现ClassCastException异常。另一个可以使用的方法是在try和catch块中包裹cast,并捕获ClassCastException.。
It should be noted that RTTI check is expensive due to the time and resources needed to effectively verify that a type is correct. In addition, frequent use of the instanceof keyword almost always implies a bad design.
应该注意的是,RTTI检查是很昂贵的,因为需要时间和资源来有效地验证一个类型是否正确。此外,频繁地使用instanceof关键字几乎总是暗示着一个糟糕的设计。
5.2. Fragile Base Class Problem
5.2.脆弱的基类问题
According to Wikipedia, base or superclasses are considered fragile if seemingly safe modifications to a base class may cause derived classes to malfunction.
根据维基百科,如果对基类进行看似安全的修改可能会导致派生类出现故障,那么基类或超类就被认为是脆弱的。
Let’s consider a declaration of a superclass called GenericFile and its subclass TextFile:
让我们考虑一个名为GenericFile的超类及其子类TextFile的声明。
public class GenericFile {
private String content;
void writeContent(String content) {
this.content = content;
}
void toString(String str) {
str.toString();
}
}
public class TextFile extends GenericFile {
@Override
void writeContent(String content) {
toString(content);
}
}
When we modify the GenericFile class:
当我们修改GenericFile类时。
public class GenericFile {
//...
void toString(String str) {
writeContent(str);
}
}
We observe that the above modification leaves TextFile in an infinite recursion in the writeContent() method, which eventually results in a stack overflow.
我们观察到,上述修改使TextFile在writeContent()方法中处于无限递归状态,最终导致堆栈溢出。
To address a fragile base class problem, we can use the final keyword to prevent subclasses from overriding the writeContent() method. Proper documentation can also help. And last but not least, the composition should generally be preferred over inheritance.
为了解决一个脆弱的基类问题,我们可以使用final关键字来防止子类覆盖writeContent()方法。适当的文档也会有所帮助。最后但并非最不重要的是,一般来说,应该优先选择组合而不是继承。
6. Conclusion
6.结论
In this article, we discussed the foundational concept of polymorphism, focusing on both advantages and disadvantages.
在这篇文章中,我们讨论了多态性的基础概念,重点是优点和缺点。
As always, the source code for this article is available over on GitHub.
一如既往,本文的源代码可在GitHub上获得over on GitHub。