Por que é um tipo de parâmetro, em seguida, um parâmetro de método mais forte

jukzi:

Por que é

public <R, F extends Function<T, R>> Builder<T> withX(F getter, R returnValue) {...}

mais rigorosa depois

public <R> Builder<T> with(Function<T, R> getter, R returnValue) {...}

Este é um acompanhamento sobre Porque é lambda tipo de retorno não verificados em tempo de compilação . Eu encontrei usando o método withX()como

.withX(MyInterface::getLength, "I am not a Long")

produz o erro tempo de compilação queria:

O tipo de getLength () a partir do tipo BuilderExample.MyInterface é longo, este é incompatível com o tipo de retorno do descritor: String

enquanto estiver usando o método with()não funciona.

exemplo completo:

import java.util.function.Function;

public class SO58376589 {
  public static class Builder<T> {
    public <R, F extends Function<T, R>> Builder<T> withX(F getter, R returnValue) {
      return this;
    }

    public <R> Builder<T> with(Function<T, R> getter, R returnValue) {
      return this;
    }

  }

  static interface MyInterface {
    public Long getLength();
  }

  public static void main(String[] args) {
    Builder<MyInterface> b = new Builder<MyInterface>();
    Function<MyInterface, Long> getter = MyInterface::getLength;
    b.with(getter, 2L);
    b.with(MyInterface::getLength, 2L);
    b.withX(getter, 2L);
    b.withX(MyInterface::getLength, 2L);
    b.with(getter, "No NUMBER"); // error
    b.with(MyInterface::getLength, "No NUMBER"); // NO ERROR !!
    b.withX(getter, "No NUMBER"); // error
    b.withX(MyInterface::getLength, "No NUMBER"); // error !!!
  }
}

javac SO58376589.java

SO58376589.java:32: error: method with in class Builder<T> cannot be applied to given types;
    b.with(getter, "No NUMBER"); // error
     ^
  required: Function<MyInterface,R>,R
  found: Function<MyInterface,Long>,String
  reason: inference variable R has incompatible bounds
    equality constraints: Long
    lower bounds: String
  where R,T are type-variables:
    R extends Object declared in method <R>with(Function<T,R>,R)
    T extends Object declared in class Builder
SO58376589.java:34: error: method withX in class Builder<T> cannot be applied to given types;
    b.withX(getter, "No NUMBER"); // error
     ^
  required: F,R
  found: Function<MyInterface,Long>,String
  reason: inference variable R has incompatible bounds
    equality constraints: Long
    lower bounds: String
  where F,R,T are type-variables:
    F extends Function<MyInterface,R> declared in method <R,F>withX(F,R)
    R extends Object declared in method <R,F>withX(F,R)
    T extends Object declared in class Builder
SO58376589.java:35: error: incompatible types: cannot infer type-variable(s) R,F
    b.withX(MyInterface::getLength, "No NUMBER"); // error
           ^
    (argument mismatch; bad return type in method reference
      Long cannot be converted to String)
  where R,F,T are type-variables:
    R extends Object declared in method <R,F>withX(F,R)
    F extends Function<T,R> declared in method <R,F>withX(F,R)
    T extends Object declared in class Builder
3 errors

Exemplo estendida

O exemplo a seguir mostra o comportamento diferente do método e do tipo de parâmetro fervida para baixo para um fornecedor. Além disso, mostra a diferença a um comportamento do consumidor para um parâmetro de tipo. E isso mostra que não faz uma diferença wether é um consumidor ou fornecedor para um parâmetro de método.

import java.util.function.Consumer;
import java.util.function.Supplier;
interface TypeInference {

  Number getNumber();

  void setNumber(Number n);

  @FunctionalInterface
  interface Method<R> {
    TypeInference be(R r);
  }

  //Supplier:
  <R> R letBe(Supplier<R> supplier, R value);
  <R, F extends Supplier<R>> R letBeX(F supplier, R value);
  <R> Method<R> let(Supplier<R> supplier);  // return (x) -> this;

  //Consumer:
  <R> R lettBe(Consumer<R> supplier, R value);
  <R, F extends Consumer<R>> R lettBeX(F supplier, R value);
  <R> Method<R> lett(Consumer<R> consumer);


