A chave para o seu erro está na declaração genérica do tipo de F
: F extends Function<T, R>
. A afirmação que não funciona é: new Builder<MyInterface>().with(MyInterface::getNumber, 4L);
Primeiro, você tem um novo Builder<MyInterface>
. A declaração da classe, portanto, implica T = MyInterface
. Conforme sua declaração de with
, F
deve ser um Function<T, R>
, que é um Function<MyInterface, R>
nessa situação. Portanto, o parâmetro getter
deve usar um MyInterface
parâmetro as (satisfeito pelas referências ao método MyInterface::getNumber
e MyInterface::getLong
) e return R
, que deve ser do mesmo tipo que o segundo parâmetro da função with
. Agora, vamos ver se isso vale para todos os seus casos:
// 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);
Você pode "corrigir" esse problema com as seguintes opções:
// 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);
Além desse ponto, é principalmente uma decisão de design para qual opção reduz a complexidade do código para seu aplicativo em particular; portanto, escolha o que melhor se adequa a você.
O motivo pelo qual você não pode fazer isso sem a transmissão está no seguinte, na Java Language Specification :
A conversão de boxe trata expressões de um tipo primitivo como expressões de um tipo de referência correspondente. Especificamente, as nove conversões a seguir são chamadas de conversões de boxe :
- Do tipo booleano para o tipo booleano
- Do tipo byte para o tipo Byte
- Do tipo curto para o tipo Curto
- Do tipo char para o tipo Character
- Do tipo int para o tipo Inteiro
- Do tipo longo para o tipo Longo
- Do tipo float para o tipo Float
- Do tipo double para o tipo Double
- Do tipo nulo para o tipo nulo
Como você pode ver claramente, não há conversão implícita de box de long para Number, e a conversão de ampliação de Long para Number só pode ocorrer quando o compilador tiver certeza de que exige um Número e não um Long. Como existe um conflito entre a referência do método que requer um Número e o 4L que fornece um Long, o compilador (por algum motivo ???) não consegue dar o salto lógico que Long é um Número e deduz que F
é umFunction<MyInterface, Number>
.
Em vez disso, consegui resolver o problema editando ligeiramente a assinatura da função:
public <R> Builder<T> with(Function<T, ? super R> getter, R returnValue) {
return null;//TODO
}
Após essa alteração, ocorre o seguinte:
// 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:
Depois de gastar mais tempo com ele, é irritantemente difícil impor a segurança do tipo baseado em getter. Aqui está um exemplo de trabalho que usa métodos setter para impor a segurança de tipo de um construtor:
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");
}
}
Com a capacidade segura de digitar para construir um objeto, esperamos que, em algum momento no futuro, possamos retornar um objeto de dados imutáveis do construtor (talvez adicionando um toRecord()
método à interface e especificando o construtor como a Builder<IntermediaryInterfaceType, RecordType>
), para que você nem precise se preocupar com o objeto resultante sendo modificado. Honestamente, é uma pena absoluta que exija muito esforço para obter um construtor flexível em campo com segurança de tipo, mas provavelmente é impossível sem alguns novos recursos, geração de código ou uma quantidade irritante de reflexão.
MyInterface
?