Java Recent History — Java 8 [Part 1] — Default Method, Static Method, and Lambda Expressions

Júlio Falbo
Tradeshift Engineering
10 min readOct 11, 2019

Heey people!

Following the Java releases, today I’ll explain the most important changes that we have in Java 8!

Note: If you did not see the article about Java 7, click here!

Java 8 was released officially on March 18, 2014, as you can see here!

For me, this is the most important major release in the history of Java and you will see why!

So, Java 8 had a huge amount of improvements and new features, but I’ll focus on the enhancements that IMHO impacted the daily work of all Java Developers. You can check the complete list here.

As I said, we had a lot of interesting things in Java 8, so, I’ll split this article into 5 parts. Below are the topics that we’ll explore in the different parts.

Part 1

  • Default Method
  • Static Method
  • Lambda Expressions

Part 2

  • Functional Interface
  • Predefined Functional Interface

Part 3

  • Two-Argument (Bi) Functional Interfaces
  • Primitive Type Functional Interfaces

Part 4

  • Method Reference & Constructor Reference by Double Colon(::) Operator
  • ForEach Loop
  • Streams

Part 5

  • Repeating Annotations
  • Date & Time API ( Joda API)

Part 6

  • Type Annotations
  • Improved type inference
  • Method parameter reflection

Here we go with Part 1.

Default Methods

So, let’s imagine that we have an interface that has been implemented by a lot of classes. After some time, you want to introduce a new method in this interface. Can you imagine the headache?

You will break a lot of classes and the effort to refactor (implementing the new method in all implementations of this interface) is huge since an interface is a contract that requires all implementations to follow a pattern.

To solve this headache like a painkiller now we have a default method!

Default Methods enable you to add new functionality to the interfaces of your libraries and ensure binary compatibility with code written for older versions of those interfaces. They are interface methods that have an implementation and the default keyword at the beginning of the method signature. Also, you can define static methods in interfaces.

Another benefit of default methods is that now we can reuse a piece of code helping with maintainability and readability.

Before we check the implementation let’s check all the rules around Default Methods:

  1. The default method must have a body
  2. Implementation classes are not required to override a default method
  3. We can have 0 or N default methods in an interface

So, how does it work when we have multiple inheritances?

To answer it I’ll use a simple example. Let’s imagine that we have two interfaces (OneInterface and TwoInterface) that contains a default method with the same signature (default void defaultMethod(){ … }). What will happen if we try to implement both interfaces in a class (class SomeClass implements OneInterface, TwoInterface)?

You are right, we can not do this. When we try to do this, we receive a compiler error that says: “class SomeClass inherits unrelated defaults for defaultMethod() from types OneInterface and TwoInterface”.

This is a classic problem called “The Diamond Problem”. Before Java 8, Java was not subject to the Diamond problem risk because it did not support multiple inheritances and interface default methods were not available.

Hm, and how can I solve it?

The solution is very simple! We can not forget that we are using an Interface, so the only thing that we need to do is implement the method! Java compiler will override both methods and your class will have only 1 method (your implementation).

Niceee! But I will complicate it a little bit, ok? Let’s imagine that in my implementation I would like to call the default method of a OneInterface, is it possible?

No problem, it is possible!

We can use the super keyword of an Interface and call the default method. Generally super is used to refer immediate parent class instance variable, but in the case of an overridden default method of an interface, we can specify the interface which default implementation you want to invoke.

Implementation:

Hm really nice, but I have a question! Since now we can implement with a body in an interface, what is the difference between an Interface with Default Methods and an Abstract Class?

I already said that I love your questions?

First, let’s understand something that we can/can’t do/have in an Interface.

  1. In the Interface, every variable is always public, static and final. So, we can not declare instance variables
  2. Interfaces never talk about the state of the object
  3. We can not declare constructors to an Interface
  4. We can not declare instance and static blocks in the Interface
  5. Functional Interface with default methods can refer to Lambda Expressions
  6. We can not override object class methods in the Interface

Now let’s understand something that we can/can’t do/have in an Abstract Class.

  1. In the Abstract Class, we can declare instance variables, which are required for the child class.
  2. Abstract Classes can talk about the state of an object
  3. We can have a constructor in the Abstract Class
  4. We can declare instance and static blocks in the Abstract Class
  5. Abstract Classes can not refer to Lambda Expressions
  6. In the Abstract Classes, we can override object class methods

Now that we already know a lot of things about interface and default methods, let’s see the implementation.

Note: In Java 9 we can see a successful implementation of default methods. In the Stream package (added in Java 8), the methods takeWhile and dropWhile were added using a default method strategy.

Implementation:

Static Method

Here I’m going to be very direct.

Imagine a static method in an interface as a normal static method (a static method is a method that is associated with the class in which it is defined rather than with any object. Every instance of the class shares its static methods), that is, we don’t need to implement the Interface to call the method.

This makes it easier for you to organize helper methods in your libraries. We can keep static methods specific to an interface in the same interface rather than in a separate class.

Rules:

  1. To create a static method we need to use the keyword static
  2. The static method must have a body and can not be empty
  3. A static method is used to provide a utility method in an interface
  4. We can not override a static method
  5. A static method can not access a non-static method (like a default method)
  6. We can not have a “default static method”
  7. We can not call a static method throughs implementation
  8. Now is it possible to add “public static void main(String[] args)“ in an interface, it means that now we can execute and interface!

Note: In Java 9 we can see a successful implementation of default methods. In the Stream package (added in Java 8), the methods ofNullable and of were added using a static method strategy.

Lambda Expressions

The most important thing that you need to understand about lambdas: Lambda Expressions are Anonymous Methods (Functions), that is, a method (function) without a name, simple like that!

To support my affirmation, I would like to quote the best definition that I read about Lambda!

