The idea behind this article is quite simple: try to use interfaces instead of concrete classes whenever is possible!
Today, I'll try to convince you to use interfaces in Java as much as possible instead of concrete classes. The concept of interface in Java is quite powerful and comes with huge potential.
Let's start with a simple example, something already familiar if you are a Java developer:
ArrayList<Integer> myIntegers = new ArrayList<>();
myIntegers.add(1);
myIntegers.add(2);
myIntegers.add(3);
myIntegers.add(1);
// and so on
As you can already see, I just created a list of integers and I'll like to play with it. But, as you can see, I'm using concrete class to define variable type - myIntegers
. If you want to pass this variable as argument to a method, you are allowed to define also concrete classes for method argument types. Going deeper, you can use ArrayList
also for method returning type.
Unfortunately, this approach is not flexible. You should use interfaces as much as possible. Let me continue the example provided to let you understand why we should start to use interfaces. Let's assume, I have a simple method that is doing a mathematical sum:
public int sum(ArrayList<Integer> numbers) {
return numbers.stream().reduce(0, Integer::sum);
}
If I want to pass myIntegers
variable to sum
method, everything will work as expected. Now, what happens if tomorrow there is a production issue reported and in my list I should not have duplicates?! There is a simple fix for my bug, right? I just need to switch from ArrayList
to HashSet
. Yeah, but there is not just a single line to be changed, because I need to change also method parameter type. Please do not duplicate sum
method and create a new one for your desired type! The idea is to try to generalize as much as possible, to follow principle Open-closed principle
from SOLID. So, if I dig a little bit more in Java Collection Framework, I'll discover a magic interface called Collection
which is generic and can be used as method parameter type. Having this in mind, I can easily refactor my sum
method:
public int sum(Collection<Integer> numbers) {
return numbers.stream().reduce(0, Integer::sum);
}
As a result, I can pass myIntegers
variable to this new method signature. In fact, I can replace line ArrayList<Integer> myIntegers = new ArrayList<>();
with Collection<Integer> myIntegers = new ArrayList<>();
. At this moment, I declare my variable as a simple collection and someone else who is going to use it, will not have to know anything about current implementation.
There are few cases when you want to restrict the type and in this situation, you can declare your variable ArrayList
instead of List
or Collection
. If you want me to give you an example, you can think about a scenario when you want to enforce implementation type to ArrayList
instead of LinkedList
to avoid memory impact when you switch implementation type.
If you want, you can even go one step forward and use extends
keyword to define a generic type Collection<? extends Number>
and then you can pass Collection<Integer>
or Collection<Short>
to our sum
method.
Happy coding!