I was reading how to instantiate a generic and after reading and applying this answer; I would like to know what would be the differences between expecting a Supplier<T>
vs. expecting a new instance of T
.
Example:
abstract class AbstractService<T extends AbstractEntity> {
protected Supplier<T> makeNewThing(); // supplier is expected
public T myMethod(){
T object = makeNewThing().get(); // local object by calling supplier
object.doStuff();
return object;
}
}
class CarService extends AbstractService<Car> {
public Supplier<Car> makeNewThing(){
return Car::new;
}
}
vs.
abstract class AbstractService<T extends SomeAbstractEntity> {
protected T makeNewThing(); // object is expected, newness is assumed
public T myMethod(){
T object = makeNewThing(); // local object by calling constructor
object.doStuff();
return object;
}
}
class CarService extends AbstractService<Car> {
public Car makeNewThing(){
return new Car();
}
}
The only thing I can think of is that expecting a supplier ensures that a new object will be created, but when expecting an object we can only assume that the implementing classes are calling the constructor and not re-using an existing instance.
I'd like to know of other objective differences and possible use cases, if any. Thanks in advance.
Using a Supplier
postpones the creation of the instance.
This means that you might avoid a creation of an unnecessary instance.
For example, suppose you pass the output of makeNewThing()
to some method.
public void makeNewThingSometimes (T newInstance)
{
if (someCondition) {
this.instance = newInstance;
}
}
public void makeNewThingSometimes (Supplier<T> supplier)
{
if (someCondition) {
this.instance = supplier.get();
}
}
Calling the first variant requires creating an instance of T even if you are not going to use it.
Calling the second variant only creates an instance of T when necessary.
Using a Consumer
can save both storage (if the create instance requires a significant amount of memory) and time (if the execution of the constructor is expansive).
The only thing I can think of is that expecting a supplier ensures that a new object will be created,
Not necessarily.
You implement the Supplier
in this way :
return SomeEntityImplementation::new;
But you could have implemented it in this other way :
if (myCachedObject != null){
return (()-> myCachedObject);
}
return SomeEntityImplementation::new;
Both ways may be used to return a cached object or create a new one.
One of Supplier
advantages is the case of Supplier
creating an object : this one is actually created only as the Supplier.get()
method is invoked.
Note that in your example, using Supplier
doesn't bring any advantage as in both cases (with or without Supplier
) the object creation is already performed in a lazy way : as the factory method is invoked.
To take advantage of it, you should have a method that provides a Supplier<T>
as parameter as in the Eran and Dasblinkenlight examples.
Another Supplier
advantage is its ability to implement factory that may return multiple of things.
Using Supplier
allows to have a shorter and more readable code and besides that doesn't rely on Java Reflection.
Supposing that you want to create the object from an Enum
value, you could so write :
public enum MyBaseClassFactory {
ENUM_A (A::new),
ENUM_B (B::new),
ENUM_C (C::new),
ENUM_D (D::new);
private Supplier<BaseClass> supplier;
MyBaseClassFactory (Supplier<BaseClass> supplier){
this.supplier = supplier;
}
public BaseClass createObject(){
return supplier.get();
}
}
You could so use it :
BaseClass base = MyBaseClassFactory.ENUM_A.createObject();
Without Supplier
, you will have to use Reflection (that may fail at runtime) or write a verbose and unmaintainable code.
For example with Reflection :
public enum MyEnumFactoryClass {
ENUM_A(A.class), ENUM_B(B.class), ENUM_C(C.class), ENUM_D(D.class);
private Class<BaseClass> clazz;
MyEnumFactoryClass(Class<BaseClass> clazz) {
this.clazz = clazz;
}
public BaseClass createObject() {
return clazz.newInstance();
}
}
For example without reflection but with more verbose code :
public enum MyEnumFactoryClass {
ENUM_A {
@Override
public BaseClass createObject() {
return new A();
}
},
ENUM_B {
@Override
public BaseClass createObject() {
return new B();
}
},
ENUM_C {
@Override
public BaseClass createObject() {
return new C();
}
},
ENUM_D {
@Override
public BaseClass createObject() {
return new D();
}
};
public abstract BaseClass createObject();
}
You could of course take advantage in a close way of Supplier
by using it with a Map<String, Supplier<BaseClass>>
.
The first solution is more flexible, because an extra level of indirection in object creation lets users of your class library change the source of new items independently of ServiceImpl<SomeEntityImplementation>
class.
You can make a new Supplier<T>
instance without subclassing or recompiling ServiceImpl
, because there is an extra level of indirection. ServiceImpl
could be implemented as follows:
class ServiceImpl<SomeEntityImplementation> {
private final Supplier<SomeEntityImplementation> supplier;
public Supplier<T> makeNewThing(){
return supplier;
}
public ServiceImpl(Supplier<SomeEntityImplementation> s) {
supplier = s;
}
}
This makes it possible for users of ServiceImpl
to provide their own Supplier<T>
, which is not possible using the second approach, in which the source of new items is merged into the implementation of service itself.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With