What is the interface default method

Keywords: ASP.NET Java JDK Lambda

The reason why an interface becomes an interface is that it is not implemented, only declared. But then everything changed. There was a default method in Java and a default method in C ා. Interface is not like the traditional interface, its concept began to approach to abstract class, a pure abstract thing, suddenly appeared entity, so it began to be confused.

The world has changed, but how did he start to change?

1. origin

Although Java is mentioned in this article, but I am still writing C ා program in recent years, so the naming specification of unspecified language will tend to C ා specification, please understand.

Once, we defined the IStringList interface, which declared a list:

This is just an example. To avoid introducing more technical concepts, generic examples are not used here.

interface IStringList {
    void Add(string o); // Additive elements
    void Remove(int i); // Delete elements
    string Get(int i);  // Get elements
    int Length { get; } // Get list length
}

Anyway, this list already has basic functions of adding, deleting, modifying and querying, such as traversal, which can be written like this

IStringList list = createList();
for (var i = 0; i < list.Length; i++) {
    string o = list.Get(i);
    // Do something with o
}

After publishing this IStringList as a basic interface in the class library, a large number of programmers use this interface to implement a variety of lists, such as StringArrayList, LinkedStringList, StringQueue, StringStack, SortedStringList There are abstract classes, extension interfaces, and implementation classes. In short, after a long period of accumulation, the descendants of IStringList are all over the world.

Then the inventors of IStringList decided to define more methods for lists to meet the requirements of developers for the convenience of using IStringList under the rapid development of technology

interface IStringList {
    int IndexOf(string o);          // Index of lookup element, not found return - 1
    void Insert(string o, int i);   // Insert element at specified location

    // ------------------------------
    void Add(string o); // Additive elements
    void Remove(int i); // Delete elements
    string Get(int i);  // Get elements
    int Length { get; } // Get list length
}

Of course, all implementation classes except interface change must implement it, or the compiler will report an error. The abstract class AbstractStringList in the basic library implements the above-mentioned new interface. The whole basic library is compiled perfectly, and version 2.0 is released.

However, the reality is very cruel!

The users (developers) of the basic library have made a lot of complaints, because they can't compile too much code!

Yes, not all users directly inherit AbstractStringList. Many users directly implement IStringList. Many users even extended IStringList, but they did not define int IndexOf(string o), but defined int Find(string o). Due to the change of the basic library interface IStringList, users need to spend a lot of time to code to implement the new methods defined in IStringList.

This example refers to IStringList, with only two methods added. Although the trouble to users is not small, the workload is acceptable. But think about the huge base libraries of JDK and. NET Framework/Core. I'm afraid that users can only use "crash" to describe it!

2. way

We must not let users crash. We have to find a way to solve this problem. As a result, two solutions of Java and C ා, emerged

  • Java proposes a default method, that is, adding a default implementation to the interface
  • C ා proposes an extension method, which pretends to be an object call by changing the call form of a static method

It has to be said that the extension method of C ා is very smart, but it is not really extending the interface after all, so in C ා 8, a default method is added to solve the problem caused by interface extension.

After the interface extension method is put forward, it solves the problem of default implementation, but brings new problems.

  • The interface implements the default method. Does the class that implements the interface need to be implemented? What if it doesn't come true?
  • Both Java and C ා do not allow multiple class inheritance, but the interface can. However, the default implementation in the interface brings about problems similar to class multiple inheritance. What should I do?
  • In the complex relationship of implementation and inheritance, which method will be executed finally?

3. Problem 1: the relationship between default methods and class implementation methods

Ignore the additional Insert(Object, int) method in the IStringList interface above, and we will focus on IndexOf(Object). Java and C ා have the same syntax:

3.1. Let's first look at the syntax of the default method

  • Java version
interface StringList {
    void add(Object s);
    void remove(int i);
    Object get(int i);
    int getLength();

    default int indexOf(Object s) {
        for (int i = 0; i < getLength(); i++) {
            if (get(i) == s) { return i; }
        }
        return -1;
    }
}
  • C# version