  public static void main(TypeInference t) {
    t.letBe(t::getNumber, (Number) 2); // Compiles :-)
    t.lettBe(t::setNumber, (Number) 2); // Compiles :-)
    t.letBe(t::getNumber, 2); // Compiles :-)
    t.lettBe(t::setNumber, 2); // Compiles :-)
    t.letBe(t::getNumber, "NaN"); // !!!! Compiles :-(
    t.lettBe(t::setNumber, "NaN"); // Does not compile :-)

    t.letBeX(t::getNumber, (Number) 2); // Compiles :-)
    t.lettBeX(t::setNumber, (Number) 2); // Compiles :-)
    t.letBeX(t::getNumber, 2); // !!! Does not compile  :-(
    t.lettBeX(t::setNumber, 2); // Compiles :-)
    t.letBeX(t::getNumber, "NaN"); // Does not compile :-)
    t.lettBeX(t::setNumber, "NaN"); // Does not compile :-)

    t.let(t::getNumber).be(2); // Compiles :-)
    t.lett(t::setNumber).be(2); // Compiles :-)
    t.let(t::getNumber).be("NaN"); // Does not compile :-)
    t.lett(t::setNumber).be("NaN"); // Does not compile :-)
  }
}
user31601:

Esta é uma pergunta muito interessante. A resposta, eu tenho medo, é complicado.

tl; dr

Trabalhando a diferença envolve uma leitura bastante aprofundada do Java especificação inferência tipo , mas basicamente se resume a isto:

  • Todas as outras coisas iguais, o infere do compilador o mais específico tipo que pode.
  • No entanto, se pode encontrar uma substituição para um parâmetro de tipo que satisfaz todos os requisitos, então a compilação será bem sucedida, no entanto vaga a substituição acaba por ser.
  • Para withexiste um (reconhecidamente vaga) de substituição que preencha todos os requisitos sobre R:Serializable
  • Para withX, a introdução do tipo de parâmetros adicionais Fforças o compilador para resolver Rprimeiro, sem considerar a restrição F extends Function<T,R>. Rresolve o (muito mais específico) Stringque, em seguida, significa que a inferência de Ffalhar.

Este último ponto é o mais importante, mas também o mais mão-ondulado. Eu não posso pensar em um melhor maneira concisa de fraseado, por isso, se você quiser mais detalhes, eu sugiro que você leia a explicação completa abaixo.

É este o comportamento desejado?

Vou sair em um membro aqui e dizer não .

Eu não estou sugerindo que há um erro na especificação, mais que (no caso de withX) os projetistas da linguagem colocaram suas mãos para cima e disse "há algumas situações em que tipo de inferência fica muito difícil, por isso vamos falhar" . Mesmo que o comportamento do compilador em relação ao withXparece ser o que você quiser, eu consideraria que para ser um efeito colateral incidental da especificação atual, em vez de uma decisão de projeto destina-se positivamente.

Isto é importante, porque informa a pergunta que eu deveria contar com esse comportamento em meu projeto de candidatura? Eu diria que você não deve, porque você não pode garantir que as futuras versões da linguagem continuará a se comportar dessa maneira.

Embora seja verdade que os designers de linguagem esforçar muito para não quebrar aplicativos existentes quando eles atualizar sua especificação / design / compilador, o problema é que o comportamento que você quer contar com é aquele em que o compilador atualmente falhar (ou seja, não uma aplicação existente ). Atualizações langauge transformar código não compilar em compilar o código todo o tempo. Por exemplo, o seguinte código pode ser garantida não para compilar em Java 7, mas seria compilar em Java 8:

static Runnable x = () -> System.out.println();

O seu caso de uso não é diferente.

Outra razão eu seria cauteloso sobre a utilização do withXmétodo é o Fpróprio parâmetro. Geralmente, um parâmetro de tipo genérico em um método (que não aparece no tipo de retorno) existe para ligar os tipos de várias partes da assinatura juntos. É dizer:

Eu não ligo para o que Té, mas quer ter certeza de que onde quer que eu uso Té o mesmo tipo.

Logicamente, então, esperamos que cada parâmetro do tipo a aparecer pelo menos duas vezes em uma assinatura do método, caso contrário, "ele não está fazendo nada". Fem sua withXsó aparece uma vez na assinatura, o que me sugere a utilização de um parâmetro de tipo não em linha com a intenção deste recurso da língua.

Uma implementação alternativa

Uma maneira de implementar isso em um "comportamento desejado" um pouco mais forma seria a de dividir o seu withmétodo se em uma cadeia de 2:

public class Builder<T> {

    public final class With<R> {
        private final Function<T,R> method;

        private With(Function<T,R> method) {
            this.method = method;
        }

