Abstract Methods <> Default Methods

Hello, I’d like to know more about the differences between abstract classes and interfaces in Java.

For example, here’s an extract from the decompiled Minecraft source code:

package net.minecraft.world.entity;
// ...
public abstract class LivingEntity extends Entity {
   // ...
   public abstract ItemStack getItemBySlot(EquipmentSlot slot);
   public abstract void setItemSlot(EquipmentSlot slot, ItemStack item);
   // ...
   public boolean canBreatheUnderwater() {
      return this.getMobType() == MobType.UNDEAD;
   }
   // ...
}

But interfaces also support default implementations, as can be seen in this Java interface:

package java.util;
// ...
public interface Iterator<E> {
   boolean hasNext();
   E next();
   default void remove() {
      throw new UnsupportedOperationException("remove");
   }
   default void forEachRemaining(Consumer<? super E> action) {
      Objects.requireNonNull(action);
      while (hasNext())
         action.accept(next());
   }
}

So what difference sets interfaces and abstract classes apart? Is there any difference except for the fact that one class can implement multiple interfaces but extend only one other class?

1 Like

Before the introduction of default implementations, they were more different.
But there are still some differences:

  • As you mentioned the inheritance relation is different
  • loose vs close relation
  • classes allow for protected and private modifiers
  • classes can have a state; interfaces not (non static-final fields for instance)
  • classes allow for constructors
  • an abstract class can overwrite object class methods

What you should use when exactly depends on the guide you are referring to and the exact circumstances.
As far as I am aware, the common ground (without knowing an exact situation) is that one uses interfaces if an abstract class is not needed.

4 Likes

Marcel already gave a very good answer. Let me just add a tiny bit: A Java class can implement multiple interfaces but only extend only one class. The reasons for that are technical. Multiple inheritance (extending multiple proper classes) is notoriously hard/cumbersome to implement and understand/use (C++ has it, but one of the first things that you’re told is to stay away from it).

Without going too much into detail, the problem is that if you were able to extend two proper classes then you can (through a deeper inheritance hierarchy) introduce duplicates of one class, like

class A { }
class B extends A { }
class C extends A { }
class D extends B, C { }

so it is not directly clear which A you mean if you’d say A o = new D(); if A has fields. Are the fields duplicated in D? Or do both A’s in D share the same fields? This is exactly the reason why interfaces in Java are forbidden to have fields.

For the pros: Java interfaces are quite similar to type classes/traits in Haskell/Rust/… They essentially specify a set of operations that an implementing class has to provide. So it’s a good idea thinking of them this way, not as crippled classes. Together with classes they participate in the subtype relationship in Java’s type system.

Finally, I propose to use an interface whenever you want to declare a set of operations that some class shall provide. Use classes if you really want to declare a data structure (with fields). Having that said, the BaseVec2 class could replaced by putting a default implementation of translate in the interface Vec2. :slight_smile:

5 Likes

When learning Rust (my first non-OOP language), I made the mistake of assuming that traits were equivalent to interfaces, not just similar. I didn’t know much about lifetimes and all that at the time, so my code ended up using something like Arc<Mutex<dyn T>> :joy_cat:
I am missing “auto interface implementations” in Java though.

Modern Java allows you to have more fun with interfaces, for example lambda methods for implementing interfaces: Chapter 15. Expressions

You also need to use sealed interfaces if you want to solve the expression problem the nice way :tm: