¿Por qué es un tipo de parámetro más fuerte que un parámetro de método

jukzi:

Por que es

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

más estricta a continuación,

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

Este es un seguimiento de qué se lambda tipo de retorno no se controla en tiempo de compilación . He encontrado utilizando el método withX()como

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

produce el error de tiempo de compilación querido:

El tipo de getLength () de la BuilderExample.MyInterface tipo es largo, esto es incompatible con el tipo de devolución del descriptor: String

mientras se utiliza el método with()no lo hace.

completo ejemplo:

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

Ejemplo Extended

El siguiente ejemplo muestra el diferente comportamiento de método y parámetro de tipo de reducir a un proveedor. Además se nota la diferencia a un comportamiento del consumidor para un parámetro de tipo. Y muestra que no hace una diferencia carnero es un consumidor o proveedor para un 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 es una pregunta muy interesante. La respuesta, me temo, es complicado.

tl; dr

La elaboración de la diferencia implica una lectura bastante profundo de Java especificación de inferencia de tipos , pero básicamente se reduce a esto:

  • Todas las demás cosas iguales, el compilador infiere el más específico de tipo que puede.
  • Sin embargo, si se puede encontrar una sustitución para un parámetro de tipo que satisfaga todos los requisitos, entonces la compilación tendrán éxito, sin embargo vaga la sustitución resulta ser.
  • Por withexiste una sustitución (ciertamente vago) que satisface todos los requisitos de R:Serializable
  • Para withXla introducción de los parámetros adicionales Tipo Ffuerzas del compilador para resolver Ren primer lugar, sin tener en cuenta la restricción F extends Function<T,R>. RResuelve el (mucho más específico) Stringque significa, entonces, que la inferencia de Ffalla.

Este último punto es el más, pero también el más importante a mano ondulado. No puedo pensar en una mejor forma concisa de expresarlo, así que si quieres más detalles, le sugiero que lea la explicación completa más abajo.

¿Es este comportamiento previsto?

Voy a salir a un miembro aquí, y decir no .

No estoy sugiriendo que hay un error en la especificación, más que (en el caso de withX) los diseñadores del lenguaje han puesto sus manos y dijo "hay algunas situaciones en las que la inferencia de tipos que baje demasiado duro, por lo que sólo tendremos que fallamos" . A pesar de que el comportamiento del compilador con respecto al withXparece ser lo que quiera, yo considero que para ser un efecto secundario incidental de la especificación actual, en lugar de una decisión positiva diseño previsto.

Esto es importante, ya que informa a la pregunta ¿Debo confiar en este comportamiento en mi diseño de la aplicación? Yo diría que no debería, porque no se puede garantizar que las futuras versiones de la lengua continuarán comportarse de esta manera.

Si bien es cierto que los diseñadores de lenguajes no tratan muy difícil de romper las aplicaciones existentes cuando actualicen sus especificaciones / diseño / compilador, el problema es que el comportamiento que desea confiar es uno donde el compilador Actualmente no (es decir, no una aplicación existente ). Actualizaciones de langauge convierten código no compilar código en la compilación de todo el tiempo. Por ejemplo, el siguiente código podría ser garantizada sin compilar en Java 7, pero sería compilar en Java 8:

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

La utilización de los casos no es diferente.

Otra razón por la que estaría cauteloso sobre el uso de su withXmétodo es el Fpropio parámetro. Generalmente, un parámetro de tipo genérico en un método (que no aparece en el tipo de retorno) existe para obligar a los tipos de múltiples partes de la firma juntos. Es como decir:

No me importa lo que Tes, pero quiero estar seguro de que dondequiera que yo uso Tes el mismo tipo.

Lógicamente, entonces, esperaríamos que cada parámetro de tipo que aparezca al menos dos veces en una firma de método, de lo contrario "no hace nada". Fen su withXsólo aparece una vez en la firma, que me sugiere el uso de un parámetro de tipo no en línea con la intención de esta característica de la lengua.

Una implementación alternativa

