Implementing methods using default methods of interfaces - Contradictory?
Asked Answered
A

1

13

Introduction

I have read multiple posts about implementing interfaces and abstract classes here on SO. I have found one in particular that I would like to link here - Link - Interface with default methods vs abstract class, it covers the same question. As the accepted answer, it is recommended to use the default methods of interfaces when it is possible to do this. But the comment below this answer stating "this feature feels more like a hack to me" explains my problem.

Default methods have been introduced to make implementations of interfaces more flexible - when an interface is changed it is not necessarily required in the implementing classes to (re)write code. Therefore, using a default method of an interface just to realize a method in all implementing classes - quote: "feels more like a hack to me".

Example for my examination:

Classes overview:

  • Item - Abstract Superclass of all Items
  • Water - Item which is consumable
  • Stone - Item which is not consumable
  • Consumable - Interface with some methods for consumable items (those methods have to be overriden by all implementing classes)

Combining those:

Water is an Item and implements Consumable; Stone is also an Item and does not implement Consumable.

My examination

I would like to implement a method which all Items have to implement. Therefore, I declare the signature in the class Item.

protected abstract boolean isConsumable(); 
//return true if class implements (or rather "is consumable") Consumable and false in case it does not

Quick edit: I am aware that instanceof might solve this particular example - if possible think of a more complicated example that makes it necessary to implement the method in the first place. (Thanks to Sp00m and Eugene)

Now I have several options:

  1. Implement the method by hand in every single subclass of Item (this is definitely not possible when scaling the application).

As mentioned above when scaling the application this would be impractical or highly inefficient.

  1. Implementing the method inside of the interface as a default method so the Consumable classes already implement the method which is required by the superclass Item.

This is the solution recommended by the other post - I see the advantages of implementing it in this way:

Quote - "The good thing about this new feature is that, where before you were forced to use an abstract class for the convenience methods, thus constraining the implementor to single inheritance, now you can have a really clean design with just the interface and a minimum of implementation effort forced on the programmer." Link

But in my opinion, it still seems contradictory to the original idea of default methods which I mentioned in my introduction. Furthermore, when scaling the application and introducing more methods that share the same implementation for all Consumables (as the example method isConsumable()), the interface would implement several default methods which contradicts the idea of an interface not implementing the actual method.

  1. Introducing sub-superclasses instead of an interface - for example the class Consumable as an abstract subclass of Item and superclass of Water.