interface IStringList
{
    public void Add(string s);
    void Remove(int i);
    string Get(int i);
    int Length { get; }
    int IndexOf(string s)
    {
        for (var i = 0; i < Length; i++)
        {
            if (Get(i) == s) { return i; }
        }
        return -1;
    }
}

Here, write out the interfaces of C and Java, mainly because they have slightly different methods and naming specifications. The next research on the similarity between C ා and Java behavior mainly takes C ා as an example.

How to distinguish between C ා sample and Java sample? Looking at the code specification, the most obvious is that the C ා method uses Pascal naming rules, and the Java method uses camel naming rules. Of course, the arrows of Lambda are also different.

For the next implementation, just take C ා as an example:

class MyList : IStringList
{
    List<string> list = new List<string>();  // Lazy and ready to use

    public int Length => list.Count;
    public void Add(string o) => list.Add(o);
    public string Get(int i) => list[i];
    public void Remove(int i) => list.RemoveAt(i);
}

MyList does not implement IndexOf, but there will be no problem in using it

class Program
{
    static void Main(string[] args)
    {
        IStringList myList = new MyList();
        myList.Add("First");
        myList.Add("Second");
        myList.Add("Third");

        Console.WriteLine(myList.IndexOf("Third"));  // Output 2
        Console.WriteLine(myList.IndexOf("first"));  // Output - 1, note first case
    }
}

3.2. Implement IndexOf in MyList

Now, add IndexOf in MyList to realize case insensitive search of string:

// In this case, partial class means partial implementation,
// Sorry for Java Er, Java has no part of class syntax
partial class MyList
{
    public int IndexOf(string s)
    {
        return list.FindIndex(el =>
        {
            return el == s
                || (el != null && el.Equals(s, StringComparison.OrdinalIgnoreCase));
        });
    }
}

Then the output of the Main function changes

Console.WriteLine(myList.IndexOf("Third")); // Or return 2
Console.WriteLine(myList.IndexOf("first")); // Return 0, not - 1

Obviously, MyList.IndexOf() is called here.

3.3. Conclusion, and the differences between Java and C ා

The above mainly uses C as an example. In fact, Java is the same. In the above example, the IndexOf method is called through the interface type. The first call is the default implementation of IStringList.IndexOf, because at this time, MyList does not implement IndexOf; the second call is the implementation of MyList.IndexOf. The author uses Java to write similar code, and the behavior is completely consistent.

Therefore, for the default method, the implementation in the class will be called first. If the interface with the default method is not implemented in the class, the default method in the interface will be called.

But!!! The previous example uses the interface type reference implementation. What if the instance type is used to reference the instance?

If IndexOf is implemented in MyList, the result is no different. But if IndexOf is not implemented in MyList, Java and C ා are different in processing.

Take a look at the Main function of C ා, but it can't be compiled( Compiler Error CS1929 ), because IndexOf is not defined in MyList.

And Java? Passed, as always run out of the results!

From the perspective of C ා, since MyList knows that there is an IndexOf interface, it should be implemented instead of pretending not to know. However, if IndexOf is called through IStringList, it can be assumed that MyList does not know that there is an IndexOf interface, so it is allowed to call the default interface. Interface or interface. I don't know if there is a new interface method that hasn't been implemented, so it's not your fault. But you know that it hasn't been implemented, that's your fault.

But from a Java perspective, the consumer of MyList is not necessarily the producer of MyList. From the perspective of consumers, MyList implements the StringList interface, and the interface defines the indexOf method, so it is reasonable for consumers to call myList.indexOf.

Java's behavior is relatively loose, as long as there is implementation you can use, regardless of what implementation.

The behavior of C ා is more strict. When using C ා, consumers can easily understand whether they are using class implementation or the default implementation in the interface through the compiler (although it is not useful to know). In fact, if it is not implemented in the class, the relevant interface will not be written in the interface document, and the intelligent prompt of the editor will not pop up. To write, you can display the conversion to interface to call:

