Back to Method References
Q1Question 1 of 5

What are the four types of method references?

Key Concepts
  • Type 1 (static): ClassName::staticMethod, e.g., Integer::parseInt, equivalent to s -> Integer.parseInt(s).
  • Type 2 (bound instance): object::instanceMethod, e.g., System.out::println, bound to a specific object.
  • Type 3 (unbound instance): ClassName::instanceMethod, e.g., String::toUpperCase, the stream element is the receiver.
  • Type 4 (constructor): ClassName::new, e.g., BigDecimal::new, equivalent to s -> new BigDecimal(s).
  • Method references are syntactic sugar for lambdas that call a single existing method.
  • The confusing pair is Type 2 vs Type 3: Type 2 binds to a specific instance; Type 3 uses the stream element as the receiver.
  • Use method references when the lambda body is a single method call; they read like English: filter(Account::active).map(Account::balance).

Definition

Four types: Class::static, object::instance, Class::instance (stream element is receiver), and Class::new, all replacing lambdas that call one existing method.
DIAGRAM
- **Type 1 (static)**: `ClassName::staticMethod`. Lambda: `s -> Integer.parseInt(s)` becomes `Integer::parseInt`.
- **Type 2 (bound instance)**: `object::instanceMethod`. Lambda: `x -> System.out.println(x)` becomes `System.out::println`. Bound to a specific object.
- **Type 3 (unbound instance)**: `ClassName::instanceMethod`. Lambda: `s -> s.toUpperCase()` becomes `String::toUpperCase`. The stream element is the receiver.
- **Type 4 (constructor)**: `ClassName::new`. Lambda: `s -> new BigDecimal(s)` becomes `BigDecimal::new`.

Type 1 (static): 'Use the RBI standard interest calculator' (RBI::calculateInterest). Type 2 (bound): 'Ask Rajesh specifically to verify' (rajesh::verify). Type 3 (unbound): 'Each account verifies itself' (Account::verify). Type 4 (constructor): 'Create a new receipt for each transaction' (Receipt::new).

Understanding the Concepts

What are the four types of method references?

JAVA
package com.bank.service;

import java.math.BigDecimal;
import java.util.List;
import java.util.function.Supplier;
import java.util.ArrayList;

public class MethodRefDemo {
    record Account(String id, BigDecimal balance, boolean active) {}

    static boolean isValidId(String id) { return id.startsWith("SAV"); }

    public static void main(String[] args) {
        List<Account> accounts = List.of(
            new Account("SAV2001", new BigDecimal("125000"), true),
            new Account("CUR3001", new BigDecimal("250000"), true)
        );

        // Type 1: static method reference
        List<String> ids = accounts.stream()
            .map(Account::id)
            .filter(MethodRefDemo::isValidId) // static: isValidId(String)
            .toList();

        // Type 2: bound instance method reference
        accounts.forEach(System.out::println); // bound to System.out

        // Type 3: unbound instance method reference
        List<BigDecimal> balances = accounts.stream()
            .map(Account::balance)    // each Account calls its own balance()
            .toList();

        // Type 4: constructor reference
        List<BigDecimal> parsed = List.of("100", "200", "300").stream()
            .map(BigDecimal::new)     // new BigDecimal(String)
            .toList();

        Supplier<List<String>> listFactory = ArrayList::new; // new ArrayList<>()

        System.out.println("SAV IDs: " + ids);
        System.out.println("Balances: " + balances);
        System.out.println("Parsed: " + parsed);
    }
}
TEXT
Account[id=SAV2001, balance=125000, active=true]
Account[id=CUR3001, balance=250000, active=true]
SAV IDs: [SAV2001]
Balances: [125000, 250000]
Parsed: [100, 200, 300]

Comparison table

DIAGRAM
| Type | Syntax | Lambda Equivalent | Receiver |
|------|--------|-------------------|----------|
| 1. Static | Integer::parseInt | s -> Integer.parseInt(s) | None (class) |
| 2. Bound | System.out::println | x -> System.out.println(x) | Specific object |
| 3. Unbound | String::toUpperCase | s -> s.toUpperCase() | Stream element |
| 4. Constructor | BigDecimal::new | s -> new BigDecimal(s) | New instance |

