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
Please enter at least three characters to search
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

Because the DevOps movement has redefined engineering responsibilities, SREs now have to become stewards of observability strategy.

Apache Cassandra combines the benefits of major NoSQL databases to support data management needs not covered by traditional RDBMS vendors.

The software you build is only as secure as the code that powers it. Learn how malicious code creeps into your software supply chain.

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

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

  • Modern Test Automation With AI (LLM) and Playwright MCP
  • Introducing Graph Concepts in Java With Eclipse JNoSQL
  • SaaS in an Enterprise - An Implementation Roadmap
  • Go 1.24+ Native FIPS Support for Easier Compliance
  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.8K 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
Oops! Something Went Wrong

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
  • support@dzone.com

Let's be friends:

Likes
There are no likes...yet! 👀
Be the first to like this post!
It looks like you're not logged in.
Sign in to see who liked this post!