The main difference lies in semantics. Interfaces originated before Java as an important concept in OOP. The purpose of an interface is to define a certain behavior or describe a property. Ideally, if you follow the
interface segregation principle, it describes a
single aspect of behavior.
A class, even an abstract one, is a combination of all properties
and their implementations that define entities of a certain category (the class itself).
From this arises the natural need for multiple inheritance for interfaces. The experience of languages like C++ has shown that multiple inheritance of classes is unnecessary and problematic (see
the diamond problem). In practice, what is usually needed is simply code reuse, which does not relate to OOP and is implemented in some languages as "interfaces with independent state" –
mixins.
In Java, unlike abstract classes, interfaces
cannot have state. However, behavior implementation is allowed in two cases: for static methods and
default
methods. Static methods are part of the entire class, not the instances. Default implementations were added, as mentioned
earlier, mainly as a hack to maintain compatibility.
Since interfaces are public descriptions, non-public members are meaningless and therefore prohibited. This leads to a syntactical difference:
public
and
abstract
modifiers for methods and
static
modifier for fields are optional and can be omitted. Modifiers that are incompatible with
abstract
, such as
final
,
synchronized
, and others, are also prohibited.
At the compiled bytecode level, there are also small differences: an interface is marked with the
ACC_INTERFACE
flag, and a default constructor is generated for a class.
There is one more small difference: an interface with a single method can be used as a
functional interface and instantiated with a lambda expression. This does not work for an abstract class, even if it has only one method.