preface

Encapsulation, inheritance, polymorphism, as the old three things in OOP world, are almost inevitable keywords.

For a long time when I was just learning Java, my understanding of polymorphism dealt with very confusing states. Is overloading polymorphic? Are generics polymorphic? Is inheritance polymorphic?

In fact, overloading, generics, and inheritance relationships are all specific representations of polymorphism and are classified as different polymorphisms

  • Ad hoc polymorphism
  • Parametric polymorphism
  • Subtyping

Of course, there are more than just the above three categories, such as Scala has another polymorphic category

  • Row polymorphism

Don’t be fooled by these noun concepts, let’s go through them with code examples.

Ad hoc polymorphism

Specific polymorphism was proposed by Christopher Strachey in 1967. From its name, we can probably guess that it is a polymorphic scheme for a specific problem, such as:

  • Function overloading
  • Operator overloading

Function overloading is when multiple functions have the same name, but have different implementations.

For example, the following example of function overloading shows two functions named print, one to print a string and one to print an image.

public void print(String word) {... }public void print(Image image) {... }Copy the code

Operator overloading is essentially syntactic sugar, and the experience is similar to function overloading. For example, + in Java is an operator that can be overloaded

Java’s + is not exactly an operator overload, because its operations on strings actually translate + into StringBuilder processing, which is syntax sugar.

But you can still use it to understand operator overloading

1 + 1

1 + 1.3
  
"hello" + " world"
 
1 + " ok"
Copy the code

The compiler performs different operations depending on the type left and right of +

  • 1 + 1 Perform the summation and return int
  • 1 + 1.3 Perform the summation, and the return value is increased to double
  • "hello" + " world"Performs String concatenation, returning String
  • 1 + " ok"Performs String concatenation, returning String

Whereas Java is limited to operator overloading (only built-in operator overloading), Scala is more open and allows developers to customize operator overloading.

Parametric polymorphism

Both parametric polymorphism and specific polymorphism were proposed by the same person in the same year, first implemented in ML (1975), and today almost all modern languages have features to support them, such as templates in D and C++, generics in C#, Delphi, and Java.

I have an excerpt from the wiki for its benefits

Parametric polymorphism allows programming languages to retain the type safety features of static languages while enhancing their expressiveness

Take the Java collection library List

for example. List is the type constructor, T is the parameter, and polymorphism is achieved by specifying different parameter types

  • List<String>
  • List<Integer>

List

and List

are two different types, regardless of type erasing, and we can see here that parameterized polymorphism can be applied to an infinite number of types.

There’s a section on the wiki that describes the difference between parameterized polymorphism and specific polymorphism that I think is pretty neat

If we were to cut the raw material in half

  • Parameter polymorphism: If you can “cut”, use the tool to cut it.
  • Specific polymorphisms: Choose different tools to cut depending on whether the raw material is iron or wood.

Subtyping

Subtype polymorphism, sometimes called inclusion polymorphism, expresses a surrogate relationship.

In the following Java code, Car has SmallCar and BigCar subclasses, respectively

abstract class Car {}

class SmallCar extends Car {}

class BigCar extends Car {}
Copy the code

BigCar and SmallCar are interchangeable within the priceOfCar function

public BigDecimal priceOfCar(Car car) {
	//TODO
}
Copy the code

Note that there is a difference between subtypes and inheritance.

Subtypes are more about describing a relationship: if S is a subtype of T, that is, S <: T), then S can be used in any context in which type T is used, equivalent to S replacing T. (Remember the Richter substitution principle?)

<: is a symbol derived from type theory, indicating subtype relationships

Inheritance is a feature of programming languages; in other words, subtype relationships are described by inheritance.

Row polymorphism

Row polymorphism is similar to subtype polymorphism, but it is a distinct concept that deals primarily with structured types of polymorphism.

So what are structured types?

In Java, we determine compatibility or equivalence of types based on their names. This type system is commonly referred to as a name-based type (or designated type system), whereas structured types determine compatibility or equivalence based on the actual structure or definition of the type.

Since Java does not support Row Polymorphism, it will be shown in Scala.

Suppose we now have a property (a Java-like interface) called Event, which is an abstraction of business events, and EventListener, which handles events, whose Listen function takes an Event object as an argument.

trait Event {
    def payload() :String
}

class InitEvent extends Event {
  override def payload() :String = {
    // TODO}}class EventListener {
    def listen(event: Event) :Unit = {
        //TODO}}Copy the code

This is how we normally use it

val listener = new EventListener()
listener.listen(new InitEvent())
Copy the code

If there is an OldEvent, it does not implement the Event attribute, but it does have the same payload method definition

class OldEvent {
  def palyload() = {
    // TODO}}Copy the code

If you’re familiar with Java, EventListener cannot accept an OldEvent object because OldEvent is not a subclass of Event

// Compile failure: OldCall is not of type Event
listener.listen(new OldCall())
Copy the code

To see how structured types behave in this scenario, change the EventListener’s Listen parameter from the Event class to a structured object

class EventListener {
    /** * event is the name of the structure type * {def payload(): String} specifies the definition of the structure: payload has no parameters and returns a String value */
  def listen(event: {def payload() :String{})// TODO}}Copy the code

In the past, we only accepted objects of type Event. Now, we accept structural objects defined by the payload function

// Compile passed
listener.listen(new InitEvent())
// Compile passed
listener.listen(new OldEvent())
Copy the code

It’s ok if OldEvent has more than one payload (partial matching of structures)

class OldEvent {
  
  def payload() :String = {}
  
  def someOtherMehod = {}}// Compile passed
listener.listen(new OldEvent())
Copy the code

Because of this feature of partial matching, structured polymorphism is often referred to as type-safe duck typing.

The last

If you’re confused by the above concepts, forget about them. Just remember that some of the common functions we use, such as function overloading, inheritance, generics, etc., are all examples of polymorphism.

Back to the title, now that you all know about polymorphisms, feel free to talk about partners……

What? No object, I have a new one.

reference

  1. Polymorphism (computer science)
  2. Ad_hoc_polymorphism
  3. Parametric polymorphism
  4. Subtyping
  5. Row polymorphism
  6. nominative type system
  7. structural type system