Each new version of Java differs from the previous ones. As an example of such a change from material we've covered, the language did not have enums before Java 5.

Just so, Java 8 is noticeably different from Java 7. We won't ignore important innovations, of course.

Since we are talking about interfaces in this lesson, let's consider one update to the language: default methods in interfaces.

You already know that an interface does not implement behavior. Its purpose is to describe what behavior must exist in all objects that implement the interface.

But developers frequently encountered situations where the implementation of a method was the same in all classes.

Let's look at our old car example:

public interface Car {

   public void gas();

   public void brake();
}
public class Sedan implements Car {

   @Override
   public void gas() {
       System.out.println("Gas!");
   }

   @Override
   public void brake() {
       System.out.println("Brake!");
   }
}


public class Truck implements Car {

   @Override
   public void go() {
       System.out.println("Gas!");
   }

   @Override
   public void brake() {
       System.out.println("Brake!");
   }
}


public class F1Car implements Car {
   @Override
   public void go() {
       System.out.println("Gas!");
   }

   @Override
   public void brake() {
       System.out.println("Brake!");
   }
}

What do you think the main problem with this code is?

You probably noticed that we wrote a bunch of duplicate code! This is a common problem in programming and it should be avoided.

It's another matter that there were no particular solutions before the release of Java 8. When that version came out, it became possible to define default methods and implement them right inside an interface!

Here's how to do it:

public interface Car {

   public default void gas() {
       System.out.println("Gas!");
   }

   public default void brake() {
       System.out.println("Brake!");
   }
}

public class Sedan implements Car {

}

public class Truck implements Car {

}

public class F1Car implements Car {

}

Now the gas() and brake() methods, which were the same for all cars, have been moved into the interface, eliminating the need for duplicate code. And the methods are available in each of the classes!

public class Main {

   public static void main(String[] args) {

       F1Car f1Car = new F1Car();
       Sedan sedan = new Sedan();
       Truck truck = new Truck();
       truck.gas();
       sedan.gas();
       f1Car.brake();
   }
}

What if there are 100 classes with a gas() method, but only 99 of them should have the same behavior? Does that spoil everything? Will a default method not work in this case? Of course not :)

Default interface methods can be overridden.

public class UnusualCar implements Car {
   @Override
   public void go() {
       System.out.println("This car accelerates differently!");
   }

   @Override
   public void brake() {
       System.out.println("This car slows down differently!");
   }
}

All the other 99 types of cars will employ the default method, while the UnusualCar class is an exception. Without spoiling the big picture, it will calmly define its own behavior.

Multiple inheritance in interfaces.

As you already know, there is no multiple inheritance in Java. There are many reasons for this. We will consider them in detail in a separate lesson.

In other languages, such as C++, the situation is reversed. No multiple inheritance presents a serious challenge, since the same object can have several different characteristics and behaviors.

For example, we are children to our parents, students to our teachers, and patients to our doctors. In real life, we fill various roles and, accordingly, behave differently: obviously we interact with teachers differently than with close friends.

Let's try to translate this situation into code. Let's imagine that we have two classes: Pond and Aviary. A pond needs swimming birds, while an aviary needs flying ones.

To represent this, we created two base classes: FlyingBird and Waterfowl.

public class Waterfowl {
}

public class FlyingBird {
}

Accordingly, we will send birds that inherit FlyingBird to the aviary, while those who derive from Waterfowl will go to the pond.

Everything seems straightforward.

But what should we do if we need to define a duck somewhere?

Ducks both swim and fly. But we don't have multiple inheritance.

Fortunately, Java supports multiple implementations of interfaces. If a class cannot inherit multiple parents, implementing multiple interfaces is easy!

Our duck can be a flying bird as well as a swimming bird :) To achieve the desired result, all we need to do is make FlyingBird and Waterfowl interfaces rather than classes.

public class Duck implements FlyingBird, Waterfowl {

   // Methods of both interfaces combine easily into one class

   @Override
   public void fly() {
       System.out.println("Flying!");
   }

   @Override
   public void swim() {

       System.out.println("Swimming!");
   }
}

This means that our program retains the ability to flexibly manage classes. When we combine that with default methods, our ability to determine the behavior of objects becomes almost limitless! :)