Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is java 8 supplier replacing constructor call

Tags:

java

I've been looking at the Supplier and Consumer interfaces of Java 8 and of what I understand it can replace constructor call.
I've seen an example on dzone (link here) where a ShapeFactory is used. The code is pretty simple, a simple factory for a shape class.
But she uses it as so :

Supplier<ShapeFactory> supplier = ShapeFactory::new;
supplier.get().getShape("rectangle").draw(); 

But why is it better than doing a simple classical :

ShapeFactory factory = new ShapeFactory();
factory.getShape("rectangle").draw()

This being much simpler and efficient enough. Besides, if the constructor of the ShapeFactory class has argument, Supplier will not work and we will have to use Function or other methods.

So why use the Supplier like this in this precise case ?

like image 420
Irindul Avatar asked Aug 02 '17 14:08

Irindul


People also ask

Is constructor reference allowed in Java 8?

A method reference can also be applicable to constructors in Java 8. A constructor reference can be created using the class name and a new keyword. The constructor reference can be assigned to any functional interface reference that defines a method compatible with the constructor.

What is a supplier in Java 8?

The Supplier Interface is a part of the java. util. function package which has been introduced since Java 8, to implement functional programming in Java. It represents a function which does not take in any argument but produces a value of type T.

What is Java Util function supplier?

java.util.function.Supplier<T> Represents a supplier of results. There is no requirement that a new or distinct result be returned each time the supplier is invoked. This is a functional interface whose functional method is get() .

Which function method is described by supplier interface?

In conclusion a supplier interface is represents an operation that takes no argument and returns a result, whose functional method is get() .


1 Answers

TLDR

Using a Supplier brings better readability/maintainability than explicit constructor invocation such as the new MyObject() idiom handled by switch/if-else if statements.
It also brings a better type safety that instantiation by reflection, an alternative to the new MyObject() idiom, often used before Java 8 to address the maintainability concern but that introduces other issues.


Instantiating a Factory class is not necessarily the best example to illustrate benefits of Supplier.
So, suppose we want to instantiate Shape classes from a Factory class.

A factory of Shape should instantiate different kind of Shape (subclasses of it).

1) Without Supplier and with a new MyShape() idiom, you will finish with a method that contains some if-else if/switch statements that checks a kind of criteria/parameter and instanties the expected class according to this criteria/parameter.
For example :

public static Shape createShape(String criteria) {
    if (criteria.equals("circle")){
        return new Circle();
    }
    else if (criteria.equals("square")){
        return new Square();
    }
    ...
}  

It is bad because when you add a class to handle by the method, you have to change this method with a new if-else if/switch so that it takes it in consideration.
It results to a not maintainable code where you may fast create side effects.

2) To avoid this problem, we use often reflection with Class.newInstance(). It eliminate the if-else if/switch problem but it creates often others as reflection may not work (security issue, class not instantiable, etc...) and you will know it only at runtime.
It results still to a brittle code.

Here are the reasons to favor the Supplier :

By providing a Supplier, the check is performed at compile time : if the class is not instantiable, the compiler will emit an error.
Besides, the method that uses/accepts a Supplier<Shape> doesn't need to use if-else if/switch statements.

When you use Supplier, you encounter generally two cases (not mutually exclusive of course) :

  • the Supplier<Shape> objects are instantiated by the factory class.
    We can so for example use in the factory a Map that stores the Supplier<Shape> instances and modifying the code to add/remove elements in the map is really cleaner as adding a new branch to a if-else if/switch statement as it is much less verbose and the way to change the map populating (add a map.put() or remove map.put() statement) is less prone to create side effects.

  • the Supplier<Shape> objects are instantiated and provided by the client class. In this case, the factory doesn't even need to change.
    So the map is so even not required.
    And from the client side, while the client provides a valid Supplier<Shape> parameter, it is fine.

Type safety, maintainable code : as you may notice, these two ways using the Supplier<Shape> address completely drawbacks of new MyShape() and instantiation by reflection idiom.


I will give two examples to illustrate these two ways.


Example where the Shape Suppliers are created in the factory :

public class SimpleShapeFactory {


    private static Map<String, Supplier<Shape>> shapesByCriteria = new HashMap<>();

    static {
        shapesByCriteria.put("square", Square::new);
        shapesByCriteria.put("circle", Circle::new);
    }

    public static Shape createShape(String criteria) {
        return shapesByCriteria.get(criteria).get();
    }           

}

The client may invoke it in this way :

Shape square = SimpleShapeFactory.createShape("square");
Shape circle = SimpleShapeFactory.createShape("circle");

This code will not fail at runtime because of the instantiation of Square or Circle as it is checked at compile time.
And the task for instantiating Shape instances are at a same place and easy to change :

static {
    shapesByCriteria.put("square", Square::new);
    shapesByCriteria.put("circle", Circle::new);
}

Example where the Shape Suppliers are provided by the client:

public class ComplexShapeFactory {

    public static Shape composeComplexShape(List<Supplier<Shape>> suppliers) {
        Shape shape = suppliers.get(0);
        for (int i = 1; i < suppliers.size() - 1; i++) {
            shape = shape.compose(suppliers.get(i + 1).get());
        }

        return shape;
    }    

}

The client may create complex shapes by chaining Supplier in this way as it invokes the method:

Shape squareWithTwoCircles = ComplexShapeFactory.composeComplexShape(Arrays.asList(Square::new, Circle::new, Circle::new));

The check is still done at compile time and as the supplier is provided by the client, the client can add new class of Shape without making the factory change.

like image 89
davidxxx Avatar answered Oct 23 '22 03:10

davidxxx