انتقل إلى المحتوى
أمين أبوزيد
منشورBEG-035-L04

مراجع الدوال (Method references)

  • الدرس 4 من 5
  • 10 دقيقة قراءة
  • مبتدئ
في هذا الدرس 11 موضوعات

هذا الدرس متاح حاليًا باللغة الإنجليزية فقط، ولم تُنشر نسخته العربية بعد.

Lesson Overview

Look back at the lambdas from the last lesson: s -> s.length(), s -> System.out.println(s), () -> new ArrayList<String>(). Each one does nothing except call one existing method. The lambda is only a courier: it takes the parameter and hands it straight to a method that already has a name. Java lets you drop the courier and point at the method directly. This is a method reference.

In the recipe-card picture: instead of writing a new card that says "do what page 12 of the cookbook says", you simply write "see page 12". The card stays valid, and there is less to read.

Why this matters: method references are everywhere in stream and collection code (map(String::trim), forEach(System.out::println)), so you must read all four forms fluently. They also teach a useful habit: when a lambda only forwards to a method, the method's name often describes the intent better than the lambda's body.

This lesson builds on the standard functional interfaces. A method reference, just like a lambda, needs a functional interface as its target type.

Learning Objectives

By the end of this lesson you will be able to:

  • Rewrite a lambda that only calls one method as a method reference.
  • Recognize and write the four forms: static method, method of a particular object, method of an arbitrary object of a type, and constructor.
  • Say which parameter becomes the receiver in Class::instanceMethod.
  • Explain why ArrayList::new can mean different constructors in different contexts.
  • Decide when to keep the lambda instead.
  • Read the compiler error for a reference that does not match.

Written Explanation

The idea

A method reference is written target::methodName, with two colons and no parentheses. It is shorthand for a lambda that forwards its parameters to that method. The Oracle Java Tutorials describe method references as compact, easy-to-read lambda expressions for methods that already have a name.

java
names.forEach(s -> System.out.println(s));   // lambda
names.forEach(System.out::println);          // method reference

Both create the same behavior. The target type decides how the parameters are passed on: forEach wants a Consumer<String>, so the method reference becomes "accept a string, call println with it".

The four forms

FormSyntaxEquivalent lambda
Static methodClass::staticMethodx -> Class.staticMethod(x)
Method of a particular objectobject::methodx -> object.method(x)
Method of an arbitrary object of a typeClass::instanceMethod(a, rest) -> a.instanceMethod(rest)
ConstructorClass::newx -> new Class(x)

The Oracle tutorial lists the same four kinds. Let us look at each with one example.

1. Static method. Integer::parseInt is x -> Integer.parseInt(x). The parameters of the functional method are passed as arguments to the static method.

java
Function<String, Integer> parse = Integer::parseInt;

2. Method of a particular object. The object is named on the left, and it already exists, so every call uses that same object. System.out::println is the best-known case: System.out is one particular PrintStream.

java
Greeter g = new Greeter("Welcome");
Function<String, String> greet = g::greet;   // x -> g.greet(x)

3. Method of an arbitrary object of a type. The left side is a class name, but the method is an instance method. Then the first parameter becomes the object on which the method is called, and the remaining parameters become the arguments:

java
Function<String, Integer> len = String::length;                 // s -> s.length()
Comparator<String> ci = String::compareToIgnoreCase;           // (a, b) -> a.compareToIgnoreCase(b)

This is the one that confuses beginners, because String::length looks like a static call. It is not: there is no static String.length(...). The compiler reads it as "take a String and call its length". In compareToIgnoreCase the first string is the receiver and the second is the argument.

4. Constructor. Class::new creates a new object. Which constructor? The one whose parameters match the functional method:

java
Supplier<List<String>> make = ArrayList::new;       // () -> new ArrayList<>()
Function<String, Person> create = Person::new;      // name -> new Person(name)

ArrayList::new is a Supplier when there are no parameters, and could be a Function<Integer, ArrayList<...>> when there is one, because ArrayList has a constructor taking an initial capacity. The target type picks. (The tutorial also shows array constructor references, such as int[]::new; we will not need them.)

Static versus instance: the two-sentence test

When you see Name::method, ask: is method static?

  • If yes, it is form 1: the parameters are the arguments.
  • If it is an instance method and Name is a class, it is form 3: the first parameter is the receiver.

If Name is a variable or expression (System.out, g, this), it is form 2.

When to keep the lambda

Use a method reference when:

  • The lambda does nothing but call one method with its parameters in the same order.
  • The name reads better than the lambda body.

Keep the lambda when:

  • It does more than forward: s -> s.length() > 4 has a comparison, so there is no method to point to. (If you want a reference, give the logic a name: static boolean isLong(String s) and then Main::isLong.)
  • The parameters are reordered, or used twice, or combined with other values.
  • The reference would be ambiguous or surprising to a reader. Clarity wins; the tutorial wording is "compact, easy-to-read", and a reference that makes people stop to puzzle it out has failed.