It offers the opportunity to write the default case for a method in Item (example: isConsumable() //return false) and afterwards override this in the sub-superclass. The problem that occurs here: When scaling the application and introducing more sub-superclasses (as the Consumable class), the actual Items would start to extend more than one sub-superclass. It might not be a bad thing because it is necessary to do the same with interfaces too but it makes the inheritance tree complicated - Example: An item might now extend a subsuperclass ALayer2 which is a sub-superclass of ALayer1 which extends Item (layer0).

  1. Introducing another superclass (thus same layer as Item) - for example the class Consumable as an abstract class which will be another superclass of Water. That means that Water would have to extend Item & Consumable

This option offers flexibility. It is possible to create a whole new inheritance tree for the new superclass while still being able to see the actual inheritance of Item. But the downside I discovered is the implementation of this structure in the actual classes and using those later on - Example: How would I be able to say: A Consumable is an Item when Consumable would be able to have subclasses that are not meant for Items. The whole converting process will possibly cause a headache - more likely than the structure of Option 3.

Question

What would be the right option to implement this structure?

  • Is it one of my listed options?
  • Is it a variation of those?
  • Or is it another option I have not thought about, yet?

I have chosen a very simple example - please keep scalability for future implementations in mind when answering. Thanks for any help in advance.

Edit#1

Java does not allow multiple inheritance. This will effect the option 4. Using multiple interfaces (because you can implement more than one) might be a workaround, unfortunately the default-method will be necessary again which is exactly the kind of implementation I have been trying to avoid originally. Link - Multiple inheritance problem with possible solution

Attach answered 3/9, 2018 at 11:38 Comment(9)
It sounds odd to me that Item is aware of whether its instances are Consumable or not. I would personally rather not have such a method, and use theInstance instanceof Consumable when needed.Kendre
is it just me that finds weird that you want Item::isConsumable and a separate interface Consumable? why does Item has to know that Consumable even exists?Allonge
I am currently trying to give a better example - the isConsumable() method is a very simple example which can be dealt with in another way. You are right regarding this example. The answer of Roland also provides a quick answer to this particular method.Attach
Be careful: Java does not allow multiple inheritance of implementation. That is, a class other than Object extends exactly one other class. But Java does have multiple inheritance: it is the result of classes implementing interfaces. Interfaces could not otherwise serve their purpose -- implementations could not be used polymorphically. With the introduction of default methods for interfaces, even the "of implementation" part is blurred.Johnsiejohnson
You can not use a default method to implement an abstract method of the super class. Since Item does not implement Consumable, there is no relationship between a default method in Consumable and a declared method of Item with the same signature. So you have two unrelated methods. The Java designers solved this potential error source, by letting the non-interface class’ method always win, even if it is abstract. That’s also compatible with pre-Java 8 code.Subbase
@John Bollinger I mentioned the issue with multiple inheritance in my edit. You are right about that. "Java does have multiple inheritance through interfaces" is a good point which may be stated in the linked post (in my edit) but is also a good addition here. I would appreciate if you clarify the last sentence of your comment, I think I understand your point but outlining it could be the better option.Attach
@Subbase The abstract method of the superclass will not be overriden / implemented by the default method of an interface which is implemented in a subclass. That would be a good addition to the answer below. Could you give an example for another case with two unrelated methods (apart from default methods of interfaces)?Attach
@Attach any methods with the same signature but not overriding each other, are unrelated, e.g. class A { void foo() {} } and class B { void foo() {} }. Of course, without multiple inheritance, there’s no problem with them. So there are only scenarios involving default methods, where you may encounter such methods in one class, either, by inheriting them from two unrelated interfaces (then, you get an error unless you override them), or by inheriting one from an interface and one from the super class (then, you get the super class method).Subbase
I appreciate the clarification. Without multiple inheritance I could not imagine another case but you mentioned: "That’s also compatible with pre-Java 8 code" - the reason why I asked for another example apart from default. I think I misinterpreted that part of your comment. Thanks again.Attach
B
4

I am missing option 5 (or maybe I didn't read correctly): supply the method inside the Item itself.

Assuming that consumable items are identifiable via the Consumable-interface here are the reasons why I can not recommend most of the points you listed: The first one (i.e. implement it in every subclass) is just too much for something as simple as this instanceof Consumable. The second might be ok, but wouldn't be my first choice. The third and the fourth I can't recommend at all. If I can just give one advice then it's probably to think about inheritance twice and to never ever use intermediate classes just because they made your life easier at one point in time. Probably this will hurt you in future when your class hierarchy becomes more complex (note: I do not say that you shouldn't use intermediate classes at all ;-)).

So what I would do for this specific case? I would rather implement something like the following in the abstract Item class:

public final boolean isConsumable() {
  return this instanceof Consumable;
}

But maybe I wouldn't even supply such a method as it is as good as writing item instanceof Consumable in the first place.

When would I use the default methods of interfaces instead? Maybe when the interface has rather a mixin character or when the implementation makes more sense for the interface then the abstract class, e.g. a specific function of the Consumable I would probably supply as default method there and not in any pseudo-implementing class just so that other classes can then again extend from it... I also really like the following answer (or rather the quote) regarding mixin.

Regarding your edit: "Java does not allow multiple inheritance" ... well, with the mixins something similar as multiple inheritance is possible. You can implement many interfaces and the interfaces themselves can extend also many others. With the default methods you have something reusable in place then :-)

