DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Refcards Trend Reports
Events Video Library
Refcards
Trend Reports

Events

View Events Video Library

Zones

Culture and Methodologies Agile Career Development Methodologies Team Management
Data Engineering AI/ML Big Data Data Databases IoT
Software Design and Architecture Cloud Architecture Containers Integration Microservices Performance Security
Coding Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Culture and Methodologies
Agile Career Development Methodologies Team Management
Data Engineering
AI/ML Big Data Data Databases IoT
Software Design and Architecture
Cloud Architecture Containers Integration Microservices Performance Security
Coding
Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance
Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks

Generative AI has transformed nearly every industry. How can you leverage GenAI to improve your productivity and efficiency?

SBOMs are essential to circumventing software supply chain attacks, and they provide visibility into various software components.

Related

  • Advanced Brain-Computer Interfaces With Java
  • Simplify Java: Reducing Unnecessary Layers and Interfaces [Video]
  • Java 21 SequenceCollection: Unleash the Power of Ordered Collections
  • Projections/DTOs in Spring Data R2DBC

Trending

  • How to Reduce Technical Debt With Artificial Intelligence (AI)
  • Docker Model Runner: Running AI Models Locally Made Simple
  • Decoding the Secret Language of LLM Tokenizers
  • Secret Recipe of the Template Method: Po Learns the Art of Structured Cooking
  1. DZone
  2. Coding
  3. Java
  4. Java Lambda Expressions vs Method References

Java Lambda Expressions vs Method References

By 
Edwin Dalorzo user avatar
Edwin Dalorzo
·
Apr. 09, 13 · Interview
Likes (5)
Comment
Save
Tweet
Share
78.9K Views

Join the DZone community and get the full member experience.

Join For Free

Now we can use lambda expressions to implement functional interfaces as we have seen in previous posts, but the lambda expressions are not the only mechanism we can use for this purpose. Particularly where there are preexisting code that we would like to reuse we can simplify the implementation of the functional interface by using method references.

Static Method References

First, consider the existence of a functional interface Predicate as follows:

public interface Predicate<T> {
public void test(T t);
}

And let’s say that we had a method to filter elements out of a list using this predicate, as follows:

static <T> List<T> filter(Predicate<T> predicate, List<T> source) {
List<T> destiny = new ArrayList<>();
for (T item : source) {
if(predicate.test(item)){
destiny.add(item);
}
}
return destiny;
}

Finally, let’s say we had a class containing a set of static method predicates which we had defined in the past, prior to the existence of the Java 8. Something as follows:

static class IntPredicates {
public static boolean isOdd(Integer n) { return n % 2 != 0; }
public static boolean isEven(Integer n) { return n % 2 == 0; }
public static boolean isPositive(Integer n) { return n >= 0; }
}

Now, one way to implement a predicate that could reuse our static methods would be through the use of lambda expressions, like this:

Predicate<Integer> isOdd = n -> IntPredicates.isOdd(n);
Predicate<Integer> isEven = n -> IntPredicates.isEven(n);

However, we can clearly see that the signature of the static predicate methods corresponds perfectly with the signature of the test method for integer predicates. So, an alternative way to implement the functional interface in this case is through a static method reference, as follows:

Predicate<Integer> isOdd = IntPredicates::isOdd;
Predicate<Integer> isEven = IntPredicate::isEven;

Notice the use of double colon :: here. We are not invoking the method, we are just referencing its name.

We could now use this technique to filter a list of numbers that satisfy any of these predicates, something like this:

List<Integer> numbers = asList(1,2,3,4,5,6,7,8,9);
List<Integer> odds = filter(IntPredicates::isOdd, numbers);
List<Integer> evens = filter(IntPredicates::isEven, numbers);

So, as we can see, we could implement the functional interfaces in this case using both: lambda expressions and method references, but the syntax with the static method references was more succinct.

Constructor Method References

Let’s consider now the existence of a functional interface named Function, as follows:

public interface Function<T,R> {
public R apply(T t);
}


Based on it, we could define a method map, that converts the elements from a source list from certain value T to certain value R, as follows:

static <T,R> List<R> map(Function<T,R> function, List<T> source) {
List<R> destiny = new ArrayList<>();
for (T item : source) {
R value = function.apply(item);
destiny.add(value);
}
return destiny;
}

