www vectores una tipos salvajes revés pronunciacion pedir org número nombres multidimensionales mostrarlo mclibre matriz matrices las ingles hacer español entre ejercicios ejemplos domesticos declarar con como cifras bartolome arreglos array apuntes animales acuaticos 3x3 java generics oop covariance

java - vectores - ¿Alguna forma sencilla de explicar por qué no puedo hacer una lista de animales<animales>=nueva lista de matrices<perro>()?



vectores en java ejemplos (13)

Sé por qué uno no debería hacer eso. Pero ¿hay alguna manera de explicarle a un profano por qué esto no es posible? Puede explicar esto fácilmente a un profano: Animal animal = new Dog(); . Un perro es un tipo de animal, pero una lista de perros no es una lista de animales.


Al heredar, en realidad estás creando tipos comunes para varias clases. Aquí tienes un tipo de animal común. lo estás usando creando una matriz en tipo de Animal y conservando valores de tipos similares (tipos heredados perro, gato, etc.).

P.ej:

dim animalobj as new List(Animal) animalobj(0)=new dog() animalobj(1)=new Cat()

.......

¿Lo tengo?


Esto se debe a que los tipos genéricos son invariant .


Imagina que creas una lista de perros . A continuación, declara esto como Lista <Animal> y se lo pasa a un colega. Él, no sin razón , cree que puede poner un gato en él.

Luego te lo devuelve, y ahora tienes una lista de Perros , con un Gato en el medio. Caos sobreviene.

Es importante tener en cuenta que esta restricción existe debido a la mutabilidad de la lista. En Scala (por ejemplo), puede declarar que una lista de perros es una lista de animales . Esto se debe a que las listas de Scala son (de forma predeterminada) inmutables, por lo que agregar un Gato a una lista de Perros le daría una nueva lista de Animales .


La mejor respuesta que puedo dar es esta: porque al diseñar genéricos no quieren repetir la misma decisión que se hizo con el sistema de tipo de matriz de Java que lo hizo inseguro .

Esto es posible con matrices:

Object[] objArray = new String[] { "Hello!" }; objArray[0] = new Object();

Este código se compila muy bien debido a la forma en que el sistema de tipo de matriz funciona en Java. Levantaría una ArrayStoreException en tiempo de ejecución.

La decisión fue tomada para no permitir tal comportamiento inseguro para los genéricos.

Ver también en otro lugar: Java Arrays Break Type Safety , que muchos consideran uno de los defectos de diseño de Java .


La respuesta que está buscando tiene que ver con conceptos llamados covarianza y contravarianza. Algunos lenguajes son compatibles (.NET 4 agrega soporte, por ejemplo), pero algunos de los problemas básicos se demuestran con un código como este:

List<Animal> animals = new List<Dog>(); animals.Add(myDog); // works fine - this is a list of Dogs animals.Add(myCat); // would compile fine if this were allowed, but would crash!

Como Cat derivaría de un animal, una verificación en tiempo de compilación sugeriría que se puede agregar a List. Pero, en tiempo de ejecución, ¡no puedes agregar un gato a una lista de perros!

Entonces, aunque pueda parecer intuitivamente simple, estos problemas son en realidad muy complejos.

Hay una descripción general de MSDN de la co / contravariancia en .NET 4 aquí: http://msdn.microsoft.com/en-us/library/dd799517(VS.100).aspx ; también es aplicable a Java, aunque no lo hago. t saber cómo es el soporte de Java.


Lo que estás tratando de hacer es lo siguiente:

List<? extends Animal> animals = new ArrayList<Dog>()

Eso debería funcionar.


Primero, definamos nuestro reino animal:

interface Animal { } class Dog implements Animal{ Integer dogTag() { return 0; } } class Doberman extends Dog { }

Considere dos interfaces parametrizadas:

interface Container<T> { T get(); } interface Comparator<T> { int compare(T a, T b); }

Y las implementaciones de estos donde T es Dog .

class DogContainer implements Container<Dog> { private Dog dog; public Dog get() { dog = new Dog(); return dog; } } class DogComparator implements Comparator<Dog> { public int compare(Dog a, Dog b) { return a.dogTag().compareTo(b.dogTag()); } }

Lo que está preguntando es bastante razonable en el contexto de esta interfaz de Container :

Container<Dog> kennel = new DogContainer(); // Invalid Java because of invariance. // Container<Animal> zoo = new DogContainer(); // But we can annotate the type argument in the type of zoo to make // to make it co-variant. Container<? extends Animal> zoo = new DogContainer();

Entonces, ¿por qué Java no hace esto automáticamente? Considere lo que esto significaría para Comparator .

Comparator<Dog> dogComp = new DogComparator(); // Invalid Java, and nonsensical -- we couldn''t use our DogComparator to compare cats! // Comparator<Animal> animalComp = new DogComparator(); // Invalid Java, because Comparator is invariant in T // Comparator<Doberman> dobermanComp = new DogComparator(); // So we introduce a contra-variance annotation on the type of dobermanComp. Comparator<? super Doberman> dobermanComp = new DogComparator();