Console.WriteLine(((IStringList)myList).IndexOf("Third"));

And according to the above test results, in the future, after MyList implements IndexOf, such calls will directly switch to the implementation in calling MyList, without semantic problems.

4. Question 2: multiple inheritance

Both Java and C ා do not allow multiple class inheritance, but the interface can. However, the default implementation in the interface brings about problems similar to class multiple inheritance. What should I do?

For example, a man can walk, a bird can walk, so how to walk?

4.1. Let's first look at C Chen's

Class does not implement the default interface:

interface IPerson
{
    void Walk() => Console.WriteLine("IPerson.Walk()");
}

interface IBird
{
    void Walk() => Console.WriteLine("IBird.Walk()");
}

class BirdPerson : IPerson, IBird { }

Call result:

BirdPerson birdPerson = new BirdPerson();
// BirdPerson.Walk (); / / CS1061, Walk is not implemented
((IPerson)birdPerson).Walk();   // Output IPerson.Walk()
((IBird)birdPerson).Walk();     // Output IBird.Walk()

You can't directly use birdPerson.Walk(), which has been mentioned before. However, when called by different interface types, the behavior is inconsistent, which is completely determined by the default method of the interface. It can also be understood that since the class does not have its own implementation, what interface is used for reference, indicating that the developer wants to use the default behavior specified by that interface.

To be frank, if you think of the king in the cloud as a man, he will use the method of human walking; if you think of the king in the cloud as a bird, he will use the method of bird walking.

However, if there is an implementation in the class, the situation is different:

class BirdPerson : IPerson, IBird
{
    // Notice that there is no shortage of public
    public void Walk() => Console.WriteLine("BirdPerson.Walk()");
}
BirdPerson birdPerson = new BirdPerson();
birdPerson.Test();              // Output BirdPerson.Walk()
((IPerson)birdPerson).Walk();   // Output BirdPerson.Walk()
((IBird)birdPerson).Walk();     // Output BirdPerson.Walk()

The output is exactly the same. When the default behavior defined in the interface is implemented in the class, it doesn't exist!

Yunzhongjun has personality: no matter what you think, I will go like this.

The only thing to note here is that Walk() implemented in BirdPerson must be declared public, otherwise C ා will treat it as the internal behavior of the class, rather than the interface behavior of the implementation. This is consistent with the requirements of C ා for implementing interface methods: implementing interface members must be declared as public.

4.2. Then look at the differences in Java

Turning to Java, the situation is different. Compilation is not allowed at all

interface Person {
    default void walk() {
        out.println("IPerson.walk()");
    }
}

interface Bird {
    default void walk() {
        out.println("Bird.walk()");
    }
}

// Duplicate default methods named walk with the parameters () and ()
// are inherited from the types Bird and Person
class BirdPerson implements Person, Bird { }

This means that both Person and Bird define the default implementation for the walk method with the same signature, so the compiler does not know what to do with BirdPerson. So what if only one walk has a default implementation?

interface Person {
    default void walk() {
        out.println("IPerson.walk()");
    }
}

interface Bird {
    void walk();
}

// The default method walk() inherited from Person conflicts
// with another method inherited from Bird
class BirdPerson implements Person, Bird { }

This means that the two interfaces behave differently, and the compiler still doesn't know what to do with BirdPerson.

Anyway, for BirdPerson to implement its own walk(). Since BirdPerson implements walk (), there is no suspense about the calling behavior:

BirdPerson birdPerson = new BirdPerson();
birdPerson.walk();              // Output BirdPerson.walk()
((Person) birdPerson).walk();   // Output BirdPerson.walk()
((Bird) birdPerson).walk();     // Output BirdPerson.walk()

4.3. Conclusion: multiple inheritance is no problem

If the same signature method is defined in multiple interfaces implemented by a class, there will be no problem if there is no default implementation.

If the signature method is implemented in the class, it will be called anyway, and there will be no problem.

However, if the interface has a default implementation and the class has no implementation, C 񖓿 will give the actual behavior to the reference type for processing; Java will directly report an error to the developer for processing. After all, the original intention of the default method is not to force developers to deal with the trouble of adding interface methods.