Now imagine that we had a list of strings containing numbers that we would like to transform to integer values. We could do it using a lambda expression to provide an implementation for the Function interface, more or less like this:

List<String> digits = asList("1","2","3","4","5");
List<Integer> numbers = map(s -> new Integer(s), digits);

However, we can clearly infer that the constructor Integer(String) has the same signature as the  apply method in the Function reference required here, namely, it receives a string as argument and returns an integer.

So, in this case we simplify the implementation of the functional interface by means of using a constructor reference, as follows:

List<String> digits = asList("1","2","3","4","5");
List<Integer> numbers = map(Integer::new, digits);

This conveys the same meaning: take a string and make me an integer out of it. It is the perfect task for our Integer(String) constructor.

Instance Method Reference to Arbitrary Objects

Consider now the existence of a class named  Jedi, defined as follows:

public class Jedi  {
private String name;
private int power;
public Jedi(String name, int power){
this.name = name;
this.power = power;
}
public String getName() {
return name;
}
public int getPower() {
return power;
}
//equals,hashCode,toString
}

Now, consider that we had a list of jedis, and we would like to use our previous function map to extract the name from every jedi and generate a list of names out of the list of jedis. Somewhat like this, using lambda expressions:

List<Jedi> jedis = asList(new Jedi("Obi-wan", 80),
new Jedi("Anakin", 25),
new Jedi("Yoda", 500));
List<String> names = map(jedi -> jedi.getName() , jedis);

The interesting observation here is that the parameter jedi is the argument for the apply method in the Function reference. And we use that reference to a jedi to invoke on it the method getName. In other words, we invoke a method on the reference we receive as argument.

So, we could simplify this implementation by using an instance method reference as follows:

List<Jedi> jedi = asList(new Jedi("Obi-wan", 80),
new Jedi("Anakin", 25),
new Jedi("Yoda", 500));
List<String> names = map(Jedi::getName, jedi);


Again, the interesting aspect of this type of method reference is that the method getName is an instance method. Therefore, the target of its invocation must be an instance, which in this case is an arbitrary object being provided as the argument for the method apply in the Function interface definition.

Instance Method Reference to a Specific Object

Let’s consider the existence of functional interface named Consumer, as follows

public interface Consumer<T> {
public void accept(T t);
}

And let’s define a method capable of using a consumer to consume all the elements of a given list, like this:

static  void forEach(Consumer<T> consumer, List<T> source){
for (T item : source) {
consumer.accept(item);
}
}

Imagine that now we would like to print all the elements contained in a list, and for that purpose we could define a consumer using a lambda expressions:

List<Integer> numbers = asList(1,2,3,4,5,6,7,8,9);
forEach(n -> { System.out.println(n); }, numbers);

However, we could also make the observation that the method println has the same signature that our Consumer has, it receives an integer and does something with it, in this case  it prints it to the main output.

However, we cannot specify that this is an arbitrary instance method reference by saying PrintStream::println, because in this case the Consumer interface method accept does not receive as one of its arguments the PrintStream object on which we may want to invoke the method println. Conversely, we already know which is the target object on which we would like to invoke the method: we can see that every time we would like to invoke it on a specific reference, in this case the object System.out.

So, we could implement our functional interface using an instance method reference to a specific object as follows:

List<Integer> numbers = asList(1,2,3,4,5,6,7,8,9);
forEach(System.out::println, numbers);

In summary, there are circumstances in which we would like to use some preexisting code as the implementation for a functional interface, in those case we could use one of several variants of method references instead of a more verbose lambda expression.

 

Interface (computing) Java (programming language)

Published at DZone with permission of Edwin Dalorzo. See the original article here.

Opinions expressed by DZone contributors are their own.

Related

  • Advanced Brain-Computer Interfaces With Java
  • Simplify Java: Reducing Unnecessary Layers and Interfaces [Video]
  • Java 21 SequenceCollection: Unleash the Power of Ordered Collections
  • Projections/DTOs in Spring Data R2DBC

Partner Resources

×

Comments

The likes didn't load as expected. Please refresh the page and try again.

ABOUT US

  • About DZone
  • Support and feedback
  • Community research
  • Sitemap

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Core Program
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 3343 Perimeter Hill Drive
  • Suite 100
  • Nashville, TN 37211
  • [email protected]

Let's be friends: