Todos sabemos que Long se estende Number
. Então, por que isso não compila?
E como definir o método with
para que o programa seja compilado sem nenhuma conversão manual?
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;
}
}
- Não é possível inferir argumento (s) de tipo para
<F, R> with(F, R)
- O tipo de getNumber () do tipo Builder.MyInterface é Number, isso é incompatível com o tipo de retorno do descritor: Long
Para o caso de uso, consulte: Por que o tipo de retorno lambda não é verificado no tempo de compilação
java
type-inference
jukzi
fonte
fonte
MyInterface
?<F extends Function<T, R>, R, S extends R> Builder<T> with(F getter, S returnValue)
mas conseguijava.lang.Number cannot be converted to java.lang.Long)
, o que é surpreendente, porque não vejo de onde o compilador obtém a ideia de que o valor de retornogetter
precisará ser convertidoreturnValue
.Number getNumber()
para<A extends Number> A getNumber()
faz as coisas funcionarem. Não faço ideia se é isso que você queria. Como outros já disseram, a questão é queMyInterface::getNumber
poderia ser uma função que retorna,Double
por exemplo, e nãoLong
. Sua declaração não permite que o compilador restrinja o tipo de retorno com base em outras informações presentes. Ao usar um tipo de retorno genérico, você permite que o compilador faça isso; portanto, ele funciona.Respostas:
Esta expressão:
pode ser reescrito como:
Considerando a assinatura do método:
R
será inferido paraLong
F
seráFunction<MyInterface, Long>
e você passa uma referência de método que será inferida como
Function<MyInterface, Number>
Esta é a chave - como o compilador deve prever que você realmente deseja retornarLong
de uma função com essa assinatura? Não fará o downcasting para você.Desde que
Number
é superclasse deLong
eNumber
não é necessariamente umLong
(é por isso que não é compilado) - você teria que lançar explicitamente por conta própria:fazer
F
para serFunction<MyIinterface, Long>
ou passar argumentos genéricos explicitamente durante a chamada do método, como você fez:e saber
R
será visto comoNumber
e o código será compilado.fonte
with
está escrito. VocêMJyInterface::getNumber
tem o tipoFunction<MyInterface, Number>
soR=Number
e tambémR=Long
o outro argumento (lembre-se de que os literais Java não são polimórficos!). Nesse ponto, o compilador para porque nem sempre é possível converter aNumber
em aLong
. A única maneira de corrigir isso é para mudarMyInterface
para usar<A extends Number> Number
como tipo de retorno, isso faz com que o compilador temR=A
e, em seguida,R=Long
e uma vez queA extends Number
ele pode substituirA=Long
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 novoBuilder<MyInterface>
. A declaração da classe, portanto, implicaT = MyInterface
. Conforme sua declaração dewith
,F
deve ser umFunction<T, R>
, que é umFunction<MyInterface, R>
nessa situação. Portanto, o parâmetrogetter
deve usar umMyInterface
parâmetro as (satisfeito pelas referências ao métodoMyInterface::getNumber
eMyInterface::getLong
) e returnR
, que deve ser do mesmo tipo que o segundo parâmetro da funçãowith
. Agora, vamos ver se isso vale para todos os seus casos:Você pode "corrigir" esse problema com as seguintes opções:
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 :
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:
Após essa alteração, ocorre o seguinte:
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:
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 aBuilder<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.fonte
Parece que o compilador usou o valor 4L para decidir que R é longo e getNumber () retorna um número, que não é necessariamente um longo.
Mas não sei por que o valor tem precedência sobre o método ...
fonte
O compilador Java geralmente não é bom para inferir vários tipos genéricos / curinga ou curingas. Frequentemente, não consigo compilar algo sem usar uma função auxiliar para capturar ou inferir alguns dos tipos.
Mas, você realmente precisa capturar o tipo exato de
Function
asF
? Caso contrário, talvez os seguintes trabalhos, e como você pode ver, também funcionem com os subtipos deFunction
.fonte
A parte mais interessante está na diferença entre essas duas linhas, eu acho:
No primeiro caso, o
T
é explicitamenteNumber
,4L
tambémNumber
não é um problema. No segundo caso,4L
é aLong
, tambémT
é aLong
, portanto sua função não é compatível e Java não pode saber se você quis dizerNumber
ouLong
.fonte
Com a seguinte assinatura:
todos os seus exemplos são compilados, exceto o terceiro, que exige explicitamente que o método tenha duas variáveis de tipo.
O motivo de sua versão não funcionar é porque as referências de método do Java não possuem um tipo específico. Em vez disso, eles têm o tipo necessário no contexto fornecido. No seu caso,
R
é inferido que éLong
por causa do4L
, mas o getter não pode ter o tipoFunction<MyInterface,Long>
porque em Java, tipos genéricos são invariantes em seus argumentos.fonte
with( getNumber,"NO NUMBER")
que não é desejado. Também não é verdade que os genéricos são sempre invariante (veja stackoverflow.com/a/58378661/9549750 para uma prova que os genéricos de setters se comportam outro, em seguida, aqueles de getters)Thing<Cat>
a umaThing<? extends Animal>
variável, mas, por covariância real, eu esperaria que aThing<Cat>
pudesse ser atribuído a aThing<Animal>
. Outros idiomas, como o Kotlin, permitem definir variáveis do tipo co- e contravariante.