HuntExams Academy logo
JavaIntermediate#java8#lambda

What is a method reference and what are its types?

Answer

A method reference is shorthand for a lambda that calls an existing method, using :: syntax. Types include static (Class::staticMethod), instance on a particular object (obj::method), instance on an arbitrary object (Class::instanceMethod), and constructor references (Class::new).

Example

List<String> names = List.of("bob","amy");
names.forEach(System.out::println); // instance method reference on particular object
names.stream().map(String::toUpperCase); // instance method on arbitrary object

Detailed explanation

A method reference is a compact way to express a lambda whose job is to call an existing method or constructor. Introduced in Java 8, it uses the :: operator and improves readability when the intended operation is already clearly named. It is not a method call: System.out::println describes an operation to invoke later, whereas System.out.println("hello") invokes it immediately. Like a lambda, a method reference needs a target functional interface, such as Consumer<T>, Function<T, R>, or Supplier<T>, which has one abstract method.

There are four standard forms. A static method reference, such as Integer::parseInt, can implement Function<String, Integer> and corresponds to s -> Integer.parseInt(s), with boxing of the result. An instance method reference on a particular object, such as System.out::println, binds the receiver when the reference is evaluated; as a Consumer<String>, it corresponds to s -> System.out.println(s) for ordinary use. An instance method reference on an arbitrary object of a particular type, such as String::toUpperCase, uses the functional interface’s first argument as the receiver and corresponds to s -> s.toUpperCase(). A constructor reference, such as ArrayList<String>::new, creates a new instance when invoked and can implement Supplier<ArrayList<String>>.

The distinction between bound and unbound instance references is important. In names.forEach(System.out::println), System.out supplies the receiver, while each name supplies the method argument. In names.stream().map(String::toUpperCase), each stream element is itself the receiver, so the no-argument toUpperCase method still fits Function<String, String>. For an unbound reference such as String::compareTo, the first functional argument supplies the receiver and the second supplies the method argument. Also, map is lazy: without a terminal operation, the uppercase transformation does not execute.

During compilation, Java uses the target interface to resolve overloads and check parameter types, return compatibility, and checked exceptions. Runtime implementations typically use invokedynamic-based linkage rather than translating every reference into an anonymous class. Interviewers usually look for correct examples of all four forms, especially the receiver distinction. Method references suit direct delegation; use a lambda when you need argument rearrangement, additional calculations, or multiple statements.

Key points to remember

  • Method references use :: and require a compatible target functional interface.
  • Static reference: Integer::parseInt delegates to a static method.
  • Bound instance reference: System.out::println uses a particular receiver object.
  • Unbound instance reference: String::toUpperCase takes its receiver from the first functional argument.
  • Constructor reference: ArrayList<String>::new invokes a constructor when the functional operation runs.

Common mistakes candidates make

  • Confusing Class::instanceMethod with a static reference; the selected method and target interface determine the form.
  • Treating a method reference as an immediate invocation, or expecting a stream’s map operation to run without a terminal operation.
  • Assuming any lambda can become a method reference despite extra logic, reordered arguments, or incompatible types.

Likely follow-up questions

Can a method reference refer to an overloaded method?

Yes; the compiler uses the target functional interface’s signature to select a compatible overload. If the surrounding context leaves the choice ambiguous, provide an explicit target type or a suitable cast.

What happens if the receiver of an instance method reference is null?

For a bound reference such as obj::method, a null obj causes a NullPointerException when the reference is evaluated. For an unbound reference such as String::length, passing null as the receiver argument causes the exception when the functional operation is invoked.

Can constructor references create arrays or call constructors with parameters?

Yes; Function<String, StringBuilder> factory = StringBuilder::new selects the constructor accepting a String. Array references also work: IntFunction<String[]> factory = String[]::new creates an array whose length is the supplied integer.

Interview tip: Define method references, show all four forms, and explain bound versus unbound receivers using the two stream examples.

Related Interview Questions

1
JavaIntermediate#streams

What is the difference between Stream.reduce() with and without an identity value?

Open
2
JavaAdvanced#streams

What is a parallel stream and when should you avoid it?

Open
3
JavaAdvanced#memory#gc

What are the different generations in the JVM heap?

Open