Definition
- A bound instance-method reference
instance::methodcaptures a specific receiver object. - System.out::println captures the
System.outPrintStream as the receiver. - It serves as
Consumer: forEach(System.out::println) prints every element. - Bound = receiver is fixed. Unbound (String::length) = receiver is supplied per call.
- Fine for debugging; use a proper Logger in production.
Priya at the National Bank of India has one specific printer at her desk. Rather than saying 'take an item and send it to this printer', she just points at the printer's print action. Every item goes to that same printer.
Understanding the Concepts
How does a bound instance-method reference work?
The receiver is captured when the reference is created. Only the method's arguments come from the stream.
package com.bank.demo;
import java.util.List;
public class BoundRefDemo {
public static void main(String[] args) {
List<String> names = List.of("Rajesh", "Priya", "Amit");
// Lambda
names.forEach(name -> System.out.println(name));
// Bound method reference (equivalent)
names.forEach(System.out::println);
}
}
Rajesh
Priya
Amit
How is bound different from unbound?
| Type | Syntax | Receiver | Stream element provides |
|---|---|---|---|
| Bound | System.out::println | Fixed (System.out) | Method argument |
| Unbound | String::length | Supplied per call | Is the receiver |
// Bound: System.out is the receiver, element is the argument
names.forEach(System.out::println);
// Each call: System.out.println("Rajesh"), System.out.println("Priya"), ...
// Unbound: element IS the receiver
names.stream().map(String::toUpperCase);
// Each call: "Rajesh".toUpperCase(), "Priya".toUpperCase(), ...
Why is System.out::println wrong for production?
System.out writes directly to the console with no log levels, no formatting, and no way to disable it.
// WRONG: production code
accounts.forEach(System.out::println);
// CORRECT: use a Logger
import java.util.logging.Logger;
Logger log = Logger.getLogger("com.bank.hr");
accounts.forEach(a -> log.info(a.toString()));
// Or with a bound reference on the logger:
accounts.stream()
.map(Object::toString)
.forEach(log::info);
Can you use bound references with other objects?
Yes. Any object's method can be captured.
StringBuilder sb = new StringBuilder();
names.forEach(sb::append); // appends each name to the same builder
System.out.println(sb); // RajeshPriyaAmit
What are common mistakes?
Mistake: assuming System.out::println works with formatted output.
// System.out::println prints each element on its own line
// For formatted output, use a lambda:
names.forEach(n -> System.out.printf("Name: %s%n", n));
What should a senior engineer know?
- The bound reference captures the receiver once at creation time and reuses it for every call.
- If the receiver changes between creation and use, you get the object as it was at capture time.
- System.out::println is the most common debugging pattern but should never appear in committed code.
- Bound references apply broadly: logger::info, list::add, builder::append.
What are common interview follow-up questions?
- "What is the difference between System.out::println and x -> System.out.println(x)?" Functionally identical; the reference is more concise. - "Can you capture a method reference to a local variable?" Yes, as long as the variable is effectively final. - "What happens if System.out is reassigned?" The reference captured the original object; the reassignment has no effect on the reference.
Visual Overview
Bound reference: System.out::println
Capture:
receiver = System.out (fixed at creation)
forEach call:
element "Rajesh" → System.out.println("Rajesh")
element "Priya" → System.out.println("Priya")
element "Amit" → System.out.println("Amit")
Compare with unbound (String::length):
element "Rajesh" → "Rajesh".length() (element IS the receiver)
Key Takeaway
A bound instance-method reference fixes the receiver at the moment the reference is created. System.out::println captures the System.out PrintStream, so forEach passes each element to that specific object's println method, printing every element on its own line.
The distinction is bound vs unbound: in the bound form the receiver is already chosen and only the method argument comes from the stream. In the unbound form (like String::length), the stream element itself becomes the receiver.
System.out::println is perfect for quick debugging but wrong for production. At the National Bank of India, production code uses a Logger bound reference like log::info instead. The concept is the same: fix the receiver, feed it elements.
Remember one sentence: System.out::println is a bound reference with a fixed receiver, fine for debugging but replaced by a Logger reference in production.
---