Java
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Object-Oriented Java
-
Section 4: Collections Framework
-
Section 5: Exception Handling
-
Section 6: Generics
-
Section 7: Functional Java
-
Section 8: Concurrency
-
Section 9: I/O and NIO
-
Section 10: JVM Internals
-
Section 11: Modern Java Features
-
Section 12: Build Tools and Project Structure
-
Section 13: Testing
-
Section 14: Databases and Persistence
-
Section 15: Networking
-
Section 16: Design and Best Practices
-
Section 17: Reflection and Annotations
-
Section 18: Logging and Diagnostics
-
Section 19: Date, Time, and Internationalization
-
Section 20: Java Platform Module System
-
Section 21: Security in Java
-
Section 22: Advanced Collections and Data Structures
-
Section 23: More Concurrency Patterns
-
Section 24: Compression, Files, and System Integration
-
Section 25: GUI Programming
-
Section 26: Practical Projects
-
Section 27: Data Structures and Algorithms
-
Section 28: Interview and Algorithm Practice
-
Section 29: JSON and Data Interchange
-
Section 30: More Concurrency Utilities
-
Section 31: More Collections and Streams Practice
-
Section 32: More File and System Programming
-
Section 33: Standard Library Deep Dive
-
Section 34: More Practice and Drills
-
Section 35: More Testing and Quality
-
Section 36: More Design Patterns and Architecture
-
Section 37: Career and Ecosystem
-
Section 38: More OOP and Architecture Practice
-
Section 39: More Enterprise Concepts
-
Section 40: Advanced JavaFX
-
Section 41: More Interview Practice
75: Method References
I was looking through some old code today—a module that handles a list of user-submitted tags for a blog engine—and I noticed a lot of "boilerplate lambdas." You know the kind. They aren't complex, but they're noisy. They basically just take an input and pass it directly into another method without changing a thing.
Let's look at a snippet I'm refactoring. I have a list of tags, and I want to print each one to the console in uppercase.
List<String> tags = List.of("java", "streams", "functional", "backend");
tags.stream()
.map(s -> s.toUpperCase())
.forEach(s -> System.out.println(s));
Cutting out the middleman
If you look at s -> System.out.println(s), it feels redundant. I'm declaring a variable s just to hand it immediately to println. I'm basically acting as a courier. In Java, when a lambda does nothing but call an existing method with the exact same arguments, we can use a Method Reference.
I'll try replacing the forEach lambda with the :: operator. Instead of defining the logic, I'm just pointing Java toward the method I want to use.
tags.stream()
.map(s -> s.toUpperCase())
.forEach(System.out::println);
It's cleaner. I'm telling Java: "Take whatever comes out of the stream and plug it directly into the println method of the System.out object."
Wait, what about the map call?
Now look at the .map(s -> s.toUpperCase()) part. That's also just a middleman. It takes a string s and calls a method on that same string. This is slightly different because toUpperCase() isn't a static method; it's an instance method called on the object being passed through the stream.
I wonder if I can use the :: syntax here too. I'll try referencing the String class itself.
tags.stream()
.map(String::toUpperCase)
.forEach(System.out::println);
This works perfectly. Even though toUpperCase isn't static, Java is smart enough to understand that the first argument of the functional interface (the Function in map) should be treated as the target of the method call. It's essentially saying: "For every string in this stream, call its own toUpperCase method."
Handling static logic
What if I have a custom utility method? Let's say I have a TagValidator class with a static method that checks if a tag is valid.
class TagValidator {
public static boolean isValid(String tag) {
return tag != null && tag.length() > 2;
}
}
If I want to filter my list using this, I'd normally write .filter(tag -> TagValidator.isValid(tag)). But again, I'm just passing the argument through. I can shorten this by referencing the static method directly:
tags.stream()
.filter(TagValidator::isValid)
.map(String::toUpperCase)
.forEach(System.out::println);
I personally prefer this style because it reads like a sentence: "Filter by tag validator's is-valid, map to string's to-upper-case, and for each, print."
The constructor shortcut
One last thing I stumbled upon while cleaning up this code. Sometimes we use lambdas to instantiate new objects, like s -> new Tag(s). If the lambda just calls a constructor, we can use the ClassName::new syntax.
Let's say I want to turn these strings into Tag objects:
List<Tag> tagObjects = tags.stream()
.map(Tag::new)
.toList();
It's a subtle change, but across a project with thousands of lines, removing those (s) -> ... wrappers makes the actual business logic pop. You stop seeing the "plumbing" and start seeing the intent.
📋 Practical Task
Refactoring the Inventory Processor
You are reviewing a piece of legacy code that processes a list of product names. The current implementation uses verbose lambdas that make the stream pipeline harder to read. Your task is to refactor the processInventory method to use Method References wherever possible.
import java.util.*;
import java.util.stream.*;
class Product {
String name;
Product(String name) { this.name = name; }
public String getName() { return name; }
@Override
public String toString() { return "Product: " + name; }
}
class InventoryUtils {
public static boolean isPremium(String name) {
return name.startsWith("Gold") || name.startsWith("Platinum");
}
}
public class InventoryManager {
public static void processInventory(List<String> rawNames) {
rawNames.stream()
.filter(name -> InventoryUtils.isPremium(name))
.map(name -> new Product(name))
.map(product -> product.toString())
.forEach(info -> System.out.println(info));
}
public static void main(String[] args) {
List<String> items = List.of("Gold Watch", "Silver Ring", "Platinum Chain", "Bronze Coin");
processInventory(items);
}
}
Requirements:
- Replace the
filterlambda with a reference to the static method inInventoryUtils. - Replace the
maplambda that creates aProductwith a constructor reference. - Replace the
maplambda that callstoString()with an instance method reference. - Replace the
forEachlambda with a reference toSystem.out.println.
There are no comments for now.