When the compiler complains

If the method does not exist or does not fit the functional interface, you get an error such as this, for a mistaken String::size:

text
Bad.java:4: error: invalid method reference
        Function<String, Integer> f = String::size;
                                      ^
  cannot find symbol
    symbol:   method size(String)
    location: class String

Check, in order: the name, whether it is static, and whether the number of parameters matches the interface method.

Examples

Example 1: printing and sorting (simple)

java
List<String> names = new ArrayList<>(List.of("omar", "Sara", "layla", "Adam"));
names.forEach(System.out::println);
names.sort(String::compareToIgnoreCase);
System.out.println(names);

Output:

text
omar
Sara
layla
Adam
[Adam, layla, omar, Sara]

System.out::println is a particular-object reference; String::compareToIgnoreCase is an arbitrary-object reference with two parameters. Note the plain String.compareTo would put the capital letters first; ignoring case gives the order a person expects.

Example 2: converting and creating (practical)

java
Function<String, Integer> len = String::length;
Function<String, Integer> parse = Integer::parseInt;
System.out.println(len.apply("abcd") + " " + (parse.apply("42") + 1));
Supplier<List<String>> make = ArrayList::new;
List<String> fresh = make.get();
fresh.add("x");
System.out.println(fresh);
Function<String, Person> create = Person::new;
System.out.println(create.apply("Noor"));

Output:

text
4 43
[x]
Person(Noor)

This example uses the static form, the arbitrary-object form, and the constructor form, each matching a different functional interface.

Example 3: your own methods (realistic)

Any method can be referenced, including your own:

java
class Greeter {
    private final String greeting;
    Greeter(String g) { greeting = g; }
    String greet(String n) { return greeting + ", " + n; }
}
static boolean isLong(String s) { return s.length() > 4; }
static int compareByName(Person a, Person b) { ... }   // inside Person
java
Greeter g = new Greeter("Welcome");
Function<String, String> greet = g::greet;
System.out.println(greet.apply("Omar"));
Predicate<String> p = Main::isLong;
System.out.println(p.test("banana") + " " + p.test("fig"));
people.sort(Person::compareByName);
people.sort(Comparator.comparing(Person::getName).reversed());

Output (the two sorts are shown with two people, Zed and Amal):

text
Welcome, Omar
true false
[Person(Amal), Person(Zed)]
[Person(Zed), Person(Amal)]

The last line is the pattern you saw in BEG-032: Person::getName is a "key extractor", an arbitrary-object reference that means "take a person, get the name". Now the notation is no longer a preview. Moving isLong out of the lambda gave the logic a name that tells the reader what is being tested.

Backend Insight

In backend code, method references describe intent at the call site. Compare:

java
orders.stream().map(order -> order.getCustomer()).filter(c -> c.isActive())...
orders.stream().map(Order::getCustomer).filter(Customer::isActive)...

(The stream methods are the subject of BEG-036; read them as "for each order, get its customer; keep the active ones".) The second form reads like a sentence, and it breaks no rule: each reference is just a named method.

Method references also encourage good design. If you cannot write Customer::isActive because the logic lives in an expression scattered across a lambda, that is a hint that the class lacks a method with a good name. Pulling the logic into a method gives you something you can reference, test, and reuse. Remember also that a constructor reference such as Person::new is how frameworks and factories are often configured: "here is how to create one".

Common Mistakes

Mistake 1: Adding parentheses

  • Incorrect: names.forEach(System.out::println());
  • Correct: names.forEach(System.out::println);

With parentheses you would call the method. A reference names it without calling.

Mistake 2: Thinking String::length is static

It is an instance method, referenced through the class name. The first parameter of the interface method becomes the receiver. If you call String.length() anywhere, it will not compile.

Mistake 3: Forcing a reference where the lambda does more

s -> s.length() > 4 cannot be written as a reference. Do not contort the code; keep the lambda, or extract a method with a clear name.

Mistake 4: Confusing this::method with Class::method

this::process calls process on the current object (a particular object). Order::process calls it on whatever order the interface passes in. Choose on purpose.

Mistake 5: Ignoring ambiguity and overloads

If a class has several overloads of a method (for instance, println has many), the functional interface's parameter types pick one. If the compiler says the reference is ambiguous or invalid, write the lambda and be explicit.

Exercises

Try each exercise yourself before opening the solutions.

Level A: Foundation

A1. Name the form (static, particular object, arbitrary object, constructor) of: (a) Integer::valueOf (b) String::trim (c) HashSet::new (d) System.out::print.

أظهر الحل A1

A1. (a) static method. (b) arbitrary object of a type (s -> s.trim()). (c) constructor. (d) method of a particular object (System.out).

A2. Rewrite as method references: s -> s.isEmpty(), x -> Integer.valueOf(x), () -> new HashSet<Integer>().

