When I think about OOP languages, my 2 cents goes into choosing a good contract between the components of your code. I never liked "smart" solutions which tries to steal a little time from here, a little from there and so on. Don't say that there are no good solutions of that type. When you accommodate a generic solution to a specific problem, you might be find that your code is much better. But, to be in that position, you have to have first the best design possible. That gives good solutions in every day practice. And a good OOP design implies healthy contracts between components.
Interfaces apart from classes can define a contract without implementation. Interfaces exists only as pure contracts. And interfaces are firm contracts. If a class implements an interface you can be sure that it will respond to you on the contract terms. Of course, nobody saves you from a bad implementation behind, we should live with that possibility.
Interfaces solves somehow the multiple inheritance problem. Can't agree on that
When we think a contract we have in mind classes which consume services stated by interface and other classes, behind interface, who provide the service. In this way interface acts as an indirection between two classes. You can change always the consumer or the provider of that service, things will go on. There was said that interfaces solves somehow the multiple inheritance problem. Can't agree on that. Interfaces does not provides behavior. The classes which implements multiple interfaces provides the behavior. Still, the implementation classes can't be considered as multiroot inherited. The main difference is that the class which implements multiple interfaces doesn't inherit behavior. And that is the main purpose of multiple inheritance.
Multiple inheritance is a bit clumsy and error prone
I always considered multiple inheritance as a bit clumsy and error prone. When a class inherits two behaviors we live on the edge. If the behaviors are small than there is a possibility to handle things in a logical way. If the behaviors are rich in features, that we are already doomed. That is the worst form of dependency. But even if we are in the best situation possible that an be on short term only. In time things can roll on in a bad way. Considering that the maintenance of the code is the biggest stage of a software component, there will be usually a lot of occasions or opportunities to "improve" and "enrich" the behavior of a component. If a class inherits multiple behavior, these kind of improvements is a curse.
Thanks to James Gosling and others that they don't allowed that in Java. But there are problems which still need to be solved.
Interfaces define how a component should look to be albe to be used by others
Interfaces can define the form of a class. That's why they are called interfaces, right? The interface defines how a component should look to be albe to be used by others. We can have marker interfaces or interfaces with content. Our clases can be less or more polluted by methods implemented from many interfaces. For that we allways have the addapter solution. When we have a class which needs to address many targets we always can split the class in many pieces. The main piece will retain the original goal of the class. The other classes will adapt the main class to othe target, of course, by providing appropriate interface implementations. That address well the mltiple inheritance gap. But can be better, and here comes annotations.
Annotations don't enforce types, don't enforce method signatures or exceptions, like interface does
Annotations are like interfaces, but there are suble differences.Interfaces defines a contract on a whole class. Annotations defines contracts for a class, method, field and others. Like interfaces annotations does not provide behavior. Still, the interfaces defines the form of a possible behavior in a complete way. Annotations don't imply these kind of restrictions. The consumer of annotations will have to handle evenrithing in a more generic manner. Annotations don't enforce types, don't enforce method signatures or exceptions, like interface does. That's way provider classes should alter their code to include the contract of it's implemented interfaces. Annotations don't pollute business code. Don't pollute contract of a class. They just decorate the class.
Annotations can be considered more like a part of the consumer which relies on the provider
Let's take the simplest case for the moment. The marker interfaces. A marker interface is an interface which don't have a body. The are used only to "mark" a component. Just to know at runtime that a class "is a" kind of something. You can't use a marker interface to call methods. A classic example of marker interface is java.io.Serializable. When a class implements Serializable, we know that this class is allowed to be persisted. The interface is only a semantic to serialization. The same effect could be achieved using an annotation on class level. We know that on serialization problem, because of the fact that annotations were not invented at that time. Considering that, the interface solution is a normal solution. Still, if we had to design serialization now, when we have all, what should we choose? I would still choose interface. Both annotations and interfaces are ment to adapt a class to a specific usage. Thought, the interfaces are more a part of a class, more dependent to a class than annotations. Annotations can be considered more like a part of the consumer which relies on the provider. That's why they are more dependent of consumer class than interfaces. If on marker interface teh case is not very clear, we go further.
The only discriminator to choose interface or annotations is the degree of business cognation or similarity
When we want that a consumer class to use another one we usually need that the provider class to tell somethings about that. We can do that using an interface to be implemented by provider class or by using annotations. When we provide information to the consumer we have to fight against code pollution. Even if the provided information is in small or big quantity the problem is still there. The only discriminator to choose interface or annotations is the degree of business cognation or similarity. If we want our class to provide a functionality closer to its bussiness purpose, the most appropriate is interface. If we want our class to be used in a completely different way, apart from it's original scope we should use annotations. Finally, if both ways conduct to a lot of work to adapt, we are probably on a wrong way. We should give a call to adapter or it's cousins to help us because in most of the cases our problem can't be handled by our class. We take a look on two examples for clarifying purpose.
We have a BusinessProcess class which handles information about a process of our business. This class is a java bean, so all properties have setters and getters. Nice simple class. This class provides some methods to run a process, to stop it while is runnings and to collect output data as results.
We have to implement a GUI interface which shows us a log with running events, when tasks have started or stopped. A simple GUI list and that's all. We decide that the model behind the GUI to be a class which handle running business execution events. Because announcing when a process has started or stopped fits very well with our class, we can define an interface for Observer pattern. That interface can have as a sample onStart and onStop methods, with process name as a parameter. So our GUI model will implement our ProcessNotifierListener interface. Our BusinessProcess class will have methods to add or remove listeners of that type, and will call them when any event appeared.
Our classes should not be aware of the problems which don't relates to it's business
Second task is to provide a way to specify the level of logging information for each type of process. The purpose is to show full logs for processes related to security transactions. The other type of processes will have only regular logs saved. We can do that by enforcing and interface with a method. But more appropriate is to use a custom annotation with a property defined. Like @LogLevel(LogLevel.FULL). At runtime we can read the annotation and action appropriate. Why is better to use annotations in this case? Because the logging activity is not even a secondary business purpose of our class. Our classes should not be aware of the problems which don't relates to it's business.
The discussion is never ending. For sure there are not rule appliable everywhere. And you always should reevaluate the case on using interface or annotations. That's whay good programmers are valuable. Thought, at least some of the questions, if not answers, will raise in your mind after reading that. Happy choosing!