So, why are default methods in interfaces ok to use (or not contradicting the interface definition itself):

  • to supply a simple or naive implementation that already suffices the most use cases (where implementing classes may deliver specific, specialized and/or optimized functionality)
  • when it is clear from all the parameters and the context what the method has to do (and there is no suitable abstract class in place)
  • for template methods, i.e. when they issue calls to abstract methods to perform some work whose scope is broader. Typical example would be Iterable.forEach, which uses the abstract method iterator() of Iterable and applies the provided action to each one of its elements.

Thanks Federico Peralta Schaffner for the suggestions.

Backward compatibility is here for completeness too, but listed seperately as are the functional interfaces: The default implementation also helps to not break existing code, when adding new functions (either by just throwing an exception so that the code still keeps compiling or by supplying an appropriate implementation that works for all implementing classes). For functional interfaces, which is rather a special interface case, the default methods are rather crucial. Functional interfaces can easily be enhanced with functionality, which itself doesn't need any specific implementation. Just consider Predicate as an example.. you supply test, but you get also negate, or and and in addition (supplied as default methods). Lots of functional interfaces supply additional contextual functions via default methods.

Blintze answered 3/9, 2018 at 12:54 Comment(12)
First of all: I share the same opinion about a simple method as my example isConsumable() - I would like to give a more complicated example which makes it necessary to implement instead of using an easier way. I like the idea of your linked post. I also noticed that abstract classes for functionality may be a bad idea - but I still feel like using the default method of interfaces is not the right thing to do because implementing methods in interfaces and using the feature - that was introduced for backwards compatibility - to accomplish that, seems wrong to me.Attach
note that I do not see default methods as backwards compatibility-feature only ;-) I often wished something similar already was available earlier... and I definitely missed the private default methods as well... because sometimes it shouldn't be part of an abstract class but rather already be available in the interface. Just imagine an interface which uses other classes and interfaces but the functionality is so clear, that the interface itself can supply all the required methods already... I wonder whether it is clear what I mean :-)Blintze
If you consider the default methods as more than compatibility only it definitely makes sense to use them. Implementing certain things will be way easier. Note that I am still in the learning-stage of Java so I might not be able to talk from personal experience regarding special fields of Java. The way I understand interfaces has always been: Implementing an interface makes sure that a class provides certain functionality. The interface providing functionality itself (even when it makes sense) is just contradicting the way I learned to understand the use of interfaces.Attach
Ok... let me clarify... most of the times I really like the default methods is, when using them in functional interfaces. There are however also cases where I like the default implementation also in "normal" interfaces, e.g. if I do not want to break the current code. Such an implementation may even just throw an exception. But that's sometimes better then to just have uncompilable code.Blintze
@Attach Not necessarily. Interfaces can provide a default, simple, naive implementation, and some subclasses can then override these simple implementations with their own specific, specialized, optimized implementation.Telegonus
In rare cases however I may also have a default implementation in "normal" interfaces when it is clear from all the parameters and values what the method would return in most cases and if there is no suitable abstract class in place.Blintze
@Attach Another good reason to use default methods is for template methods, i.e. when they issue calls to abstract methods to perform some work whose scope is broader. Typical example would be Iterable.forEach, which uses the abstract method iterator() of Iterable and applies the provided action to each one of the elements returned by the iterator.Telegonus
I think the comments undeaneath the post summarize the point and give a good explanation aswell as providing the answer to my question. If you could summarize the statements of Frederico and yourself in your answer I would happily accept the answer.Attach
@Blintze You've already provided good reasons to use default methods, I've provided two more reasons here in the comments. Please feel free to edit your answer with them if you're OK with it.Telegonus
updated... let me know if I miseed something or whether something isn't clear enough.Blintze
I think you summarized it very well and this answer can be very helpful to everyone learning Java (starter and intermediate). When I started using interfaces a post and answer like this would have been gold - saving me a huge amount of time.Attach
It’s instanceof, with a lowercase o.Subbase

© 2022 - 2024 — McMap. All rights reserved.