أظهر الحل A2

A2. String::isEmpty, Integer::valueOf, HashSet::new (target type Supplier<Set<Integer>>).

A3. In String::compareToIgnoreCase used as a Comparator<String>, which argument is the receiver of the call?

أظهر الحل A3

A3. The first one. String::compareToIgnoreCase means (a, b) -> a.compareToIgnoreCase(b).

Level B: Application

B1. Sort List<String> w = new ArrayList<>(List.of("b", "A", "c")) ignoring case using a method reference. What is the result?

أظهر الحل B1

B1. w.sort(String::compareToIgnoreCase); gives [A, b, c].

B2. Write a BiFunction<Integer, Integer, Integer> that returns the larger of two numbers, using a method reference to a method of the Math class.

أظهر الحل B2

B2. BiFunction<Integer, Integer, Integer> m = Math::max; For m.apply(2, 9) the result is 9. The int method is chosen and the Integer values are unboxed and boxed automatically.

B3. The method static boolean isLong(String s) { return s.length() > 4; } exists. Use it as a Predicate<String> with a method reference, and call it with "abcde".

أظهر الحل B3

B3. Predicate<String> p = Main::isLong; (replace Main with the class that holds the method). p.test("abcde") is true.

Level C: Challenge

C1. Given Greeter g = new Greeter("Hi") and String greet(String n) in Greeter, write both g::greet and Greeter::greet as references. What functional interface does each need?

أظهر الحل C1

C1. Function<String, String> bound = g::greet; is a function of one string: the receiver g is fixed. BiFunction<Greeter, String, String> unbound = Greeter::greet; takes the greeter as its first parameter, then the string. For example unbound.apply(new Greeter("Yo"), "B") gives Yo, B.

C2. Explain why s -> s.length() > 4 cannot be turned into a method reference directly, and give two ways to handle it.

أظهر الحل C2

C2. The body is not a plain call that forwards its parameter, because it also compares the result with 4. Option 1: keep the lambda. Option 2: extract a method such as isLong(String s) and reference that. The second is better when the rule is used in several places or deserves a name.

Mini Challenge

Create a small Product class with a constructor taking a name, getName(), and a static compareByName(Product a, Product b). Then, in main:

  1. Build a Supplier<List<Product>> from ArrayList::new and add three products.
  2. Create a product from a string with a Function<String, Product> that uses Product::new.
  3. Sort the products using Product::compareByName, then again with Comparator.comparing(Product::getName).
  4. Print each product name with a method reference to System.out::println (after mapping names to strings in a loop or with getName).

Check your work: every method reference corresponds to exactly one lambda you could write by hand; try writing it, and make sure the two forms behave the same.

Summary & Next Lesson

Key takeaways

  • A method reference replaces a lambda that only forwards to an existing method. Write target::method, with no parentheses.
  • Four forms: Class::staticMethod, object::method, Class::instanceMethod (first parameter becomes the receiver), and Class::new.
  • The functional interface decides how parameters are passed on, and which constructor or overload is chosen.
  • Keep the lambda when the body does more than forward, or when the reference would be less clear.
  • Give repeated logic a named method, and then reference it.

Next lesson

The next lesson is a demo, "Replacing anonymous classes with lambdas". You will take noisy code, shrink it step by step with lambdas and method references, and meet the "effectively final" error on purpose, which prepares the lab "Filters".

Self-check

  1. How is String::length different from String.length()?
  2. What does System.out::println refer to?
  3. In Class::instanceMethod, what is the role of the first parameter?
  4. Which form is ArrayList::new?
  5. When should you keep a lambda?
أظهر الإجابات
  1. String::length is a reference to an instance method meaning "take a string and call its length"; String.length() is not valid Java at all, since length is not static.
  2. The println method of the one object System.out.
  3. It is the object on which the method is called; the other parameters are the arguments.
  4. A constructor reference.
  5. When it does more than forward its parameters, or when the lambda is clearer than the reference.

References

  • Method References, The Java Tutorials (Oracle). Supports: the four kinds of method reference, the rule that a method reference is for a lambda that calls an existing method, and the String::compareToIgnoreCase and HashSet::new examples.
  • Lambda Expressions, The Java Tutorials (Oracle). Supports: lambdas as the starting point and the target-typing idea that picks the interface.
  • All code was compiled and run with JDK 21; outputs and the compiler message shown are real. The statement that the first parameter becomes the receiver in the arbitrary-object form is from the Oracle method references page; the rule of thumb on when to keep a lambda is the course's own advice.

التطبيق

طبّق هذا الدرس بمختبر الوحدة وتمارينها واختبارها.

المختبر والتمارين الاختبار

تعليم الدرس كمكتمل اختياري ويمكنك التراجع عنه في أي وقت. يبقى تقدمك محفوظًا في هذا المتصفح.

اكتب للبحث في المستويات والمراحل والوحدات والدروس.