Table of Contents
Introduction
An interface in Java is just the definition of the externally visible signatures without the implementation. The benefit is that, if used correctly, which implementation is used at runtime is unimportant to the calling code (AKA like a plugin implementation).
The List interface defines how we can interact with any implementation. For example:
Whether we choose an ArrayList, LinkedList, or some other implementation of that interface may impact runtime performance. One implementation of the interface may be better suited for data that may change frequently, but for which the size doesn’t often grow. Another may better fit a situation where we are unclear of the size of the data and the size may grow / shrink significantly from our initial estimates while it is referenced in the runtime code. Either way, whichever approach is chosen, the impact to the source code should only really be one line – the constructor call. Everywhere else, if we treat it as a plain old List, we can change which type of list is used under the hood without having to refactor the code.
For example:
| Instead Of | Example Using Interface | Notes |
|---|---|---|
| ArrayList<String> myList = new ArrayList<>(); | List<String> myList = new ArrayList<>(); | Local variables in a method or static / instance field variables in a class. |
| boolean isEmpty(ArrayList<String> myList) { | boolean isEmpty(List<String> myList) { | Input parameters for methods. |
| ArrayList<String> getNickNames() { | List<String> getNickNames() { | Return type declarations for methods. |
When consuming an interface, just like we want to avoid any type definitions that call out which implementation we are using, we should also avoid any attempt to determine what implementation is used as we interact with the interface (avoid calls to instanceof). Doing so would make our code brittle if we need to change which list implementation is used in the future.
We aren’t to that point yet, but once we get to full applications, this can be helpful when introducing new / upgraded features. For that scenario, you could setup the logic to choose which implementation of the interface to use at runtime…say as a percentage. That way, you can slowly roll-out a change to a portion of the users, ensuring that it behaves as expected before moving over entirely.
Animals Example
If we were to add plain old java objects (POJOs) for animals, one of the animal’s behavior is whether they lay eggs (such as birds and a few mammals) or give a live birth (most mammals). Since this is an application, either will end up creating a new instance of the POJO. However, let’s say that egg laying causes the animal to be created with a negative age (until it hatches), while a live birth means that the animal is born with an age of 0. While we won’t implement this logic today, we will document these expectations in the new interfaces.
Since some, but not all mammals have live births, we cannot simply tie the behavior to being a bird vs. a mammal. Instead, we can create an interface that shows how to interact with any possibly egg-laying animal and another to show how to interact with any animal that may have a live birth approach.
New Package
Let’s create a new package to house our code. In IntelliJ, this can be done via a right click on src/main/java/codingchica.java101 -> New -> Package -> The full package name will look like: codingchica.java101.model. Don’t include the period at the end. Repeat for src/test/java/codingchica.java101.
Add a new file describing the package for any Javadoc generated: Right click on src/main/java/codingchica.java101.model -> New -> package-info.java. Add a Javadoc comment to the generated code, like:
/**
* This package contains plain old java objects (POJOs) that model the animals
* we want to create.
*/
package codingchica.java101.model;
Unit Tests
Interfaces themselves, don’t have constructors, that is all handled by the classes that implement those interfaces. Therefore, we can skip directly to the unit test for the Animal class.
Let’s add a new a new unit test class. In IntelliJ, this can be done via right click on src/test/java/codingchica.java101.model -> New -> Java Class -> Name = AnimalTest -> Type = Class. We are only going to add one test, as the Animal will be an empty class in our source code, at least for the time being. Therefore, it will only have the default, no-argument constructor that is automatically added during compilation.
Our unit test class will look something like:
package codingchica.java101.model;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
/**
* Unit test for the Animal class.
*/
class AnimalTest {
@Nested
class NoArgConstructor {
@Test
void noArgConstructor_whenInvoked_returnsObject() {
// Setup
// Execution
Animal animal = new Animal();
// Validation
assertNotNull(animal);
}
}
}
package codingchica.java101.model;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
/**
* Unit test for the Animal class.
*/
class AnimalTest {
@Nested
class NoArgConstructor {
@Test
void noArgConstructor_whenInvoked_returnsObject() {
// Setup
// Execution
Animal animal = new Animal();
// Validation
assertNotNull(animal);
}
}
}
Runtime Modifications
Animal Class
Since we created the unit test showing what we expect of the Animal class, let’s start there.
Following the same approach as above, let’s add a new class at: src/main/java/codingchica.java101.model.Animal.java. It is going to be just the shell of the class, as we do not yet need any logic within it. However, this class is needed, because our interfaces will need to return a new Animal instance.
package codingchica.java101.model;
/**
* A class to model a real-world animal.
*/
public class Animal {
}
Maven Build Success
At this point, we should run the Maven build and confirm that it succeeds.
The default, no-argument constructor is the only logic in the Animal class, so our one unit test should generate 100% code coverage.
Adding the EggLayer Interface
Now that we have the animal class that we should use as a return type, let’s create the egg-layer interface.
In IntelliJ, this can be achieved via: Right click on src/main/java/codingchica.java101.model -> New -> Java Class -> Name = EggLayer -> Type = Interface. The skeleton created will use the interface keyword where you are used to seeing the class keyword.
Next, let’s add the method signature for a couple of methods we will require of any class that implements this interface:
package codingchica.java101.model;
/**
* An EggLayers create eggs (shells of new animals) but they are not
* fully usable until they hatch. They will not have a birth date / time.
*/
public interface EggLayer {
Instant
/**
* Whether or not an animal can, at present, lay eggs.
*
* @return A boolean value indicating if the animal is currently
* capable of laying eggs.
*/
boolean canLayEggs();
/**
* Create new shell animals, so we can track them until they hatch.
* Before they hatch, ages should be negative.
*
* @return An array containing zero or more animals.
*/
Animal[] layEggs();
}
Notice that there are no curly braces ({}) for the method body’s code. Instead we just define the method signature (inputs and outputs), then end with the semi-colon (;). This is because there is no code in this interface, it will all be defined in the classes that implement the interface.
Maven Build Success
Again, we should be able to run the Maven build successfully. As there is no constructor / code in our interface, the code coverage minimums do not apply.
Adding the LiveBirth Interface
Follow the same approach to add a new interface named LiveBirth.
package codingchica.java101.model;
/**
* An animal that has a live birthing process can create animal that are
* immediately able to perform their own, possibly limited, actions.
*/
public interface LiveBirth {
/**
* Determine whether the animal is currently pregnant (able to give
* birth). This is, of course, a huge over-simplification, as actual
* animals would be pregnant for a while before they are able to give birth.
*
* @return A boolean value indicating whether the animal is able to give
* birth.
*/
boolean isPregnant();
/**
* The process of creating a new, distinct animal, for which the age will
* initially be set to 0.
*
* @return An array containing zero or more new animals.
*/
Animal[] giveBirth();
}
Maven Build Success
Again, we should be able to successfully run the Maven build after this addition, as it contains no actual code.
Commit
Let’s commit in small, incremental batches. Now is a good time.

Leave a comment