Si Java permitiera automáticamente que Container<Dog> se asignara a Container<Animal> , también se esperaría que un Comparator<Dog> pudiera asignar a un Comparator<Animal> , lo que no tiene sentido, ¿cómo podría un Comparator<Dog> comparar dos gatos?

Entonces, ¿cuál es la diferencia entre Container y Comparator ? El contenedor produce valores del tipo T , mientras que el Comparator consume . Estos corresponden a usos covariantes y contravariantes del parámetro tipo.

En ocasiones, el parámetro tipo se usa en ambas posiciones, lo que hace que la interfaz sea invariable .

interface Adder<T> { T plus(T a, T b); } Adder<Integer> addInt = new Adder<Integer>() { public Integer plus(Integer a, Integer b) { return a + b; } }; Adder<? extends Object> aObj = addInt; // Obscure compile error, because it there Adder is not usable // unless T is invariant. //aObj.plus(new Object(), new Object());

Por razones de compatibilidad con versiones anteriores, Java se predetermina a la invarianza . Debe elegir explícitamente la varianza adecuada con ? extends X ? extends X o ? super X ? super X en los tipos de variables, campos, parámetros o resultados del método.

Esto es una verdadera molestia: cada vez que alguien usa un tipo genérico, ¡deben tomar esta decisión! Seguramente los autores de Container and Comparator deberían ser capaces de declarar esto de una vez por todas.

Esto se llama ''Declaración de varianza del sitio'' y está disponible en Scala.

trait Container[+T] { ... } trait Comparator[-T] { ... }


Si no pudieras mutar la lista, tu razonamiento sería perfectamente correcto. Desafortunadamente una List<> es manipulada imperativamente. Lo que significa que puede cambiar una List<Animal> añadiéndole un Animal nuevo. Si se le permitiera usar una List<Dog> como una List<Animal> podría terminar con una lista que también contiene un Cat .

Si List<> fuera incapaz de mutar (como en Scala), entonces podría tratar A List<Dog> como List<Animal> . Por ejemplo, C # hace que este comportamiento sea posible con argumentos de tipo genéricos covariantes y contravariantes.

Esta es una instancia del principio de sustitución de Liskov más general.

El hecho de que la mutación te cause un problema aquí sucede en otros lugares. Considere los tipos Square y Rectangle .

¿Es un Square un Rectangle ? Ciertamente, desde una perspectiva matemática.

Podría definir una clase Rectangle que ofrezca getWidth legibles getWidth y getHeight .

Incluso podría agregar métodos que calculen su area o perimeter , según esas propiedades.

Luego puede definir una clase Square que subclasifique Rectangle y haga que tanto getWidth como getHeight devuelvan el mismo valor.

Pero, ¿qué sucede cuando comienzas a permitir la mutación a través de setWidth o setHeight ?

Ahora, Square ya no es una subclase razonable de Rectangle . La mutación de una de esas propiedades tendría que cambiar silenciosamente la otra para mantener la invariante, y se violaría el principio de sustitución de Liskov. Cambiar el ancho de un Square tendría un efecto secundario inesperado. Para seguir siendo un cuadrado, deberías cambiar la altura también, ¡pero solo pediste cambiar el ancho!

No puedes usar tu Square siempre que puedas haber usado un Rectangle . Entonces, en presencia de una mutación, ¡ un Square no es un Rectangle !

Podría hacer un nuevo método en Rectangle que sepa cómo clonar el rectángulo con un nuevo ancho o una nueva altura, y luego su Square podría ceder de forma segura a un Rectangle durante el proceso de clonación, pero ahora ya no está mutando el valor original.

De manera similar, una List<Dog> no puede ser una List<Animal> cuando su interfaz le permite agregar nuevos elementos a la lista.


Supongamos que pudieras hacer esto. Una de las cosas que alguien entregó una List<Animal> razonablemente podría esperar que sea capaz de agregarle una Giraffe . ¿Qué debería pasar cuando alguien intenta agregar una Giraffe a los animals ? ¿Un error de tiempo de ejecución? Eso parecería frustrar el propósito del tipeo en tiempo de compilación.


Tenga en cuenta que si tiene

List<Dog> dogs = new ArrayList<Dog>()

entonces, si pudieras hacer

List<Animal> animals = dogs;

esto no convierte a los dogs en una List<Animal> . La estructura de datos subyacente a los animales sigue siendo un ArrayList<Dog> , así que si tratas de insertar un Elephant en animals , en realidad lo estás insertando en un ArrayList<Dog> que no va a funcionar (el Elephant es obviamente demasiado grande; -).


Una Lista <Animal> es un objeto donde puede insertar cualquier animal, por ejemplo, un gato o un pulpo. Un ArrayList <perro> no lo es.


Yo diría que la respuesta más simple es ignorar a los gatos y perros, no son relevantes. Lo importante es la lista en sí misma.

