Constructor References
A constructor reference is a special form of method reference used when you want a functional interface to create objects by calling a class constructor. Instead of writing a lambda that explicitly uses new, Java allows you to use the ClassName::new syntax.
This is especially useful when object creation itself is the behavior you want to pass around. It keeps code concise while still making the intention clear: "when requested, create an object of this type."
Constructor Reference Syntax
ClassName::new
A constructor reference does not create an object immediately. It provides a reference to a constructor that can be invoked later through the method defined by the target functional interface.
Remember: ClassName::new is a constructor reference. The constructor must be compatible with the abstract method of the functional interface receiving the reference.
Basic Constructor Reference
import java.util.function.Supplier;
class Student {
Student() {
System.out.println("Student created");
}
}
public class Main {
public static void main(String[] args) {
Supplier<Student> supplier =
Student::new;
Student student = supplier.get();
}
}
The Student::new reference points to the no-argument constructor. The object is created when supplier.get() is called.
Constructor Reference vs Lambda
The easiest way to understand constructor references is to compare them with the equivalent lambda expression.
Supplier<Student> first =
() -> new Student();
Supplier<Student> second =
Student::new;
Both versions create a Student using the no-argument constructor. The constructor reference simply removes the unnecessary lambda syntax.
Constructor Reference with Parameters
Constructor references can also point to constructors that require arguments. The target functional interface must have a compatible parameter list.
import java.util.function.Function;
class Student {
String name;
Student(String name) {
this.name = name;
}
}
public class Main {
public static void main(String[] args) {
Function<String, Student> creator =
Student::new;
Student student =
creator.apply("Ravi");
System.out.println(student.name);
}
}
The Function accepts a String and returns a Student. Because the Student constructor accepts one String, the constructor reference matches the required functional interface.
Constructor Reference with Multiple Parameters
When a constructor requires multiple parameters, a functional interface with a matching parameter list is needed.
import java.util.function.BiFunction;
class Product {
String name;
double price;
Product(String name, double price) {
this.name = name;
this.price = price;
}
}
public class Main {
public static void main(String[] args) {
BiFunction<String, Double, Product> creator =
Product::new;
Product product =
creator.apply("Laptop", 55000.0);
System.out.println(product.name);
System.out.println(product.price);
}
}
The constructor accepts two arguments, so BiFunction is suitable here. Its two input types correspond to the constructor parameters, while its result type is Product.
Constructor Parameters Must Match
A constructor reference is only valid when the constructor signature is compatible with the abstract method of the target functional interface.
| Constructor | Suitable Functional Interface | Reference |
|---|---|---|
| Student() | Supplier<Student> | Student::new |
| Student(String) | Function<String, Student> | Student::new |
| Product(String, Double) | BiFunction<String, Double, Product> | Product::new |
The constructor itself is not chosen by the reference syntax alone. Java uses the target functional interface and the available constructor signatures to determine which constructor is compatible.
Constructor Reference with a Custom Functional Interface
You are not limited to the standard functional interfaces. A constructor reference can target your own functional interface as long as its method signature matches the constructor.
@FunctionalInterface
interface StudentCreator {
Student create(String name);
}
class Student {
String name;
Student(String name) {
this.name = name;
}
}
public class Main {
public static void main(String[] args) {
StudentCreator creator =
Student::new;
Student student =
creator.create("Anita");
System.out.println(student.name);
}
}
The custom interface describes the required behavior, while Student::new supplies the constructor implementation.
Constructor Reference with Collections
Constructor references become particularly useful when a stream needs to transform data into new objects.
import java.util.*;
import java.util.stream.Collectors;
import java.util.function.Function;
class Student {
String name;
Student(String name) {
this.name = name;
}
public String toString() {
return name;
}
}
public class Main {
public static void main(String[] args) {
List<String> names =
Arrays.asList("Ravi", "Anita", "Priya");
Function<String, Student> creator =
Student::new;
List<Student> students =
names.stream()
.map(creator)
.collect(Collectors.toList());
System.out.println(students);
}
}
Each String is passed to the Student constructor, producing a new Student object. The Function describes the transformation from String to Student.
Constructor Reference Directly with map()
The same operation can often be written even more compactly.
List<Student> students =
names.stream()
.map(Student::new)
.collect(Collectors.toList());
The stream's map() operation expects a Function, and the constructor reference provides exactly the required behavior.
Constructor Reference with Arrays
Constructor references can also be useful with APIs that accept object-creation functions.
import java.util.function.IntFunction;
public class Main {
public static void main(String[] args) {
IntFunction<String[]> creator =
String[]::new;
String[] names = creator.apply(3);
System.out.println(names.length);
}
}
Here, String[]::new is a constructor reference for creating an array. The supplied integer determines the array size.
Constructor Reference Does Not Mean Immediate Creation
A common misunderstanding is assuming that ClassName::new creates an object immediately. It does not.
Supplier<Student> creator =
Student::new;
System.out.println("Supplier created");
Student student = creator.get();
The Student object is created when get() executes, not when the Supplier variable is assigned.
Think of a constructor reference as an object-creation recipe. The recipe is stored first; the object is created when the functional interface method is invoked.
Constructor Reference vs Factory Method
A constructor reference points directly to a constructor, while a factory method can contain additional creation logic.
Supplier<Student> direct =
Student::new;
A factory method can provide more control:
static Student createStudent() {
Student student = new Student();
return student;
}
Use a constructor reference when direct construction is sufficient. Use a factory method when object creation requires validation, configuration, caching, logging, or other business rules.
Common Beginner Mistakes
- Writing ClassName::new() instead of the correct ClassName::new.
- Forgetting that a constructor reference needs a compatible target functional interface.
- Assuming the constructor executes when the reference is created.
- Using a functional interface whose parameter count does not match the constructor.
- Using a constructor reference when additional object-creation logic is required.
Best Practices
- Use constructor references when direct object creation is the intended behavior.
- Choose a functional interface whose method signature naturally matches the constructor.
- Prefer constructor references when they make stream transformations easier to read.
- Use factory methods when construction requires meaningful additional logic.
- Remember that object creation happens when the functional interface method is invoked.
Interview Insight
Interview shortcut: A constructor reference uses ClassName::new to refer to a constructor. It can replace a compatible lambda such as () -> new Student() or name -> new Student(name). The constructor signature must match the target functional interface.
Quick Revision
| Concept | Key Point |
|---|---|
| Syntax | ClassName::new |
| No-argument constructor | Often used with Supplier |
| Parameterized constructor | Requires a compatible functional interface |
| Object creation | Occurs when the functional interface method is invoked |
| Stream usage | Can be used with map() to create objects |
| Alternative | A lambda such as () -> new Student() |
Final Takeaway
Constructor references make object creation fit naturally into Java's functional programming style. With the simple ClassName::new syntax, a constructor can be passed wherever a compatible functional interface is expected. They are particularly valuable with Suppliers, Functions, streams, and custom functional interfaces, helping you express object creation clearly without unnecessary lambda syntax.