Una forma de implementar esto en un "comportamiento previsto" un poco más manera sería dividir el withmétodo arriba en una cadena 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);
    }

}

Esto se puede usar entonces como sigue:

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

Esto no incluye un parámetro de tipo ajeno al igual que su withXhace. Al romper el método en dos firmas, sino que también expresa mejor la intención de lo que estás tratando de hacer, desde un punto de vista de tipo de seguridad:

  • Los primeros conjuntos de método hasta una clase ( With) que define el tipo basado en la referencia del método.
  • El método scond ( of) restringe el tipo de la valueque sea compatible con lo que configuró previamente.

La única manera que una versión futura de la lengua sería capaz de compilar esto es si el total de pato a escribir en marcha, lo que parece poco probable.

Una nota final para hacer todo esto irrelevante: Creo Mockito (y, en particular, su funcionalidad stubbing) podría básicamente ya hacer lo que estamos tratando de lograr con su "tipo de constructor genérico seguro". Tal vez usted podría utilizar ese lugar?

El (ish) explicación completa

Voy a trabajar a través del procedimiento de inferencia de tipos para ambos withy withX. Esto es bastante largo, así que tome con calma. A pesar de ser larga, he dejado todavía un buen montón de detalles a cabo. Es posible que desee hacer referencia a la especificación para más detalles (seguir los enlaces) para convencerse de que estoy en lo correcto (I bien puede haber cometido un error).

Además, para simplificar las cosas un poco, voy a utilizar un ejemplo de código más mínimo. La diferencia principal es que se intercambia a cabo Functiondurante Supplier, por lo que hay menos tipos y parámetros en juego. He aquí un fragmento completo que reproduce el comportamiento que usted describe:

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
    }

}

El trabajo de dejar pasar el tipo de inferencia aplicabilidad y la inferencia de tipos procedimiento para cada invocación de métodos a su vez:

with

Tenemos:

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

El conjunto consolidado inicial, B 0 , es:

  • R <: Object

Todas las expresiones de los parámetros se pertinentes a la aplicabilidad .

Por lo tanto, el conjunto de restricciones inicial para la inferencia aplicabilidad , C , es:

  • TypeInference::getLong es compatible con Supplier<R>
  • "Not a long" es compatible con R

Esto reduce al límite conjunto B 2 de:

  • R <: Object(de B 0 )
  • Long <: R (De la primera restricción)
  • String <: R (De la segunda restricción)

Dado que este no contiene la cota ' falso ', y (supongo) Resolución de Rtiene éxito (dar Serializable), entonces la invocación es aplicable.

Por lo tanto, pasamos a tipo de invocación de inferencia .

El nuevo conjunto de restricciones, C , con asociados de entrada y de salida de las variables, es:

  • TypeInference::getLong es compatible con Supplier<R>
    • Las variables de entrada: ninguno
    • Variables de salida: R

Este no contiene interdependencias entre entrada y salida de las variables, lo que puede ser reducida en un solo paso, y el conjunto final consolidado, B 4 , es el mismo que B 2 . Por lo tanto, la resolución tiene éxito como antes, y el compilador da un suspiro de alivio!

withX

Tenemos:

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

El conjunto consolidado inicial, B 0 , es:

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

Sólo la segunda expresión parámetro se pertinentes con al aplicabilidad . El primero ( TypeInference::getLong) no es, ya que cumple la condición siguiente:

Si mes un método genérico y la invocación del método no proporciona argumentos de tipo explícitos, una expresión lambda mecanografiado explícitamente o una expresión de referencia método exacto para el que el correspondiente tipo de destino (tal como se deriva de la firma del m) es un parámetro de tipo de m.

Por lo tanto, el conjunto de restricciones inicial para la inferencia aplicabilidad , C , es:

  • "Also not a long" es compatible con R

Esto reduce al límite conjunto B 2 de:

  • R <: Object(de B 0 )
  • F <: Supplier<R>(de B 0 )
  • String <: R (De la restricción)