List<Dog>

y

List<Animal>

son diferentes tipos, que Dog deriva de Animal no tiene nada que ver con esto.

Esta declaración no es válida

List<Animal> dogs = new List<Dog>();

por la misma razón que esta es

AnimalList dogs = new DogList();

Mientras Dog puede heredar de Animal, la clase de lista generada por

List<Animal>

no hereda de la clase de lista generada por

List<Dog>

Es un error suponer que debido a que dos clases están relacionadas, el usarlas como parámetros genéricos hará que esas clases genéricas también se relacionen. Si bien podría agregar un perro a un

List<Animal>

eso no implica que

List<Dog>

es una subclase de

List<Animal>


Respuesta en inglés:

Si '' List<Dog> es una List<Animal> '', el primero debe soportar (heredar) todas las operaciones de este último. Agregar un gato se puede hacer a este último, pero no antes. Entonces la relación ''es una'' falla.

Respuesta de programación:

Tipo de seguridad

Una opción de diseño predeterminado de lenguaje conservador que detiene esta corrupción:

List<Dog> dogs = new List<>(); dogs.add(new Dog("mutley")); List<Animal> animals = dogs; animals.add(new Cat("felix")); // Yikes!! animals and dogs refer to same object. dogs now contains a cat!!

Para tener una relación de subtipo, debe mejorar los criterios de ''fundibilidad'' / ''subsistencia''.

  1. Substición de objeto legal: todas las operaciones de antepasado compatibles con descendencia:

    // Legal - one object, two references (cast to different type) Dog dog = new Dog(); Animal animal = dog;

  2. Sustitución de colección legal: todas las operaciones de antepasado compatibles con descendientes:

    // Legal - one object, two references (cast to different type) List<Animal> list = new List<Animal>() Collection<Animal> coll = list;

  3. Sustitución genérica ilegal (elenco del parámetro tipo) - operaciones no respaldadas en descendencia:

    // Illegal - one object, two references (cast to different type), but not typesafe List<Dog> dogs = new List<Dog>() List<Animal> animals = list; // would-be ancestor has broader ops than decendant

sin embargo

Dependiendo del diseño de la clase genérica, los parámetros de tipo pueden usarse en "posiciones seguras", lo que significa que la conversión / sustitución a veces puede tener éxito sin corromper la seguridad del tipo. Covarianza significa que la instalación genérica G<U> puede sustituir a G<T> si U es un mismo tipo o subtipo de T. Contravariancia significa que la instancia genérica G<U> puede G<U> sustituir G<T> si U es un mismo tipo o supertipo de T. Estas son las posiciones seguras para los 2 casos:

  • posiciones covariantes:

    • método de devolución tipo (salida de tipo genérico): los subtipos deben ser igualmente / más restrictivos, por lo que sus tipos de devolución cumplen con ancestro
    • tipo de campos inmutables (establecidos por clase de propietario, luego ''internamente solo salida''): los subtipos deben ser más restrictivos, por lo que cuando establecen campos inmutables, cumplen con el antecesor

    En estos casos, es seguro permitir la sustituibilidad de un parámetro de tipo con una descendencia como esta:

    SomeCovariantType<Dog> decendant = new SomeCovariantType<>; SomeCovariantType<? extends Animal> ancestor = decendant;

    El comodín más ''extensiones'' proporciona la covarianza especificada en el sitio de uso.

  • posiciones contributivas:

    • Método tipo de parámetro (entrada al tipo genérico): los subtipos deben ser igualmente / más acomodaticios para que no se rompan cuando pasan los parámetros del ancestro
    • límites de parámetros de tipo superior (instanciación de tipo interno): los subtipos deben ser igualmente / más adaptables, por lo que no se rompen cuando los antepasados ​​establecen valores de variables

    En estos casos, es seguro permitir la sustituibilidad de un parámetro de tipo con un antecesor como este:

    SomeContravariantType<Animal> decendant = new SomeContravariantType<>; SomeContravariantType<? super Dog> ancestor = decendant;

    El comodín más ''super'' da una contravariancia especificada en el sitio de uso.

El uso de estos 2 idiomas requiere un esfuerzo extra y el cuidado del desarrollador para obtener ''poder de sustitución''. Java requiere un esfuerzo de desarrollador manual para garantizar que los parámetros de tipo se usen realmente en posiciones covariantes / contravariantes, respectivamente (de ahí que sean seguros para el tipo). No sé por qué, por ejemplo, el compilador scala comprueba esto: - /. Básicamente le estás diciendo al compilador "créeme, sé lo que estoy haciendo, esto es seguro".

  • posiciones invariables

    • tipo de campo mutable (entrada y salida interna): puede leerse y escribirse con todas las clases de ancestros y subtipos; la lectura es covariante, la escritura es contravariante; el resultado es invariante
    • (también si el parámetro de tipo se usa tanto en posiciones covariantes como contravariantes, entonces esto da como resultado la invarianza)