        public Builder<T> of(R value) {
            // TODO: Body of your old 'with' method goes here
            return Builder.this;
        }
    }

    public <R> With<R> with(Function<T,R> method) {
        return new With<>(method);
    }

}

Este pode, então, ser utilizado como se segue:

b.with(MyInterface::getLong).of(1L); // Compiles
b.with(MyInterface::getLong).of("Not a long"); // Compiler error

Isto não inclui um parâmetro de tipo estranho como o seu withXfaz. Ao quebrar o método em duas assinaturas, também exprime melhor a intenção de que você está tentando fazer, a partir de um ponto tipo de vista da segurança:

  • Os primeiros conjuntos do método acima de uma classe ( With) que define o tipo de base no método de referência.
  • O método scond ( of) restringe o tipo de valueser compatível com o que você configurou anteriormente.

A única maneira de uma versão futura da língua seria capaz de compilar isso é se o pato-digitando completa implementado, o que parece improvável.

Uma nota final para fazer essa coisa toda irrelevante: Eu acho Mockito (e em particular a sua funcionalidade stubbing) pode, basicamente, já faz o que você está tentando alcançar com o seu "tipo de construtor genérico seguro". Talvez você poderia usar apenas que em vez disso?

O completo (ish) explicação

Eu estou indo para o trabalho através do procedimento de inferência de tipos para ambos withe withX. Isto é bastante longa, então levá-lo lentamente. Apesar de ser muito tempo, eu ainda deixei um monte de detalhes para fora. Você pode querer referir-se a especificação para mais detalhes (siga os links) para se convencer de que estou certo (I pode ter cometido um erro).

Além disso, para simplificar as coisas um pouco, eu vou usar um exemplo de código mais minimalista. A principal diferença é que ele alterna para fora Functionpara Supplier, por isso há menos tipos e parâmetros em jogo. Aqui está um trecho completo que reproduz o comportamento que você descreveu:

public class TypeInference {

    static long getLong() { return 1L; }

    static <R> void with(Supplier<R> supplier, R value) {}
    static <R, F extends Supplier<R>> void withX(F supplier, R value) {}

    public static void main(String[] args) {
        with(TypeInference::getLong, "Not a long");       // Compiles
        withX(TypeInference::getLong, "Also not a long"); // Does not compile
    }

}

Trabalho de deixar passar o tipo de inferência aplicabilidade e inferência tipo de procedimento para cada invocação de método, por sua vez:

with

Nós temos:

with(TypeInference::getLong, "Not a long");

O conjunto ligado inicial, B 0 , é:

  • R <: Object

Todas as expressões de parâmetros são pertinentes à aplicabilidade .

Assim, o conjunto de constrangimento inicial de inferência aplicabilidade , C , é:

  • TypeInference::getLong é compatível com Supplier<R>
  • "Not a long" é compatível com R

Isto reduz a ligado conjunto B 2 de:

  • R <: Object(a partir de B 0 )
  • Long <: R (A partir da primeira restrição)
  • String <: R (A partir da segunda restrição)

Uma vez que este não contém o 'limite falsa ', e (presumo) resolução da Rsucede (dando Serializable), então a invocação é aplicável.

Então, vamos passar para tipo invocação inferência .

O novo conjunto de restrição, C , com associados de entrada e de saída variáveis, é:

  • TypeInference::getLong é compatível com Supplier<R>
    • Variáveis de entrada: nenhum
    • variáveis ​​de saída: R

Esta não contém interdependências entre entrada e de saída variáveis, de modo que pode ser reduzido em um único passo, e o conjunto consolidada final, B 4 , é o mesmo que B 2 . Assim, resolução sucede como antes, eo compilador respira um suspiro de alívio!

withX

Nós temos:

withX(TypeInference::getLong, "Also not a long");

O conjunto ligado inicial, B 0 , é:

  • R <: Object
  • F <: Supplier<R>

Apenas a segunda expressão parâmetro é pertinentes à aplicabilidade . O primeiro ( TypeInference::getLong) não é, porque satisfaz a seguinte condição:

Se mé um método genérico e a chamada de método não fornece argumentos de tipo explícitas, uma expressão lambda explicitamente digitado ou uma expressão de referência método exacto para o qual o tipo de alvo correspondente (como derivado a partir da assinatura de m) é um parâmetro de tipo m.

Assim, o conjunto de constrangimento inicial de inferência aplicabilidade , C , é:

  • "Also not a long" é compatível com R

