¿Covarianza, invariancia y contravarianza explicadas en inglés simple?

Hoy, leo algunos artículos sobre Covarianza, Contravariancia (e Invarianza) en Java. Leí el artículo de Wikipedia en inglés y alemán, y algunas otras publicaciones de blog y artículos de IBM.

Pero todavía estoy un poco confundido acerca de qué se trata exactamente. Algunos dicen que se trata de la relación entre tipos y subtipos, algunos dicen que se trata de la conversión de tipos y algunos dicen que se usa para decidir si un método se anula o se sobrecarga.

Así que estoy buscando una explicación fácil en inglés simple, que muestre a un principiante lo que es la covarianza y la contradicción (y la invarianza). Un punto más para un ejemplo fácil.

Algunos dicen que se trata de la relación entre tipos y subtipos, otros dicen que se trata de la conversión de tipos y otros dicen que se usa para decidir si un método se sobreescribe o se sobrecarga.

Todas las anteriores.

En el fondo, estos términos describen cómo la relación de subtipo se ve afectada por las transformaciones de tipo. Es decir, si A y B son tipos, f es una transformación de tipo, y ≤ la relación de subtipo (es decir, A ≤ B significa que A es un subtipo de B ), tenemos

  • f es covariante si A ≤ B implica que f(A) ≤ f(B)
  • f es contravariante si A ≤ B implica que f(B) ≤ f(A)
  • f es invariante si ninguno de los anteriores sostiene

Consideremos un ejemplo. Deje f(A) = List donde List es declarado por

 class List { ... } 

¿Es f covariante, contravariante o invariante? Covariante significaría que List es un subtipo de List , contravariante que List es un subtipo de List e invariante que tampoco es un subtipo del otro, es decir, List y List son tipos inconvertibles. En Java, esto último es cierto, decimos (de manera un tanto informal) que los generics son invariables.

Otro ejemplo. Deje f(A) = A[] . ¿Es f covariante, contravariante o invariante? Es decir, ¿String [] es un subtipo de Object [], Object [] un subtipo de String [], o no es un subtipo de la otra? (Respuesta: en Java, las matrices son covariantes)

Esto todavía era bastante abstracto. Para hacerlo más concreto, veamos qué operaciones en Java se definen en términos de la relación de subtipo. El ejemplo más simple es la asignación. La statement

 x = y; 

comstackrá solo si typeof(y) ≤ typeof(x) . Es decir, acabamos de enterarnos de que las declaraciones

 ArrayList strings = new ArrayList(); ArrayList objects = new ArrayList(); 

no se comstackrá en Java, pero

 Object[] objects = new String[1]; 

será.

Otro ejemplo donde importa la relación de subtipo es una expresión de invocación de método:

 result = method(a); 

Hablando informalmente, esta afirmación se evalúa asignando el valor de a al primer parámetro del método, luego ejecutando el cuerpo del método y luego asignando los métodos valor devuelto al result . Al igual que la asignación simple en el último ejemplo, el “lado derecho” debe ser un subtipo del “lado izquierdo”, es decir, esta afirmación solo puede ser válida si typeof(a) ≤ typeof(parameter(method)) y returntype(method) ≤ typeof(result) . Es decir, si el método es declarado por:

 Number[] method(ArrayList list) { ... } 

ninguna de las siguientes expresiones se comstackrá:

 Integer[] result = method(new ArrayList()); Number[] result = method(new ArrayList()); Object[] result = method(new ArrayList()); 

pero

 Number[] result = method(new ArrayList()); Object[] result = method(new ArrayList()); 

será.

Otro ejemplo donde la subtipificación importa es primordial. Considerar:

 Super sup = new Sub(); Number n = sup.method(1); 

dónde

 class Super { Number method(Number n) { ... } } class Sub extends Super { @Override Number method(Number n); } 

Informalmente, el tiempo de ejecución reescribirá esto para:

 class Super { Number method(Number n) { if (this instanceof Sub) { return ((Sub) this).method(n); // * } else { ... } } } 

Para la línea marcada a comstackr, el parámetro de método del método de anulación debe ser un supertipo del parámetro de método del método reemplazado, y el tipo de retorno es un subtipo del método reemplazado. Formalmente hablando, f(A) = parametertype(method asdeclaredin(A)) debe al menos ser contravariante, y si f(A) = returntype(method asdeclaredin(A)) debe al menos ser covariante.

Tenga en cuenta el “al menos” de arriba. Esos son los requisitos mínimos que exigirá cualquier lenguaje de progtwigción orientado a objetos seguro de tipo estático razonable, pero un lenguaje de progtwigción puede elegir ser más estricto. En el caso de Java 1.4, los tipos de parámetros y los tipos de retorno de método deben ser idénticos (excepto el borrado de tipo) cuando se anulan los métodos, es decir, tipo de parametertype(method asdeclaredin(A)) = parametertype(method asdeclaredin(B)) . Desde Java 1.5, se permiten los tipos de retorno covariantes cuando se anula, es decir, los siguientes se comstackrán en Java 1.5, pero no en Java 1.4:

 class Collection { Iterator iterator() { ... } } class List extends Collection { @Override ListIterator iterator() { ... } } 

Espero haber cubierto todo, o mejor dicho, haber arañado la superficie. Aún así, espero que ayude a comprender el concepto abstracto, pero importante, de la varianza de tipo.

Tomando el sistema tipo java, y luego las clases:

Cualquier objeto de algún tipo T puede ser sustituido por un objeto de subtipo de T.

VARIEDAD DE TIPO – LOS MÉTODOS DE CLASE TIENEN LAS SIGUIENTES CONSECUENCIAS

 class A { public S f(U u) { ... } } class B extends A { @Override public T f(V v) { ... } } B b = new B(); t = bf(v); A a = ...; // Might have type B s = af(u); // and then do V v = u; 

Puede observarse que:

  • El T debe ser el subtipo S ( covariante, ya que B es el subtipo de A ).
  • El V debe ser un supertipo de U ( contravariante , como contra dirección de herencia).

Ahora co-y contra-se refieren a que B es el subtipo de A. Los siguientes typings más fuertes pueden ser introducidos con conocimiento más específico. En el subtipo

La covarianza (disponible en Java) es útil, para decir que uno arroja un resultado más específico en el subtipo; especialmente visto cuando A = T y B = S. La contradicción dice que estás preparado para manejar un argumento más general.