One issue with anonymous classes is that if the implementation of your anonymous class is very simple, such as an interface that contains only one method, then the syntax of anonymous classes may seem unwieldy and unclear. In these cases, you’re usually trying to pass functionality as an argument to another method, such as what action should be taken when someone clicks a button. Lambda expressions enable you to do this, to treat functionality as a method argument, or code as data.

So, now we don’t need to spend a lot of lines just to create an implementation to a simple interface (anonymous inner classes, for example), we can use lambda to help us to improve the code legibility!

But most important than code legibility, Lambda brings to Java the concept of Functional Programming!

Wait, what does that mean?

The most important point of Functional Programming is the possibility to pass a function/method as a parameter. Before Java 8 it was impossible, but now, with Lambda, we can send a function/method as a parameter.

Implementation (ignore the syntax, for now, I’ll explain in the next section):

Now that you already know what is a Lambda and when to use it, let’s see the Syntax of Lambda Expressions.

As I said, let’s imagine a simple method implementation of an interface.

Now, let’s remove the access modifiers, the return type, and the types of the parameters. (I’ll explain after the syntax).

So, by default in a Lambda Expression, we have 3 elements: Parameters, token or symbol, and logic.

Here is an example of a Lambda Expression

(parameter, anotherParameter)¹ ->² { return anotherParameter + parameter; }³;

¹ A comma-separated list of formal parameters enclosed in parentheses. But here we have 2 special cases:

Single Parameter — When we have a Single Parameter we can remove the parentheses. e.g.:

parameter -> { return "Hello"; };

No Parameter — When we have a method without a parameter we only need to open and close the parentheses. e.g.:

() -> { return 10;};

² The arrow symbol (also called arrow token) of Lambda Expression (->)

³ Here we have the logic that will be executed when a Lambda is called, and we also have 2 special cases:

Single Line Logic — When we have a Single Line logic, we can remove the return statement and the braces ({}).

(a, b) -> a + b;

Void return

  • With Logic: We can not use the keyword return (even in a block statement)
() -> {
System.out.println("Now is");
System.out.println(LocalDate.now().toString());
};
  • Without Logic: If we want to create an empty lambda without return, the only thing that we need to do is open and close braces
() -> {};

Nice Julio, but you said that you will explain why we need remove the access modifiers, the return type and the parameters types.

Sure, this is really important information about lambdas!

Why do we need to remove the access modifiers, the return type, and the types of the parameters?

Access Modifiers:

Lambda expressions are lexically scoped (also called static scope). This means that they do not inherit any names from a supertype or introduce a new level of scoping. Declarations in a lambda expression are interpreted just as they are in the enclosing environment.

But we have a rule here: Local variables referenced from a lambda expression must be final or effectively final.

Hmm, ok Julio, but if I want to create a count using some local variable, how can I do this since the variable is final?

Amazing question!

To do this, we need to encapsulate our logic in a method that can be called by a final variable.

AtomicInteger atomicInteger = new AtomicInteger(0);CountInterface count = () -> atomicInteger.incrementAndGet();

We have 2 interesting points here:

  1. Since my variable never gets a new value it is considered as effectively final so, I don’t need to use the keyword: final. (But remember that you can not set a new value for this variable). My suggestion here: Always use the keyword final, defensive programming with immutability is really important to a safe code!
  2. As you can see, I needed to use encapsulated logic in another object. Atomics objects are nice to work with Lambdas!

Return Type and Parameters Types:

To determine the type of a lambda expression, the Java compiler uses the target type of the context or situation in which the lambda expression was found, it is called Type Inference.

Short explanation about Type Inference:
“Type inference is a Java compiler’s ability to look at each method invocation and corresponding declaration to determine the type argument (or arguments) that make the invocation applicable. The inference algorithm determines the types of the arguments and, if available, the type that the result is being assigned, or returned. Finally, the inference algorithm tries to find the most specific type that works with all of the arguments.”

It follows that we can only use lambda expressions in situations in which the Java compiler can determine a target type:

  • Variable declarations
  • Assignments
  • Return statements
  • Array initializers
  • Method or constructor arguments
  • Lambda expression bodies
  • Conditional expressions, ?:
  • Cast expressions

For method arguments, the Java compiler determines the target type with two other language features: overload resolution and type argument inference.

Let’s imagine a situation.

We have 2 interfaces that are receiving the same number of parameters with the same type.

interface ConcatStringInterface {
String concat(String param1, String param2);
}
interface PrintInterface {
void print(String param1, String param2);
}

Now I’ll create methods that are receiving the interfaces as a parameter, and suppose that we have overloaded the method invoke as follows:

public static void invoke(PrintInterface p) {
p.print("a", "b");
}
public static String invoke(ConcatStringInterface c) {
return c.concat("a", "b");
}

Which method will be invoked in the following statement?

invoke((param1, param2) -> "Concat" + param1 + param2);

And the answer is:

public static String invoke(ConcatStringInterface c) {
return c.concat("a", "b");
}

The method “invoke(ConcatStringInterface c)” will be invoked because that method returns a value. The method “invoke(PrintInterface p)” does not. In this case, the type of the lambda expression “(param1, param2) -> “Concat” + param1 + param2" is ConcatStringInterface.

And how can I call the other method?

Simple, if we want to call the other method, we can not have any return, so, the correct way to call the method “invoke(PrintInterface p)” is:

invoke((param1, param2) -> {
System.out.println(param1 + param2);
});

And that is it for now!
I hope that you enjoyed this article and I see you in Part 2 when I’ll talk about Functional Interface and Predefined Functional Interface (Predicate, Consumer and Supplier).

Note: You can find all implementations in my Github repository: https://github.com/juliofalbo/java-recent-history

Thank you, bye!

References:

--

--