What is the confusion between Type 2 and Type 3?

Type 2 binds to a specific instance at pipeline creation time. Type 3 uses the stream element as the instance.

JAVA
// Type 2: bound to a specific object
AuditLogger logger = new AuditLogger();
trades.forEach(logger::log); // calls logger.log(trade) on THIS specific logger

// Type 3: the stream element IS the receiver
trades.stream().map(Trade::getSymbol); // calls trade.getSymbol() on EACH trade

When should you use method references vs lambdas?

| Situation | Use | Why |
|-----------|-----|-----|
| Single method call | Method reference | Cleaner, reads like English |
| Multi-line logic | Lambda | References cannot have multiple statements |
| Additional arguments | Lambda | References cannot pass extra args |
| Clarity needed | Whichever is clearer | Readability over brevity |

JAVA
// Method reference: cleaner
trades.stream().map(Trade::getAmount).reduce(BigDecimal.ZERO, BigDecimal::add);

// Lambda needed: extra logic
trades.stream().map(t -> t.getAmount().multiply(taxRate)).toList();

How do constructor references work with collections?

JAVA
import java.util.function.Supplier;
import java.util.function.Function;

// No-arg constructor: Supplier
Supplier<ArrayList<String>> listFactory = ArrayList::new;
ArrayList<String> list = listFactory.get();

// Single-arg constructor: Function
Function<String, BigDecimal> parser = BigDecimal::new;
BigDecimal amount = parser.apply("50000");

// Collect to specific collection type
List<Trade> sorted = trades.stream()
    .sorted(Comparator.comparing(Trade::getAmount))
    .collect(Collectors.toCollection(ArrayList::new));

What are common mistakes?

JAVA
// WRONG: trying to use method reference with additional arguments
// trades.stream().filter(Trade::isAbove(limit)); // COMPILE ERROR

// CORRECT: use lambda for additional arguments
trades.stream().filter(t -> t.isAbove(limit));

// WRONG: ambiguous method reference (overloaded methods)
// Function<String, String> fn = String::valueOf; // which valueOf?

// CORRECT: use explicit lambda to resolve ambiguity
Function<Integer, String> fn = String::valueOf;

What about method references and generics?

JAVA
// Method references work seamlessly with generic types
List<String> names = accounts.stream()
    .map(Account::id)
    .filter(Objects::nonNull)
    .sorted(String::compareTo) // Type 3: element.compareTo(other)
    .toList();

What should a senior engineer know?

  • Use method references when the lambda body is a single method call. They make code read like English: filter(Account::active).map(Account::balance).reduce(ZERO, BigDecimal::add).
  • For complex lambdas (multi-line or with additional logic), use explicit lambdas or extract into named methods.
  • Method references are NOT always clearer. str -> str.trim() may be clearer than String::trim in some contexts.
  • The compiler resolves which overload to use based on the functional interface's type signature.

Visual Overview

DIAGRAM
  Four Types:
  ──────────────────────────────────────────────────
  Type 1: ClassName::staticMethod
          Integer::parseInt     s -> Integer.parseInt(s)

  Type 2: object::instanceMethod
          System.out::println   x -> System.out.println(x)

  Type 3: ClassName::instanceMethod
          String::toUpperCase   s -> s.toUpperCase()

  Type 4: ClassName::new
          BigDecimal::new       s -> new BigDecimal(s)

Key Takeaway

There are four types of method references. Type 1 is static (Integer::parseInt). Type 2 is bound to a specific object (System.out::println). Type 3 uses the stream element as the receiver (String::toUpperCase). Type 4 is a constructor (BigDecimal::new).

Method references are syntactic sugar for lambdas that call one existing method. Use them when the lambda body is a single method call.

Remember one sentence: four types (static, bound instance, unbound instance, constructor) all replace lambdas that simply call one existing method.

---

Share
Use ← → arrow keys to navigate