Isto reduz a ligado conjunto B 2 de:

  • R <: Object(a partir de B 0 )
  • F <: Supplier<R>(a partir de B 0 )
  • String <: R (A partir da restrição)

Novamente, uma vez que este não contém o 'obrigado falsa ', e resolução de Rsucesso (dando String), então a invocação é aplicável.

Tipo de invocação inferência mais uma vez ...

Desta vez, o novo conjunto de restrição, C , com associados de entrada e de saída variáveis, é:

  • TypeInference::getLong é compatível com F
    • variáveis ​​de entrada: F
    • Variáveis de saída: nenhum

Novamente, não temos interdependências entre entrada e saída variáveis. No entanto, desta vez, não é uma variável de entrada ( F), por isso temos de resolver isso antes de tentar a redução . Então, começamos com o nosso conjunto vinculado B 2 .

  1. Nós determinamos um subconjunto Vda seguinte forma:

    Dado um conjunto de variáveis de inferência para resolver, vamos Vser a união deste conjunto e todas as variáveis sobre as quais a resolução de pelo menos uma variável neste conjunto depende.

    Pelo segundo ligado em B 2 , a resolução de Fdepende R, por isso V := {F, R}.

  2. Nós escolher um subconjunto de Vacordo com a regra:

    deixar { α1, ..., αn }ser um subconjunto não vazio de variáveis não instanciadas em Vtal que i) para todos i (1 ≤ i ≤ n), se αidepende da resolução de uma variável β, em seguida, quer βtem uma instanciação ou existe alguma jtal que β = αj; e ii) não existe nenhum subconjunto apropriado não-vazia de { α1, ..., αn }com esta propriedade.

    O único subconjunto de Vque satisfaz esta propriedade é {R}.

  3. Usando o terceiro obrigado ( String <: R) instanciamos R = Stringe incorporar isso em nosso conjunto vinculado. Ragora é resolvido, e a segunda ligada de forma eficaz se torna F <: Supplier<String>.

  4. Usando o (revisto) segundo ligado, instanciamos F = Supplier<String>. Fagora é resolvido.

Agora que Festá resolvido, podemos prosseguir com a redução , usando a nova restrição:

  1. TypeInference::getLong é compatível com Supplier<String>
  2. ... reduz-se Long é compatível com String
  3. ... o que reduz a falsa

... e ficamos com um erro do compilador!


Notas adicionais sobre o 'exemplo estendido'

O exemplo estendido na questão olha para alguns casos interessantes que não estão directamente abrangidos pelos trabalhos acima:

  • Quando o tipo de valor é um subtipo do tipo de retorno do método ( Integer <: Number)
  • Onde a interface funcional é contravariante no tipo inferido (ou seja, Consumerem vez de Supplier)

Em particular, três das invocações dadas destaca como potencialmente sugerindo comportamento 'diferente' compilador ao descrito nas explicações:

t.lettBe(t::setNumber, "NaN"); // Does not compile :-)

t.letBeX(t::getNumber, 2); // !!! Does not compile  :-(
t.lettBeX(t::setNumber, 2); // Compiles :-)

O segundo destes 3 vai passar exatamente o mesmo processo de inferência como withXacima (apenas substituir Longcom Numbere Stringcom Integer). Isso ilustra ainda uma outra razão pela qual você não deve contar com esse comportamento inferência de tipos falha para seu projeto de classe, como a falta de compilar aqui é provavelmente não um comportamento desejável.

Para os outros 2 (e na verdade qualquer uma das outras invocações envolvendo um Consumerque deseja trabalhar através), o comportamento deve ser aparente se você trabalhar através do processo de inferência de tipos definidos por um dos métodos acima (ou seja, withpara o primeiro, withXpara o terceiro). Há apenas uma pequena mudança que você precisa para tomar nota de:

  • A restrição no primeiro parâmetro ( t::setNumber é compatível com Consumer<R> ) irá reduzir a R <: Numbervez de Number <: Rcomo faz para Supplier<R>. Isto é descrito na documentação relacionada na redução.

Eu deixá-lo como um exercício para o leitor a obra carfully através de um dos procedimentos acima, armados com este pedaço de conhecimento adicional, para demonstrar a si mesmos exatamente por isso que uma invocação especial faz ou não compilar.

Acho que você gosta

Origin http://43.154.161.224:23101/article/api/json?id=306388&siteId=1
Recomendado
Clasificación