Here are a few reasons to choose an abstract class by interface. Those that you list - private fields, etc., are one of the main ones. These are slightly more subtle.
- Clarity of types. You can expand only one class. This gives a clearer picture of how your object uses it.
- The problem with the diamond. You must implement all the default methods in the interface in order to avoid a diamond problem. If the interface is Collections, this can be a huge pain, as there are a billion of them. With an abstract class, you only overwrite what you need
- Java 8 has lambda expressions, and the old interface workaround routine with the implementation method was usurped. However, when you see an interface with a standard method, this may be interpreted as you might not have expected.
Here's what Oracle has to say on this:
What should you use abstract classes or interfaces? Consider using abstract classes if any of these statements apply to your situation:
- You want to share code between several related classes.
- You expect classes that extend your abstract class to have many common methods or fields, or require access modifiers other than public ones (like protected and private).
- You want to declare non-static or non-final fields. This allows you to define methods that can access and change the state of the object to which they belong.
In this article, Orace protects the distinction between the two types of systems https://docs.oracle.com/javase/tutorial/java/IandI/abstract.html
EDIT To clarify the problem with the "diamond problem" The problem is described here (and in many other places) http://www.lambdafaq.org/what-about-the-diamond-problem/
The problem arises when you inherit from two places declaring the same method, and you need to select one when allowing a function call. This was never a problem in Java 7-, since you could only extend one class and the interfaces had no methods.
Resolution tactics are now a bit complicated, but here is a good description: (from http://blog.loxal.net/2013/05/java-8-default-interface.html )
To solve the problem with diamond, there is a priority in which the implementation is used: only if the class implements all the default values / optional methods of its interfaces, the code can be compiled and implementations of this class are used. Otherwise, the compiler tries to fix missing implementations with the default implementation interface. And if there are several implementations of the default method, then there is a problem with the diamond, and the compiler rejects the compilation. Also, if the class implements the default interface of the method, the class implementation will be used instead of the default interface.
If you stick to abstract classes, you will never run into this problem. However, if your object is required to implement two interfaces (because you need to add it to lists waiting for these types, for example), and these interfaces have conflicting method signatures, you will need to redefine a whole bunch of methods, even if it means that you just make super-call calls, which defeats the dynamic inheritance sending point. This is not an end to the transaction, but something to consider when structuring a complex Java project.