Una vez más, ya que este no contiene el 'unido falso ', y resolución de Rtiene éxito (dando String), entonces la invocación es aplicable.

Tipo de invocación inferencia , una vez más ...

Esta vez, el nuevo conjunto de restricciones, C , con asociados de entrada y de salida de las variables, es:

  • TypeInference::getLong es compatible con F
    • Las variables de entrada: F
    • Variables de salida: ninguno

Una vez más, no tenemos interdependencias entre entrada y salida variables. Sin embargo esta vez, no es una variable de entrada ( F), por lo que hay que resolver esto antes de intentar la reducción . Por lo tanto, comenzamos con nuestro conjunto cota B 2 .

  1. Determinamos un subconjunto Vde la siguiente manera:

    Dado un conjunto de variables de inferencia para resolver, dejar que Vsea la unión de este conjunto y todas las variables sobre las que la resolución de al menos una variable en este conjunto depende.

    Por la segunda atado en B 2 , la resolución de Fdepende de R, por lo V := {F, R}.

  2. Escogemos un subconjunto de Vacuerdo con la regla:

    deje que { α1, ..., αn }sea un subconjunto no vacío de variables sin instanciar en Vtal que i) para todos i (1 ≤ i ≤ n), si αidepende de la resolución de una variable β, entonces o bien βtiene una instanciación o hay alguna jtal que β = αj; y ii) existe ningún subconjunto propio no vacía de { α1, ..., αn }con esta propiedad.

    El único subconjunto de Vque satisface esta propiedad es {R}.

  3. Utilizando el tercer atado ( String <: R) creamos una instancia R = Stringe incorporar esto en nuestro conjunto de atado. RAhora se ha resuelto, y la segunda con destino efectivamente se convierte F <: Supplier<String>.

  4. Utilizando el (revisada) con destino segundos, creamos una instancia F = Supplier<String>. FAhora se ha resuelto.

Ahora que Fse ha resuelto, se puede proceder a la reducción , usando la nueva restricción:

  1. TypeInference::getLong es compatible con Supplier<String>
  2. ... reduce a Long es compatible con String
  3. ... que se reduce a falsa

... y obtenemos un error de compilación!


Notas adicionales sobre el 'Ejemplo Extended'

El Ejemplo extendido en las miradas de interrogación en algunos casos interesantes que no están cubiertos directamente por el funcionamiento anteriores:

  • Cuando el tipo de valor es un subtipo del tipo de retorno del método ( Integer <: Number)
  • Cuando la interfaz funcional es contravariant en el tipo inferido (es decir, Consumeren lugar de Supplier)

En particular, 3 de las invocaciones dadas destacan como potencialmente sugiriendo 'diferente' el comportamiento del compilador a la descrita en las explicaciones:

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

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

El segundo de estos 3 pasará a través de exactamente el mismo proceso de inferencia como withXanteriormente (basta con sustituir Longcon Numbery Stringcon Integer). Esto ilustra otra razón por la que no debería confiar en este comportamiento inferencia de tipos falló para el diseño de su clase, como la falta de recopilar aquí probable es no un comportamiento deseable.

Para el otro 2 (y de hecho cualquiera de los otros invocaciones que implican una Consumerque desea trabajar a través de), el comportamiento debe ser evidente si se trabaja mediante el procedimiento de la inferencia de tipos establecido para uno de los métodos anteriores (es decir, withpara la primera, withXpara la tercero). Sólo hay un pequeño cambio es necesario tomar nota de:

  • La restricción en el primer parámetro ( t::setNumber es compatible con Consumer<R> ) se reducirá al R <: Numberlugar de Number <: Rcomo lo hace para Supplier<R>. Esto se describe en la documentación vinculado en reducción.

Lo dejo como ejercicio para el lector a la obra carfully a través de uno de los procedimientos anteriores, armados con este pedazo de conocimiento adicional, para demostrar a sí mismos exactamente por qué una invocación particular, hace o no se compila.

Supongo que te gusta

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