Key Take Aways:
By reading through this article you would get to know about
- What are Java Interfaces?
- Know about type of methods a Java Interface could have.
- Why do Java Interfaces exists if we have abstract classes?
And if you practice by following the unit tests I have written to explain various concepts related to interfaces in this post, you would also get some basic knowledge of utilizing Mockito to mock various situations for your unit testing.
Pre-Requisites
This article assumes that you have some basic knowledge of Java language and have a basic understanding of Object Oriented Programming Concepts.
Technologies Referred Here
- Java programming language version 14.0
- JUnit version 5.6.2
- Mockito version 3.3.3
- Eclipse IDE for Enterprise Java Developers version: 2020-06
Interfaces
Let’s first understand what an interface is. An interface is a way to interact with something. In our day to day life we get to interact with a lot of interfaces that makes our life easy. For example
- A light switch allows us to power on or off a light.
- A car accelerator allows us to operate the car speed.
- A thermostat allows us to control the room temperature.
So how does an interface makes our life easy, that is because an interface
- Only exposes the most important function to the outside world hiding all the non essential information and complexity behind it. For example a thermostat provides you an interface to set the room temperature. But you don’t need to know how it controls and maintain the room temperature, it hides behind all the logic and implementation details for temperature control.
- Once you learn to operate an interface, you can operate anything that implements or provides the same interface to operate it. For example once you learn driving a car, you could drive any car that has the same interface as the one you learnt. So it promotes standardization.
Example Interfaces & Class Diagram
Before we start diving in, here is a diagram depicting all the Java Interfaces and Classes used for explaining the concept in this post.
Java Interfaces
A Java interface is a way to achieve abstraction in the Java Language. It is just like a class but having only method signatures and no body. An interface could have
- Member attributes that are by default constant i.e. public, static and final
- Member methods that are by default abstract and public
Here is an example of a Java Interface:
package com.day2dayjava.tutorials.java.interfaces;
import java.math.BigDecimal;
/**
************************************************************************
* An interface to represent Vehicles. This interface has just methods. *
* *
* @author D2DJ *
************************************************************************
**/
public interface Vehicle {
//Returns the make/brand name of the vehicle manufacturer.
String getMake();
// Returns the make year of the vehicle.
Integer getMakeYear();
// Returns the Vehicle Model
String getModel();
//Returns the detailed specification of the Vehicle.
String getConfiguration();
//Returns the base price of the vehicle that goes out of the box
//without any customizations.
BigDecimal getBasePrice();
}
Since Java 8 you can also add default methods with body and static methods with body too, those are discussed later in this post.
A java interface supports inheritance and could extend one or more java interfaces. For example we could create an interface for Motorcycles that extends the interface Vehicle.
package com.day2dayjava.tutorials.java.interfaces;
/**
************************************************************************
* An interface extending another interface. This interface represents *
* all Vehicles that represent the Vehicle Category Motorcycle. *
* *
* @author D2DJ *
************************************************************************
**/
public interface Motorcycle extends Vehicle {
enum MotorCycleStyle {
STANDARD, CRUISER, SPORT_BIKE, SCOOTER, OFF_ROAD, TOURING
}
//Returns the body style of the car. For example one of Standard, Cruiser,
//Sport Bike, Scooter, Off-road, Touring etc.
MotorCycleStyle getMotorCycleStyle();
}
And let’s create another interface Car that extends Vehicle interface too.
package com.day2dayjava.tutorials.java.interfaces;
/**
************************************************************************
* Example for an interface extending another interface. This interface *
* represents all Vehicles that represent the Vehicle Category Car. *
* *
* @author D2DJ *
************************************************************************
**/
public interface Car extends Vehicle {
enum CarStyle {
COUPE, CONVERTIBLE, SEDAN, WAGON, HATCHBACK, SUV
}
/**
* Member attributes, that are a constant.
**/
int MIN_DOORS = 1;
int MAX_DOORS = 5;
//Returns the body style of the car. For example one of Coupe, Convertible,
//Sedan, Wagon, Hatchback, SUV etc.
CarStyle getCarStyle();
}
Here is a diagram showing this inheritance between the above interfaces:
Java interface also provides another way to achieve polymorphism. For example let’s create some classes implementing these two interfaces.
package com.day2dayjava.tutorials.java.interfaces;
import java.math.BigDecimal;
/**
************************************************************************
* An abstract class providing a default behavior for the common method.*
* *
* @author D2DJ *
************************************************************************
**/
public abstract class GroundVehicle implements Vehicle {
protected String make;
protected Integer makeYear;
protected String model;
protected BigDecimal basePrice;
/**
* @param make the make/brand name of the vehicle manufacturer.
* @param makeYear the make year of the vehicle.
* @param model Vehicle Model.
* @param basePrice Vehicle base price.
*/
protected GroundVehicle(String make, Integer makeYear, String model, BigDecimal basePrice) {
super();
this.make = make;
this.makeYear = makeYear;
this.model = model;
this.basePrice = basePrice;
}
// Returns the make/brand name of the vehicle manufacturer.
@Override
public String getMake() {
return this.make;
}
// Returns the make year of the vehicle.
@Override
public Integer getMakeYear() {
return this.makeYear;
}
// Returns the Vehicle Model
@Override
public String getModel() {
return this.model;
}
@Override
public BigDecimal getBasePrice() {
return this.basePrice;
}
// Prints the detailed specification of the Vehicle.
@Override
public String getConfiguration() {
return String.format("GroundVehicle [make=%s, makeYear=%s, model=%s]", getMake(), getMakeYear(), getModel());
}
}
package com.day2dayjava.tutorials.java.interfaces;
import java.math.BigDecimal;
/**
************************************************************************
* Class representing cars built by Honda. *
* *
* @author D2DJ *
************************************************************************
**/
public class HondaCar extends GroundVehicle implements Car {
private CarStyle carStyle = null;
/**
* @param make the make/brand name of the vehicle manufacturer.
* @param makeYear the make year of the vehicle.
* @param model Vehicle Model
* @param basePrice Vehicle base price.
* @param carStyle the style of the Car
*/
protected HondaCar(String make, Integer makeYear, String model, BigDecimal basePrice, CarStyle carStyle) {
super(make, makeYear, model, basePrice);
this.carStyle = carStyle;
}
@Override
public CarStyle getCarStyle() {
return this.carStyle;
}
@Override
public String getConfiguration() {
return String.format("HondaCar [carStyle=%s, make=%s, makeYear=%s, model=%s]", getCarStyle(), getMake(),
getMakeYear(), getModel());
}
}
package com.day2dayjava.tutorials.java.interfaces;
import java.math.BigDecimal;
/**
************************************************************************
* Class representing cars built by Honda. *
* *
* @author D2DJ *
************************************************************************
**/
public class HondaMotorcycle extends GroundVehicle implements Motorcycle {
private MotorCycleStyle motorCycleStyle = null;
/**
* @param make the make/brand name of the vehicle manufacturer.
* @param makeYear the make year of the vehicle.
* @param model Vehicle Model
* @param basePrice Vehicle base price.
* @param motorCycleStyle the style of the Motorcycle.
*/
protected HondaMotorcycle(String make, Integer makeYear, String model, BigDecimal basePrice,
MotorCycleStyle motorCycleStyle) {
super(make, makeYear, model, basePrice);
this.motorCycleStyle = motorCycleStyle;
}
@Override
public MotorCycleStyle getMotorCycleStyle() {
return this.motorCycleStyle;
}
@Override
public String getConfiguration() {
return String.format("HondaMotorcycle [motorCycleStyle=%s, make=%s, makeYear=%s, model=%s]",
getMotorCycleStyle(), getMake(), getMakeYear(), getModel());
}
}
Now we can write a utility class that operates on Vehicle interface and could utilize the polymorphic nature.
package com.day2dayjava.tutorials.java.interfaces;
import java.math.BigDecimal;
import java.time.LocalDate;
/**
************************************************************************
* Utility class to calculate a vehicle's current value based on its age*
* considering that a vehicle's value decreases due to depreciation. *
* *
* @author D2DJ *
************************************************************************
**/
public abstract class VehicleDepreciationCalculator {
private static final int DEPRECIATION_EVERY_YEAR = 500;
/**
* Returns the current vehicle value based upon it's age.
*
* @param vehicle the vehicle.
* @return current vehicle value.
*/
public static final BigDecimal getCurrentVehicleValue(Vehicle vehicle) {
BigDecimal vehicleAge = BigDecimal.valueOf(LocalDate.now().getYear() - vehicle.getMakeYear());
BigDecimal depreciationEveryYear = new BigDecimal(DEPRECIATION_EVERY_YEAR);
BigDecimal vehicleBasePrice = vehicle.getBasePrice();
BigDecimal currentVehicleValue = vehicleBasePrice.subtract(vehicleAge.multiply(depreciationEveryYear));
BigDecimal zeroValue = BigDecimal.valueOf(0);
return currentVehicleValue.compareTo(zeroValue) > 0 ? currentVehicleValue : zeroValue;
}
}
Let’s test this polymorphic behavior with a JUnit test.
package com.day2dayjava.tutorials.java.interfaces;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.mockito.Mockito.when;
import java.math.BigDecimal;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
/**
************************************************************************
* Test class for VehicleDepreciationCalculator. *
* *
* @author D2DJ *
************************************************************************
**/
@ExtendWith(MockitoExtension.class)
class TestVehicleDepreciationCalculator {
@Mock
private HondaCar hondaCar;
@Mock
private HondaMotorcycle hondaMotorcycle;
/**
* Test method for
* {@link com.day2dayjava.tutorials.java.interfaces.VehicleDepreciationCalculator#getCurrentVehicleValue(
* com.day2dayjava.tutorials.java.interfaces.Vehicle)}.
*/
@Test
void testGetCurrentVehicleValue() {
//Setup Honda car Mock
when(hondaCar.getMakeYear()).thenReturn(2020);
when(hondaCar.getBasePrice()).thenReturn(BigDecimal.valueOf(30000));
//Setup Honda Motorcycle Mock
when(hondaMotorcycle.getMakeYear()).thenReturn(2020);
when(hondaMotorcycle.getBasePrice()).thenReturn(BigDecimal.valueOf(5000));
assertEquals(BigDecimal.valueOf(30000), VehicleDepreciationCalculator.getCurrentVehicleValue(hondaCar));
assertEquals(BigDecimal.valueOf(5000), VehicleDepreciationCalculator.getCurrentVehicleValue(hondaMotorcycle));
//Let's change the make to 5 years back and test.
when(hondaCar.getMakeYear()).thenReturn(2015);
when(hondaMotorcycle.getMakeYear()).thenReturn(2015);
assertEquals(BigDecimal.valueOf(27500), VehicleDepreciationCalculator.getCurrentVehicleValue(hondaCar));
assertEquals(BigDecimal.valueOf(2500), VehicleDepreciationCalculator.getCurrentVehicleValue(hondaMotorcycle));
}
}
As you may see above when we run this test it passes with the flying colors i.e. with no issues. So as you may have noticed that the method getCurrentVehicleValue(…) in our utility class VehicleDepreciationCalculator accepts objects from any classes that have implemented a Vehicle, Car or Motorcycle Interface.
A java interface provides a way to enforce a contract for the implementing classes to either provide an implementation for the methods exposed by the interface or declare themselves as abstract without any deviation, an exception to that are the marker interfaces.
Marker Interfaces
Marker interfaces mostly works like a tag on the class, that identify a special operation being handled by that class. These interfaces are mostly used to give a hint to the compiler that classes implementing them would have a special behavior and those should be handled carefully. These interfaces are empty interfaces without any function present in them.
Let’s take a look at one of the example marker interface that we could create for our continuing vehicle example.
package com.day2dayjava.tutorials.java.interfaces;
/**
************************************************************************
* A marker interface that if implemented marks a vehicle as a ZEV. *
* *
* @author D2DJ *
************************************************************************
**/
public interface ZeroEmissionVehicle {
}
Now we can use this marker interface to mark a vehicle as a zero emission vehicle. For example let’s create another class implementing the Car interface and mark it as a zero emission vehicle.
package com.day2dayjava.tutorials.java.interfaces;
import java.math.BigDecimal;
/**
************************************************************************
* Class representing cars built by Tesla. *
* *
* @author D2DJ *
************************************************************************
**/
public class TeslaCar extends GroundVehicle implements Car, ZeroEmissionVehicle {
private CarStyle carStyle = null;
/**
* @param make the make/brand name of the vehicle manufacturer.
* @param makeYear the make year of the vehicle.
* @param model Vehicle Model
* @param basePrice Vehicle base price.
* @param carStyle the style of the Car
*/
protected TeslaCar(String make, Integer makeYear, String model, BigDecimal basePrice, CarStyle carStyle) {
super(make, makeYear, model, basePrice);
this.carStyle = carStyle;
}
@Override
public CarStyle getCarStyle() {
return this.carStyle;
}
@Override
public String getConfiguration() {
return String.format("TeslaCar [carStyle=%s, make=%s, makeYear=%s, model=%s]", getCarStyle(), getMake(),
getMakeYear(), getModel());
}
}
Now we can utilize the fact that a car is a zero emission car to check it’s eligibility to drive in a car pool lane. For example let’s create a utility class to do that.
package com.day2dayjava.tutorials.java.interfaces;
/**
************************************************************************
* Utility class that checks whether a vehicle is eligible to be driven *
* in a car pool lane. *
* *
* @author D2DJ *
************************************************************************
**/
public abstract class CarPoolLaneEligibilityChecker {
/**
* Verifies the eligibility of a vehicle to be driven in a car pool lane.
*
* @param vehicle vehicle to verify.
* @return whether it is eligible or not.
*/
public static boolean isEligible(Vehicle vehicle) {
if (vehicle instanceof ZeroEmissionVehicle) {
return true;
}
return false;
}
}
Let’s check the behavior implemented by this utility class with a JUnit test.
package com.day2dayjava.tutorials.java.interfaces;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
/**
************************************************************************
* Test class for CarPoolLaneEligibilityChecker. *
* *
* @author D2DJ *
************************************************************************
**/
@ExtendWith(MockitoExtension.class)
class TestCarPoolLaneEligibilityChecker {
@Mock
HondaCar hondaCar;
@Mock
TeslaCar teslaCar;
/**
* Test method for {@link com.day2dayjava.tutorials.java.interfaces.CarPoolLaneEligibilityChecker#isEligible(com.day2dayjava.tutorials.java.interfaces.Vehicle)}.
*/
@Test
final void testIsEligible() {
assertFalse(CarPoolLaneEligibilityChecker.isEligible(hondaCar));
assertTrue(CarPoolLaneEligibilityChecker.isEligible(teslaCar));
}
}
These both assertions passes as Honda car has not marked itself as a ZeroEmissionVehicle in our above example classes. Here is the result screen from eclipse after running this test.
Functional Interfaces
Java 8 introduced the new concept of Functional Interfaces. A functional interface is any interface that has only one abstract method i.e. a method that does not have an implementation. You can still have default and static methods with implementations inside that interface but the number of methods that are not implemented should not be more than one. Such interfaces in addition to the normal classes can also be implemented by the my post on Java Lambda Expressions to learn more about the lambda expressions.
. You may refer toFor now let’s use the VehicleWash interface as our functional interface that we have described in the Interfaces and Java Generics section. This interface has a single abstract method wash(…). Here is a JUnit test case that implements this interface with a lambda expression and then use it’s wash method. If you find it hard to follow then you can skip this for now and come back to this section once you have gone through my post on lambda expressions.
package com.day2dayjava.tutorials.java.interfaces;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.mockito.Mockito.when;
import java.io.ByteArrayOutputStream;
import java.io.PrintStream;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import com.day2dayjava.tutorials.java.interfaces.Car.CarStyle;
/**
************************************************************************
*Tests for functional interface VehicleWash with a lambda expression. *
* *
* @author D2DJ *
************************************************************************
**/
@ExtendWith(MockitoExtension.class)
class TestCarWashWithLambda {
private final ByteArrayOutputStream outContent = new ByteArrayOutputStream();
private final PrintStream originalOut = System.out;
@Mock
private HondaCar hondaCar;
@BeforeEach
public void setUpStreams() {
System.setOut(new PrintStream(outContent));
}
@AfterEach
public void restoreStreams() {
System.setOut(originalOut);
}
/**
* Test method for {@link com.day2dayjava.tutorials.java.interfaces.VehicleWash#wash(com.day2dayjava.tutorials.java.interfaces.Vehicle)}.
*/
@Test
final void testVehicleWash() {
when(hondaCar.getMake()).thenReturn("Honda");
when(hondaCar.getMakeYear()).thenReturn(2020);
when(hondaCar.getModel()).thenReturn("CRV EXL");
when(hondaCar.getCarStyle()).thenReturn(CarStyle.SUV);
when(hondaCar.getConfiguration()).thenCallRealMethod();
VehicleWash<Vehicle> vehicleWashStation = (vehicle) -> {
System.out.print(String.format("Your \"%s\" is clean now.", vehicle.getConfiguration()));
};
vehicleWashStation.wash(hondaCar);
assertEquals("Your \"HondaCar [carStyle=SUV, make=Honda, makeYear=2020, model=CRV EXL]\" is clean now.",
outContent.toString());
}
}
Types of Concrete Methods
Since Java 8 you can also add a default method with body and static methods with body too, let’s take a look at an example of each.
Static Methods
As you might have noticed there was a lot of proliferation of utility classes in almost every application or APIs that a developer gets a chance to work on. Although you would never need to create an instance of such classes because all those utility methods were mostly created in an abstract class as a static method. Now you can do the same but using interfaces. Static methods follow the same rules in interface as they follow in java classes. Here is an example of an interface having a static method.
package com.day2dayjava.tutorials.java.interfaces;
import java.util.Collections;
import java.util.List;
import java.util.TreeMap;
import java.util.stream.Collectors;
/**
************************************************************************
* This interface provides a utility method to select the latest *
* vehicle out of the given vehicles. *
* @author D2DJ *
************************************************************************
**/
public interface LatestMakeVehicleSelector {
static List<Vehicle> getLatestMakeVehicles(List<Vehicle> vehicles) {
if (vehicles != null) {
return vehicles.stream()
.collect(Collectors.groupingBy(Vehicle::getMakeYear, TreeMap::new, Collectors.toList())).lastEntry()
.getValue();
}
return Collections.emptyList();
}
}
Let’s try to use the interface’s static method with a JUnit test.
package com.day2dayjava.tutorials.java.interfaces;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.mockito.Mockito.when;
import java.util.Arrays;
import java.util.List;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
/**
************************************************************************
* Test class for LatestMakeVehicleSelector. *
* *
* @author D2DJ *
************************************************************************
**/
@ExtendWith(MockitoExtension.class)
class TestLatestMakeVehicleSelector {
@Mock
Vehicle vehicleOne, vehicleTwo, vehicleThree, vehicleFour, vehicleFive;
/**
* Test method for {@link com.day2dayjava.tutorials.java.interfaces.LatestMakeVehicleSelector#getLatestMakeVehicles(java.util.List)}.
*/
@Test
final void testGetLatestMakeVehicles() {
//Setup the Mock.
when(vehicleOne.getMakeYear()).thenReturn(2020);
when(vehicleTwo.getMakeYear()).thenReturn(2015);
when(vehicleThree.getMakeYear()).thenReturn(2019);
when(vehicleFour.getMakeYear()).thenReturn(2020);
when(vehicleFive.getMakeYear()).thenReturn(2015);
List<Vehicle> latestVehicles = LatestMakeVehicleSelector
.getLatestMakeVehicles(Arrays.asList(vehicleOne, vehicleTwo, vehicleThree, vehicleFour, vehicleFive));
assertEquals(vehicleOne, latestVehicles.get(0));
assertEquals(vehicleFour, latestVehicles.get(1));
assertEquals(vehicleOne.getMakeYear(), latestVehicles.get(0).getMakeYear());
assertEquals(vehicleFour.getMakeYear(), latestVehicles.get(1).getMakeYear());
assertEquals(latestVehicles.size(), 2);
}
}
This test passes with all green colors.
Default Methods
Default methods are the methods that you declare and define with the “default” keyword in the method signature. All the implementing classes do not have to override it or declare themselves as abstract if they don’t want to implement a default method. They will simply inherit the implementation provided by the interface. Default methods allow the interface evolution.
You may either use them when
- You want to add or expose a new capability through your existing interface without breaking the interface’s contract with the implementing classes. In that case all the existing classes that are implementing this interface would just inherit the default behavior from the interface. But of-course they can override this method if they want to in order for them to provide an implementation that is more specific to them.
- You are writing a new interface and you want to provide an out of box implementation of those methods.
Note: Interface’s do not have a state, so default methods can not work with any state, they can only work with the method’s arguments. however you are free to use the this operator in a default method in that case this refers to the instance of the implementing class on which behalf this method was called.
Let’s continue on our Vehicle track and let’s say now we want to add a new feature that allows a car to have an economy mode of driving. In that case we could enhance our current Vehicle interface as follows.
package com.day2dayjava.tutorials.java.interfaces;
import java.math.BigDecimal;
/**
************************************************************************
* An interface to represent Vehicles. This interface has just methods. *
* *
* @author D2DJ *
************************************************************************
**/
public interface Vehicle {
//Returns the make/brand name of the vehicle manufacturer.
String getMake();
// Returns the make year of the vehicle.
Integer getMakeYear();
// Returns the Vehicle Model
String getModel();
//Returns the detailed specification of the Vehicle.
String getConfiguration();
//Returns the base price of the vehicle that goes out of the box
//without any customizations.
BigDecimal getBasePrice();
//Default method that provides each vehicle a capability to run in economy mode.
//saving fuel and increasing mileage.
default void turnOnEconomyMode() {
throw new UnsupportedOperationException("Eco mode is not available for your vehicle.");
}
}
And let’s override this default behavior which is just throwing an exception saying that “Eco mode is not available for your vehicle.”. Let’s override it in our HondaCar class but not in TeslaCar and see what we get when try to access the newly added feature method.
package com.day2dayjava.tutorials.java.interfaces;
import java.math.BigDecimal;
/**
************************************************************************
* Class representing cars built by Honda. *
* *
* @author D2DJ *
************************************************************************
**/
public class HondaCar extends GroundVehicle implements Car {
private CarStyle carStyle = null;
/**
* @param make the make/brand name of the vehicle manufacturer.
* @param makeYear the make year of the vehicle.
* @param model Vehicle Model
* @param basePrice Vehicle base price.
* @param carStyle the style of the Car
*/
protected HondaCar(String make, Integer makeYear, String model, BigDecimal basePrice, CarStyle carStyle) {
super(make, makeYear, model, basePrice);
this.carStyle = carStyle;
}
@Override
public CarStyle getCarStyle() {
return this.carStyle;
}
@Override
public String getConfiguration() {
return String.format("HondaCar [carStyle=%s, make=%s, makeYear=%s, model=%s]", getCarStyle(), getMake(),
getMakeYear(), getModel());
}
@Override
public void turnOnEconomyMode() {
System.out.print("Economy mode is on now.");
}
}
Now let’s check the behavior of accessing this method for Honda and Tesla car objects.
package com.day2dayjava.tutorials.java.interfaces;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.mockito.Mockito.doCallRealMethod;
import static org.mockito.Mockito.verify;
import java.io.ByteArrayOutputStream;
import java.io.PrintStream;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
/**
************************************************************************
* Tests the default method feature provided by Java interfaces. *
* *
* @author D2DJ *
************************************************************************
**/
@ExtendWith(MockitoExtension.class)
class TestDefaultMethods {
private final ByteArrayOutputStream outContent = new ByteArrayOutputStream();
private final PrintStream originalOut = System.out;
@Mock
private HondaCar hondaCar;
@Mock
private TeslaCar teslaCar;
@BeforeEach
public void setUpStreams() {
System.setOut(new PrintStream(outContent));
}
@AfterEach
public void restoreStreams() {
System.setOut(originalOut);
}
/**
* Test method for {@link com.day2dayjava.tutorials.java.interfaces.Vehicle#turnOnEconomyMode()}.
*/
@Test
final void testDefaultMethods() {
//Honda Car would run it's overridden method and would not throw the exception thrown by the default implementation.
doCallRealMethod().when(hondaCar).turnOnEconomyMode();
hondaCar.turnOnEconomyMode();
verify(hondaCar).turnOnEconomyMode();
assertEquals("Economy mode is on now.", outContent.toString());
doCallRealMethod().when(teslaCar).turnOnEconomyMode();
//Tesla Car would throw the exception thrown by the default implementation as it doesn't override the default behavior.
UnsupportedOperationException unsupportedOperationException = assertThrows(UnsupportedOperationException.class,
teslaCar::turnOnEconomyMode,
"Tesla car is not supposed to have a economy mode and should have thrown UnsupportedOperationException.");
assertEquals("Eco mode is not available for your vehicle.", unsupportedOperationException.getMessage());
}
}
When we run this test we can see that it passes successfully. That asserts that TeslaCar object threw an UnsupportedOperationException because it exibited the default behavior that it inherited from the Vehicle interface. Whereas the HondaCar object exhibited the behavior that the HondaCar provided for this method.
Private Methods
From Java 9 onward you can include private methods in Java interfaces. These private methods can only be accessed within the interface defining them and can not be inherited or used by the implementing classes or extending interfaces. You could add a static or non static private method. A static private method could be called by other static and non static member methods in the same interface. Whereas a non static private method could only be used by the other non static member methods. Here is an example of an interface that has a private static method.
package com.day2dayjava.tutorials.java.interfaces;
import java.math.BigDecimal;
import java.math.RoundingMode;
/**
************************************************************************
* Utility interface to keep the helper calculation methods for various *
* vehicle matrices. *
* *
* @author D2DJ *
************************************************************************
**/
public interface VehicleMetricsCalculationHelper {
/**
* Returns the remaining distance Miles to which a vehicle could drive given the remaining fuel.
*
* @param vehiclesMilesPerGallon vehicle average MPG
* @param remainingFuelInGallons remaining fuel in vehicle.
* @return how many miles the vehicle can go with the given remaining fuel.
*/
public static BigDecimal getAvailableDrivingDistanceInMiles(BigDecimal vehiclesMilesPerGallon,
BigDecimal remainingFuelInGallons) {
return getAvailableDrivingDistance(vehiclesMilesPerGallon, remainingFuelInGallons);
}
/**
* Returns the remaining distance in KiloMeters to which a vehicle could drive given the remaining fuel.
*
* @param vehiclesKiloMetersPerLitre vehicle average KMPL
* @param remainingFuelInLitres remaining fuel in vehicle.
* @return how many Kilo Meters the vehicle can go with the given remaining fuel.
*/
public static BigDecimal getAvailableDrivingDistanceInKiloMeters(BigDecimal vehiclesKiloMetersPerLitre,
BigDecimal remainingFuelInLitres) {
return getAvailableDrivingDistance(vehiclesKiloMetersPerLitre, remainingFuelInLitres);
}
/**
* Returns the remaining distance to which a vehicle could drive given the remaining fuel.
*
* @param vehicesFuelAverage vehicle average.
* @param remainingFuel remaining fuel in vehicle.
* @return how far the vehicle can go with the given remaining fuel.
*/
private static BigDecimal getAvailableDrivingDistance(BigDecimal vehicesFuelAverage, BigDecimal remainingFuel) {
return remainingFuel.divide(vehicesFuelAverage, RoundingMode.DOWN);
}
}
Let’s test these public interface methods to see that they are calling the private static method internally.
package com.day2dayjava.tutorials.java.interfaces;
import static com.day2dayjava.tutorials.java.interfaces.VehicleMetricsCalculationHelper.getAvailableDrivingDistanceInKiloMeters;
import static com.day2dayjava.tutorials.java.interfaces.VehicleMetricsCalculationHelper.getAvailableDrivingDistanceInMiles;
import static org.junit.jupiter.api.Assertions.assertEquals;
import java.math.BigDecimal;
import org.junit.jupiter.api.Test;
/**
************************************************************************
* Test class for VehicleMetricsCalculationHelper *
* *
* @author D2DJ *
************************************************************************
**/
class TestVehicleMetricsCalculatorHelper {
/**
* Test method for {@link com.day2dayjava.tutorials.java.interfaces.VehicleMetricsCalculationHelper#getAvailableDrivingDistanceInMiles(java.math.BigDecimal,java.math.BigDecimal)}.
*/
@Test
final void testGetAvailableDrivingDistanceInMiles() {
BigDecimal vehiclesMilesPerGallon = BigDecimal.valueOf(25);
BigDecimal remainingFuelInGallons = BigDecimal.valueOf(2.5);
BigDecimal availableDrivingDistanceInMiles = getAvailableDrivingDistanceInMiles(vehiclesMilesPerGallon,
remainingFuelInGallons);
assertEquals(BigDecimal.valueOf(62.5), availableDrivingDistanceInMiles);
}
/**
* Test method for {@link com.day2dayjava.tutorials.java.interfaces.VehicleMetricsCalculationHelper#getAvailableDrivingDistanceInKiloMeters(java.math.BigDecimal,java.math.BigDecimal)}.
*/
@Test
final void testGetAvailableDrivingDistanceInKiloMeters() {
BigDecimal vehiclesKiloMetersPerLitre = BigDecimal.valueOf(25);
BigDecimal remainingFuelInLitres = BigDecimal.valueOf(2.5);
BigDecimal availableDrivingDistanceInKiloMeters = getAvailableDrivingDistanceInKiloMeters(
vehiclesKiloMetersPerLitre, remainingFuelInLitres);
assertEquals(BigDecimal.valueOf(62.5), availableDrivingDistanceInKiloMeters);
}
}
And we can see in the below results that both the tests passed.
In a similar way you could add private default methods too. I am not adding an example of that here and would let you to give it a try and practice.
Interfaces and Java Generics
You can use Java Generics feature with interfaces the same way you could use it in classes. For example you can create a generic VehicleWash interface that represents a vehicle wash facility and exposes the basic wash operation.
Generic Interface
package com.day2dayjava.tutorials.java.interfaces;
/**
************************************************************************
* This interface represents a vehicle wash facility and exposes the *
* basic wash operation. *
* *
* @author D2DJ *
************************************************************************
**/
public interface VehicleWash<T extends Vehicle> {
void wash(T vehicle);
}
And then can do a generic or a specific implementation of this interface. Here are the examples for each type
Generic Implementation
package com.day2dayjava.tutorials.java.interfaces;
/**
************************************************************************
* Generic implementation of VehicleWash that can wash any kind of *
* Vehicles. *
* *
* @author D2DJ *
************************************************************************
**/
public class GenericVehicleWash<T extends Vehicle> implements VehicleWash<T> {
@Override
public void wash(T vehicle) {
System.out.println(String.format("Your \"%s\" is clean now.", vehicle.getConfiguration()));
}
}
Let’s test this generic implementation with a JUnit test.
package com.day2dayjava.tutorials.java.interfaces;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import com.day2dayjava.tutorials.java.interfaces.Car.CarStyle;
import com.day2dayjava.tutorials.java.interfaces.Motorcycle.MotorCycleStyle;
/**
************************************************************************
* Tests for GenericVehicleWash. *
* *
* @author D2DJ *
************************************************************************
**/
@ExtendWith(MockitoExtension.class)
class TestGenericVehicleWash {
@Mock
private HondaCar hondaCar;
@Mock
private HondaMotorcycle hondaMotorcycle;
/**
* Test method for {@link com.day2dayjava.tutorials.java.interfaces.GenericVehicleWash#wash(com.day2dayjava.tutorials.java.interfaces.Vehicle)}.
*/
@Test
final void testWash() {
//Setup the mocks.
when(hondaCar.getMake()).thenReturn("Honda");
when(hondaCar.getMakeYear()).thenReturn(2020);
when(hondaCar.getModel()).thenReturn("CRV EXL");
when(hondaCar.getCarStyle()).thenReturn(CarStyle.SUV);
when(hondaCar.getConfiguration()).thenCallRealMethod();
when(hondaMotorcycle.getMake()).thenReturn("Honda");
when(hondaMotorcycle.getMakeYear()).thenReturn(2020);
when(hondaMotorcycle.getModel()).thenReturn("Pulsor");
when(hondaMotorcycle.getMotorCycleStyle()).thenReturn(MotorCycleStyle.STANDARD);
when(hondaMotorcycle.getConfiguration()).thenCallRealMethod();
VehicleWash<HondaCar> vehicleCarWashStation = new GenericVehicleWash<>();
vehicleCarWashStation.wash(hondaCar);
verify(hondaCar).getConfiguration();
VehicleWash<HondaMotorcycle> vehicleMotorcycleWashStation = new GenericVehicleWash<>();
vehicleMotorcycleWashStation.wash(hondaMotorcycle);
verify(hondaMotorcycle).getConfiguration();
}
}
You can see that with the generic interface and generic implementation you could use the same implementation of wash for both a car and a motorcycle. And you can see the test execution results below that passed all our assertions validating that the proper class methods were called on executing the generic wash method.
And the following output at the console:
Your "HondaCar [carStyle=SUV, make=Honda, makeYear=2020, model=CRV EXL]" is clean now. Your "HondaMotorcycle [motorCycleStyle=STANDARD, make=Honda, makeYear=2020, model=Pulsor]" is clean now.
Specific Implementation
package com.day2dayjava.tutorials.java.interfaces;
/**
************************************************************************
* Specific implementation of VehicleWash that washes only cars. *
* *
* @author D2DJ *
************************************************************************
**/
public class CarWash implements VehicleWash<Car> {
@Override
public void wash(Car car) {
System.out.println(String.format("Your car \"%s\" is clean now.", car.getConfiguration()));
}
}
Let’s write a small program to test this specific to Cars implementation of VehicleWash.
package com.day2dayjava.tutorials.java.interfaces;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import com.day2dayjava.tutorials.java.interfaces.Car.CarStyle;
/**
************************************************************************
* Tests for class CarWash *
* *
* @author D2DJ *
************************************************************************
**/
@ExtendWith(MockitoExtension.class)
class TestCarWash {
@Mock
private HondaCar hondaCar;
/**
* Test method for {@link com.day2dayjava.tutorials.java.interfaces.CarWash#wash(com.day2dayjava.tutorials.java.interfaces.Car)}.
*/
@Test
final void testWash() {
//Setup the mocks.
when(hondaCar.getMake()).thenReturn("Honda");
when(hondaCar.getMakeYear()).thenReturn(2020);
when(hondaCar.getModel()).thenReturn("CRV EXL");
when(hondaCar.getCarStyle()).thenReturn(CarStyle.SUV);
when(hondaCar.getConfiguration()).thenCallRealMethod();
VehicleWash<Car> carWashStation = new CarWash();
carWashStation.wash(hondaCar);
verify(hondaCar).getConfiguration();
}
}
And here are the results of running this unit test for our CarWash implementation.
And the following output at the console:
Your car "HondaCar [carStyle=SUV, make=Honda, makeYear=2020, model=CRV EXL]" is clean now.
Same way we could also implement this interface for the Motorcycle wash.
package com.day2dayjava.tutorials.java.interfaces;
/**
************************************************************************
* Specific implementation of VehicleWash that washes only motorcycles. *
* *
* @author D2DJ *
************************************************************************
**/
public class MotorcycleWash implements VehicleWash<Motorcycle> {
@Override
public void wash(Motorcycle motorcycle) {
System.out.println(String.format("Your motorcycle \"%s\" is clean now.", motorcycle.getConfiguration()));
}
}
And let’s write a small JUnit test for this class too.
package com.day2dayjava.tutorials.java.interfaces;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import com.day2dayjava.tutorials.java.interfaces.Motorcycle.MotorCycleStyle;
/**
************************************************************************
* Tests for class MotorcycleWash *
* *
* @author D2DJ *
************************************************************************
**/
@ExtendWith(MockitoExtension.class)
class TestMotorcycleWash {
@Mock
private HondaMotorcycle hondaMotorcycle;
/**
* Test method for {@link com.day2dayjava.tutorials.java.interfaces.MotorcycleWash#wash(com.day2dayjava.tutorials.java.interfaces.Motorcycle)}.
*/
@Test
final void testWash() {
//Setup the mocks.
when(hondaMotorcycle.getMake()).thenReturn("Honda");
when(hondaMotorcycle.getMakeYear()).thenReturn(2020);
when(hondaMotorcycle.getModel()).thenReturn("Pulsor");
when(hondaMotorcycle.getMotorCycleStyle()).thenReturn(MotorCycleStyle.STANDARD);
when(hondaMotorcycle.getConfiguration()).thenCallRealMethod();
VehicleWash<Motorcycle> motorcycleWashStation = new MotorcycleWash();
motorcycleWashStation.wash(hondaMotorcycle);
verify(hondaMotorcycle).getConfiguration();
}
}
And here are the results of running this unit test for our MotorcycleWash implementation.
And the following output at the console:
Your motorcycle "HondaMotorcycle [motorCycleStyle=STANDARD, make=Honda, makeYear=2020, model=Pulsor]" is clean now.
Abstract Classes Vs Interfaces
There are a lot of things that are common between abstract classes and interfaces and sometimes you will find yourself wondering why both things still exist in Java. Mostly you should choose what to use based on some basic criteria or conventions. For example
- If the only thing you are going to have in your class are the static methods and never going to create an instance of it then better use an interface.
- A class can implement more than one interface and inherit their default methods and still can extend another class if needed. So you can use multiple inheritance using interfaces but not abstract classes.
- Abstract classes are mostly used as a parent class to abstract out the common code related to the child classes for reuse, provide default implementations of some methods or control the accessibility of certain operations for the extending classes.
- Methods declared in an interface are always public in the implementing classes.
Source Download
You can download full eclipse project from my Github repository using the following link: https://github.com/Day2DayJava/java
What’s Next
Next we will learn about the Java lambda Expressions.