5. Question 3: how to analyze the more complicated situation

For more complex cases, you can still guess how to call most of the time. After all, there is a basic principle.

5.1. Implementation priority in class

For example, WalkBase defines the Walk() method, but does not implement any interface. BirdPerson inherits from WalkBase and implements the IPerson interface, but does not implement the Walk() method. Which Walk should be executed?

WalkBase.Walk() - class methods take precedence in all cases!

class WalkBase
{
    public void Walk() => Console.WriteLine("WalkBase.Walk()");
}

class BirdPerson : WalkBase, IPerson { }

static void Main(string[] args)
{
    BirdPerson birdPerson = new BirdPerson();
    birdPerson.Walk();              // Output WalkBase.Walk()
    ((IPerson)birdPerson).Walk();   // Output WalkBase.Walk()
}

If all the parent and child classes have implementations, but the child class is not "overloaded", but "overridden", then the nearest class should be found according to the reference type, such as

class WalkBase : IBird  // < = = note that IBird is implemented here
{
    public void Walk() => Console.WriteLine("WalkBase.Walk()");
}

class BirdPerson : WalkBase, IPerson  // < = = no IBird implementation here
{
    // Note: This is new, not override
    public new void Walk() => Console.WriteLine("BirdPerson.Walk()");
}

static void Main(string[] args)
{
    BirdPerson birdPerson = new BirdPerson();
    birdPerson.Walk();              // Output BirdPerson.Walk()
    ((WalkBase)birdPerson).Walk();  // Output WalkBase.Walk()
    ((IPerson)birdPerson).Walk();   // Output BirdPerson.Walk()
    ((IBird)birdPerson).Walk();     // Output WalkBase.Walk()
}

If virtual defines walk () in WalkBase, and override defines walk () in BirdPerson, the output without any suspense is all BirdPerson.Walk().

class WalkBase : IBird
{
    public virtual void Walk() => Console.WriteLine("WalkBase.Walk()");
}

class BirdPerson : WalkBase, IPerson
{
    public override void Walk() => Console.WriteLine("BirdPerson.Walk()");
}

static void Main(string[] args)
{
    BirdPerson birdPerson = new BirdPerson();
    birdPerson.Walk();              // Output BirdPerson.Walk()
    ((WalkBase)birdPerson).Walk();  // Output BirdPerson.Walk()
    ((IPerson)birdPerson).Walk();   // Output BirdPerson.Walk()
    ((IBird)birdPerson).Walk();     // Output BirdPerson.Walk()
}

The last output in the above example is to find WalkBase.Walk() through IBird.Walk(), while WalkBase.Walk() finds BirdPerson.Walk() through the virtual method chain, so the output is still BirdPerson.Walk(). Students who have studied C + + may have a feeling at this time!

As for Java, all methods are virtual methods. Although you can make it non virtual through final, you can't define the same signature method in the subclass, so the case of Java will be simpler.

5.2. There is no implementation in the class. Find the latest default implementation according to the reference type

Take WalkBase and BirdPerson for example,

class WalkBase : IBird { }
class BirdPerson : WalkBase, IPerson { }

((IPerson)birdPerson).Walk();   // Output IPerson.Walk()
((IBird)birdPerson).Walk();     // Output IBird.Walk()

Oh, of course not in Java, because the compiler requires that BirdPerson.Walk() must be implemented.

5.3. How can there be more complicated situations

Seriously, if there are more complicated situations, I suggest doing experiments!

6. Use default method carefully

There are historical reasons for the emergence of default methods, so when designing a new library, it is better not to consider the problem of default methods too early. If there is really a default behavior that needs to be implemented, perhaps the abstract base class is more suitable.

However, if the designed class and interface relationships are really complex, and even need to be similar to multiple inheritance relationships, it is not too bad to consider the default method properly.

Posted by rodrigocaldeira on Mon, 09 Mar 2020 02:20:47 -0700