Pregunta

¿Por qué no es Colección.eliminar(Objeto o) ¿genérico?

Parece Collection<E> podría tener boolean remove(E o);

Luego, cuando intentas eliminar accidentalmente (por ejemplo) Set<String> en lugar de cada cadena individual de un Collection<String>, sería un error en el momento de la compilación en lugar de un problema de depuración posterior.

¿Fue útil?

Solución

Josh Bloch y Bill Pugh se refieren a este tema en Rompecabezas de Java IV:La amenaza de referencia fantasma, el ataque del clon y la venganza del turno.

Josh Bloch dice (6:41) que intentaron generar el método GET de mapa, eliminar el método y algún otro, pero "simplemente no funcionó".

Hay demasiados programas razonables que no podrían generarse si solo permite el tipo genérico de la colección como tipo de parámetro.El ejemplo dado por él es una intersección de una List de Numbers y unList de Longs.

Otros consejos

remove() (en Map así como en Collection) no es genérico porque debería poder pasar cualquier tipo de objeto a remove().El objeto eliminado no tiene que ser del mismo tipo que el objeto al que se pasa remove();sólo requiere que sean iguales.De la especificación de remove(), remove(o) elimina el objeto e tal que (o==null ? e==null : o.equals(e)) es true.Tenga en cuenta que no hay nada que requiera o y e ser del mismo tipo.Esto se desprende del hecho de que el equals() El método toma en cuenta un Object como parámetro, no solo del mismo tipo que el objeto.

Aunque puede ser cierto que muchas clases tienen equals() definido de modo que sus objetos sólo puedan ser iguales a los objetos de su propia clase, ciertamente ese no es siempre el caso.Por ejemplo, la especificación de List.equals() dice que dos objetos Lista son iguales si ambos son Listas y tienen el mismo contenido, incluso si son implementaciones diferentes de List.Entonces, volviendo al ejemplo de esta pregunta, es posible tener una Map<ArrayList, Something> y para que yo llame remove() con un LinkedList como argumento, y debería eliminar la clave, que es una lista con el mismo contenido.Esto no sería posible si remove() eran genéricos y restringían su tipo de argumento.

Porque si su parámetro de tipo es un comodín, no puede utilizar un método de eliminación genérico.

Creo recordar haberme encontrado con esta pregunta con el método get(Object) de Map.El método get en este caso no es genérico, aunque es razonable esperar que se le pase un objeto del mismo tipo que el primer parámetro de tipo.Me di cuenta de que si pasas Maps con un comodín como primer parámetro de tipo, entonces no hay forma de obtener un elemento de Map con ese método, si ese argumento era genérico.Los argumentos comodín realmente no se pueden satisfacer porque el compilador no puede garantizar que el tipo sea correcto.Especulo que la razón por la que agregar es genérico es que se espera que garantice que el tipo sea correcto antes de agregarlo a la colección.Sin embargo, al eliminar un objeto, si el tipo es incorrecto, no coincidirá con nada de todos modos.Si el argumento fuera un comodín, el método simplemente sería inutilizable, aunque tengas un objeto que puedas GARANTIZAR que pertenece a esa colección, porque acabas de obtener una referencia a él en la línea anterior....

Probablemente no lo expliqué muy bien, pero me parece bastante lógico.

Además de las otras respuestas, hay otra razón por la cual el método debería aceptar una Object, que son predicados.Considere el siguiente ejemplo:

class Person {
    public String name;
    // override equals()
}
class Employee extends Person {
    public String company;
    // override equals()
}
class Developer extends Employee {
    public int yearsOfExperience;
    // override equals()
}

class Test {
    public static void main(String[] args) {
        Collection<? extends Person> people = new ArrayList<Employee>();
        // ...

        // to remove the first employee with a specific name:
        people.remove(new Person(someName1));

        // to remove the first developer that matches some criteria:
        people.remove(new Developer(someName2, someCompany, 10));

        // to remove the first employee who is either
        // a developer or an employee of someCompany:
        people.remove(new Object() {
            public boolean equals(Object employee) {
                return employee instanceof Developer
                    || ((Employee) employee).company.equals(someCompany);
        }});
    }
}

El punto es que el objeto que se pasa al remove El método es responsable de definir el equals método.La construcción de predicados se vuelve muy sencilla de esta manera.

Supongamos que uno tiene una colección de Cat, y algunas referencias a objetos de tipos Animal, Cat, SiameseCat, y Dog.Preguntar a la colección si contiene el objeto al que se refiere el Cat o SiameseCat La referencia parece razonable.Preguntar si contiene el objeto al que se refiere el Animal La referencia puede parecer poco fiable, pero sigue siendo perfectamente razonable.Después de todo, el objeto en cuestión podría ser un Cat, y podría aparecer en la colección.

Además, incluso si el objeto resulta ser algo distinto de un Cat, no hay problema en decir si aparece en la colección; simplemente responda "no, no aparece".Una colección de "estilo de búsqueda" de algún tipo debería poder aceptar de manera significativa referencias de cualquier supertipo y determinar si el objeto existe dentro de la colección.Si la referencia del objeto pasado es de un tipo no relacionado, no hay forma de que la colección pueda contenerlo, por lo que la consulta en cierto sentido no es significativa (siempre responderá "no").No obstante, dado que no hay ninguna forma de restringir los parámetros a subtipos o supertipos, es más práctico simplemente aceptar cualquier tipo y responder "no" para cualquier objeto cuyo tipo no esté relacionado con el de la colección.

Siempre pensé que esto se debía a que remove() no tiene motivos para preocuparse por el tipo de objeto que le das.De todos modos, es bastante fácil verificar si ese objeto es uno de los que contiene la Colección, ya que puede llamar a equals() en cualquier cosa.Es necesario verificar el tipo en add() para asegurarse de que solo contenga objetos de ese tipo.

Remove no es un método genérico, por lo que el código existente que utiliza una colección no genérica aún se compilará y tendrá el mismo comportamiento.

Ver http://www.ibm.com/developerworks/java/library/j-jtp01255.html para detalles.

Editar:Un comentarista pregunta por qué el método add es genérico.[...eliminó mi explicación...] El segundo comentarista respondió la pregunta de firebird84 mucho mejor que yo.

Otra razón es por las interfaces.Aquí hay un ejemplo para mostrarlo:

public interface A {}

public interface B {}

public class MyClass implements A, B {}

public static void main(String[] args) {
   Collection<A> collection = new ArrayList<>();
   MyClass item = new MyClass();
   collection.add(item);  // works fine
   B b = item; // valid
   collection.remove(b); /* It works because the remove method accepts an Object. If it was generic, this would not work */
}

Porque rompería el código existente (anterior a Java5).p.ej.,

Set stringSet = new HashSet();
// do some stuff...
Object o = "foobar";
stringSet.remove(o);

Ahora se podría decir que el código anterior es incorrecto, pero supongamos que o proviene de un conjunto heterogéneo de objetos (es decir, contiene cadenas, números, objetos, etc.).Desea eliminar todas las coincidencias, lo cual era legal porque eliminar simplemente ignoraría las no cadenas porque no eran iguales.Pero si lo elimina (String o), eso ya no funciona.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top