We all know Long extends Number
. So why does this not compile?
And how to define the method with
so that the program compiles without any manual cast?
import java.util.function.Function;
public class Builder<T> {
static public interface MyInterface {
Number getNumber();
Long getLong();
}
public <F extends Function<T, R>, R> Builder<T> with(F getter, R returnValue) {
return null;//TODO
}
public static void main(String[] args) {
// works:
new Builder<MyInterface>().with(MyInterface::getLong, 4L);
// works:
new Builder<MyInterface>().with(MyInterface::getNumber, (Number) 4L);
// works:
new Builder<MyInterface>().<Function<MyInterface, Number>, Number> with(MyInterface::getNumber, 4L);
// works:
new Builder<MyInterface>().with((Function<MyInterface, Number>) MyInterface::getNumber, 4L);
// compilation error: Cannot infer ...
new Builder<MyInterface>().with(MyInterface::getNumber, 4L);
// compilation error: Cannot infer ...
new Builder<MyInterface>().with(MyInterface::getNumber, Long.valueOf(4));
// compiles but also involves typecast (and Casting Number to Long is not even safe):
new Builder<MyInterface>().with( myInterface->(Long) myInterface.getNumber(), 4L);
// compiles but also involves manual conversion:
new Builder<MyInterface>().with(myInterface -> myInterface.getNumber().longValue(), 4L);
// compiles (compiler you are kidding me?):
new Builder<MyInterface>().with(castToFunction(MyInterface::getNumber), 4L);
}
static <X, Y> Function<X, Y> castToFunction(Function<X, Y> f) {
return f;
}
}
- Cannot infer type argument(s) for
<F, R> with(F, R)
- The type of getNumber() from the type Builder.MyInterface is Number, this is incompatible with the descriptor's return type: Long
For use case see: Why is lambda return type not checked at compile time
This expression :
new Builder<MyInterface>().with(MyInterface::getNumber, 4L);
can be rewritten as :
new Builder<MyInterface>().with(myInterface -> myInterface.getNumber(), 4L);
Taking into account method signature :
public <F extends Function<T, R>, R> Builder<T> with(F getter, R returnValue)
R
will be inferred to Long
F
will be Function<MyInterface, Long>
and you pass a method reference which will be infered as Function<MyInterface, Number>
This is the key - how should compiler predict that you actually want to return Long
from a function with such signature? It will not do the downcasting for you.
Since Number
is superclass of Long
and Number
is not necessairly a Long
(this is why it does not compile) - you would have to cast explicitly on your own :
new Builder<MyInterface>().with(myInterface -> (Long) myInterface.getNumber(), 4L);
making F
to be Function<MyIinterface, Long>
or pass generic arguments explicitly during method call as you did :
new Builder<MyInterface>().<Function<MyInterface, Number>, Number> with(MyInterface::getNumber, 4L);
and know R
will be seen as Number
and code will compile.
The key to your error is in the generic declaration of the type of F
: F extends Function<T, R>
. The statement that does not work is: new Builder<MyInterface>().with(MyInterface::getNumber, 4L);
First, you have a new Builder<MyInterface>
. The declaration of the class therefore implies T = MyInterface
. As per your declaration of with
, F
must be a Function<T, R>
, which is a Function<MyInterface, R>
in this situation. Therefore, the parameter getter
must take a MyInterface
as parameter (satisfied by the method references MyInterface::getNumber
and MyInterface::getLong
), and return R
, which must be the same type as the second parameter to the function with
. Now, let's see if this holds for all of your cases:
// T = MyInterface, F = Function<MyInterface, Long>, R = Long
new Builder<MyInterface>().with(MyInterface::getLong, 4L);
// T = MyInterface, F = Function<MyInterface, Number>, R = Number
// 4L explicitly widened to Number
new Builder<MyInterface>().with(MyInterface::getNumber, (Number) 4L);
// T = MyInterface, F = Function<MyInterface, Number>, R = Number
// 4L implicitly widened to Number
new Builder<MyInterface>().<Function<MyInterface, Number>, Number>with(MyInterface::getNumber, 4L);
// T = MyInterface, F = Function<MyInterface, Number>, R = Number
// 4L implicitly widened to Number
new Builder<MyInterface>().with((Function<MyInterface, Number>) MyInterface::getNumber, 4L);
// T = MyInterface, F = Function<MyInterface, Number>, R = Long
// F = Function<T, not R> violates definition, therefore compilation error occurs
// Compiler cannot infer type of method reference and 4L at the same time,
// so it keeps the type of 4L as Long and attempts to infer a match for MyInterface::getNumber,
// only to find that the types don't match up
new Builder<MyInterface>().with(MyInterface::getNumber, 4L);
You can "fix" this problem with the following options:
// stick to Long
new Builder<MyInterface>().with(MyInterface::getLong, 4L);
// stick to Number
new Builder<MyInterface>().with(MyInterface::getNumber, (Number) 4L);
// explicitly convert the result of getNumber:
new Builder<MyInterface>().with(myInstance -> (Long) myInstance.getNumber(), 4L);
// explicitly convert the result of getLong:
new Builder<MyInterface>().with(myInterface -> (Number) myInterface.getLong(), (Number) 4L);
Beyond this point, it's mostly a design decision for which option reduces code complexity for your particular application, so choose whatever fits you best.
The reason that you cannot do this without casting lies in the following, from the Java Language Specification:
Boxing conversion treats expressions of a primitive type as expressions of a corresponding reference type. Specifically, the following nine conversions are called the boxing conversions:
- From type boolean to type Boolean
- From type byte to type Byte
- From type short to type Short
- From type char to type Character
- From type int to type Integer
- From type long to type Long
- From type float to type Float
- From type double to type Double
- From the null type to the null type
As you can clearly see, there is no implicit boxing conversion from long to Number, and the widening conversion from Long to Number can only occur when the compiler is sure that it requires a Number and not a Long. As there is a conflict between the method reference that requires a Number and the 4L that provides a Long, the compiler (for some reason???) unable to make the logical leap that Long is-a Number and deduce that F
is a Function<MyInterface, Number>
.
Instead, I managed to resolve the problem by slightly editing the function signature:
public <R> Builder<T> with(Function<T, ? super R> getter, R returnValue) {
return null;//TODO
}
After this change, the following occurs:
// doesn't work, as it should not work
new Builder<MyInterface>().with(MyInterface::getLong, (Number), 4L);
// works, as it always did
new Builder<MyInterface>().with(MyInterface::getLong, 4L);
// works, as it should work
new Builder<MyInterface>().with(MyInterface::getNumber, (Number)4L);
// works, as you wanted
new Builder<MyInterface>().with(MyInterface::getNumber, 4L);
Edit:
After spending some more time on it, it's annoyingly difficult to enforce getter-based type safety. Here's a working example that uses setter methods to enforce a builder's type-safety:
public class Builder<T> {
static public interface MyInterface {
//setters
void number(Number number);
void Long(Long Long);
void string(String string);
//getters
Number number();
Long Long();
String string();
}
// whatever object we're building, let's say it's just a MyInterface for now...
private T buildee = (T) new MyInterface() {
private String string;
private Long Long;
private Number number;
public void number(Number number)
{
this.number = number;
}
public void Long(Long Long)
{
this.Long = Long;
}
public void string(String string)
{
this.string = string;
}
public Number number()
{
return this.number;
}
public Long Long()
{
return this.Long;
}
public String string()
{
return this.string;
}
};
public <R> Builder<T> with(BiConsumer<T, R> setter, R val)
{
setter.accept(this.buildee, val); // take the buildee, and set the appropriate value
return this;
}
public static void main(String[] args) {
// works:
new Builder<MyInterface>().with(MyInterface::Long, 4L);
// works:
new Builder<MyInterface>().with(MyInterface::number, (Number) 4L);
// compile time error, as it shouldn't work
new Builder<MyInterface>().with(MyInterface::Long, (Number) 4L);
// works, as it always did
new Builder<MyInterface>().with(MyInterface::Long, 4L);
// works, as it should
new Builder<MyInterface>().with(MyInterface::number, (Number)4L);
// works, as you wanted
new Builder<MyInterface>().with(MyInterface::number, 4L);
// compile time error, as you wanted
new Builder<MyInterface>().with(MyInterface::number, "blah");
}
}
Provided the type-safe ability to construct an object, hopefully at some point in the future we'll be able to return an immutable data object from the builder (maybe by adding a toRecord()
method to the interface, and specifying the builder as a Builder<IntermediaryInterfaceType, RecordType>
), so you don't even have to worry about the resulting object being modified. Honestly, it's an absolute shame that it requires so much effort to get a type-safe field-flexible builder, but it's probably impossible without some new features, code generation, or an annoying amount of reflection.
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