2011-07-06 8 views
10

Tengo un problema al hacer que una ArrayList use correctamente un valor igual a anulado. el problema es que estoy tratando de usar los equivalentes para probar solo un campo clave único, y usar ArrayList.contains() para probar la existencia de un objeto con el campo correcto. Aquí hay un ejemploArrayList que no utiliza el reemplazado es igual a

public class TestClass { 
    private static class InnerClass{  
    private final String testKey; 
    //data and such 

    InnerClass(String testKey, int dataStuff) { 
     this.testKey =testKey; 
     //etc 
    } 
    @Override 
    public boolean equals (Object in) { 
     System.out.println("reached here"); 
     if(in == null) { 
     return false; 
     }else if(in instanceof String) { 
     String inString = (String) in; 
     return testKey == null ? false : testKey.equals(inString); 
     }else { 
     return false; 
     }  
    }  
    } 

    public static void main(String[] args) {  
    ArrayList<InnerClass> objectList = new ArrayList<InnerClass>(); 
    //add some entries 
    objectList.add(new InnerClass("UNIQUE ID1", 42)); 
    System.out.println(objectList.contains("UNIQUE ID1")); 
    }  
} 

Lo que me preocupa es que no sólo estoy recibiendo falsa en la salida, pero tampoco estoy poniendo la salida "llegado aquí".

¿Alguien tiene alguna idea de por qué esta anulación se ignora por completo? ¿Hay alguna sutileza con las anulaciones y las clases internas que no conozco?

Editar: Tengo problemas con el sitio, por lo que parece que no puedo marcar la respuesta. Gracias por la respuesta rápida: sí, un descuido de mi parte que es el String .equals que se llama, no el mío. Supongo que son cheques pasados ​​de moda por ahora

Respuesta

15

Si marca fuentes de ArrayList, se verá que se llama equals de otro objeto.En su caso se llamará equals de String "UNIQUE ID1" que comprobará que otro objeto no es del tipo String y sólo devuelve false:

public boolean contains(Object o) { 
    return indexOf(o) >= 0; 
} 

public int indexOf(Object o) { 
    ...  
    for (int i = 0; i < size; i++) 
    if (o.equals(elementData[i])) 
     return i; 
    ... 
    return -1; 
} 

Por su llamada caso contains con InnerClass que sólo contiene id:

objectList.contains(new InnerClass("UNIQUE ID1")) 

no se olvide de poner en práctica equals para InnerClass que compara id solamente.

+0

Gracias. Esa sugerencia sobre la nueva InnerClass() fue realmente útil también. :) – Sufian

+1

En mi opinión, la implementación de indexOf debería ser al revés: elementData [i] .equals (o) ... – marcolopes

0

Aunque no responde su pregunta, muchas Colecciones usan hashcode(). También debe anular eso para "estar de acuerdo" con equals().

En realidad, usted debe siempre implementar tanto equals y hashcode juntos, y siempre deben ser consistentes entre sí. A medida que el Javadoc para Object.equals() estados:

Tenga en cuenta que por lo general es necesario reemplazar el método hashCode siempre se anula este método, a fin de mantener el contrato general para el método hashCode, que establece que los objetos iguales deben tener códigos de hash iguales .

En concreto, muchas colecciones se basan en este contrato han sido consideradas válidas - comportamiento no está definido de otra manera.

+1

Si bien es cierto que debe implementar ambos, no ayuda en este caso específico, ya que 'ArrayList' no está de ninguna manera basado en hash: no le importa la implementación' hashCode() '. –

+0

'ArrayList' no utiliza' hashCode' –

+0

Bien. "Lamenté" mi respuesta. buenos puntos para ambos – Bohemian

3

Usted está invocando contains con un argumento que es un String y no un InnerClass:

System.out.println(objectList.contains("UNIQUE ID1")) 

En mi JDK:

public class ArrayList { 

    public boolean contains(Object o) { 
    return indexOf(o) >= 0; 
    } 

    public int indexOf(Object o) { 
    if (o == null) { 
     // omitted for brevity - aix 
    } else { 
     for (int i = 0; i < size; i++) 
     if (o.equals(elementData[i])) // <<<<<<<<<<<<<<<<<<<<<< 
      return i; 
    } 
    return -1; 
    } 
} 

Nota cómo indexOf llamadas o.equals(). En su caso, o es String, entonces su objectList.contains usará String.equals y no InnerClass.equals.

7

Según the JavaDoc of List.contains(o), se define para volver true

si y sólo si esta lista contiene al menos un elemento e tal que (o==null ? e==null : o.equals(e)).

Nótese que esta definición llama equals en o, que es el parámetro y no el elemento que se encuentra en la List.

Por lo tanto, se llamará String.equals() y no InnerClass.equals().

También tenga en cuenta que the contract for Object.equals() establece que

Es simétrica : para cualquier valor de referencia no nulos x y y, x.equals(y) debe devolver true si y sólo si y.equals(x) vuelve true.

Pero usted viola esta restricción, ya que los rendimientos new TestClass("foo", 1).equals("foo")true pero "foo".equals(new TestClass("foo", 1)) siempre devolverá false.

Desafortunadamente esto significa que su caso de uso (una clase personalizada que puede ser igual a otra clase estándar) no puede ser implementado de una manera totalmente conforme.

Si todavía quiere hacer algo como esto, usted tendrá que leer la especificación (y, a veces la aplicación) de todas las clases de colección muy cuidadosamente y compruebe si hay trampas como esta.

+0

Ha estado trabajando demasiado horas. Nunca es una buena señal cuando comienzas a olvidar lo básico o malinterpretar las especificaciones de la API. He encontrado la solución de tener un constructor de la nueva InnerClass (String testKey) para hacer un objeto de prueba, pero con los datos en realidad como null/0s. –

2

En general, es necesario también anular hashCode() pero esto no es el problema principal aquí. Está teniendo un método equals(..) asimétrico. Los documentos dejan claro que debe ser simétrica:

Es simétrica: para cualquier no nulos valores de referencia X e Y, x.equals (y) debe devolver verdadero si y sólo si y.equals (x) devuelve verdadero.

Y lo que observa es un comportamiento inesperado debido a un contrato roto.

Crear un método de utilidad que itera todos los artículos y verifica con equals(..) en la cadena:

public static boolean containsString(List<InnerClass> items, String str) { 
    for (InnerClass item : items) { 
     if (item.getTestKey().equals(str)) { 
      return true; 
     } 
    } 
    return false; 
} 

Usted puede hacer una cosa similar con el método de la guayaba Iterables.any(..):

final String str = "Foo"; 
boolean contains = Iterables.any(items, new Predicate<InnerClass>() { 
    @Override 
    public boolean apply(InnerClass input){ 
     return input.getTestKey().equals(str); 
    } 
} 
1

Su aplicación iguales es incorrecto . Su parámetro in no debe ser String. Debería ser un InnerClass.

public boolean equals(Object o) { 
    if (this == o) return true; 
    if (!(o instanceof InnerClass) return false; 
    InnerClass that = (InnerClass)o; 
    // check for null keys if you need to 
    return this.testKey.equals(that.testKey); 
} 

(Tenga en cuenta que instanceof null vuelve falsa, por lo que no es necesario para comprobar nula en primer lugar).

A continuación, comprobar la existencia de un equivalente objeto en su lista usando:

objectList.contains(new InnerClass("UNIQUE ID1")); 

Pero si realmente desea comprobar InnerClass por String key, por qué no usar Map<String,InnerClass> en su lugar?

+0

Para ser franco, han pasado unos 6 años desde la última vez que utilicé Java y aunque tengo la mentalidad de un codificador, me falta experiencia. Todavía no estoy completamente familiarizado con Maps o Java hashing desde que aprendí C originalmente. Usar Java ahora porque para el desarrollo de pequeñas aplicaciones/herramientas es mucho más rápido y necesito una independencia internacional y de plataforma decente. He agregado un constructor (y para algunas de mis otras clases que implementan un padre abstracto, una clase hermana) para hacer un testobjeto con solo la clave de prueba. –

0

Hay algunos problemas con su código. Mi sugerencia sería evitar anulando los iguales en su totalidad si no está familiarizado con él y extenderla en una nueva aplicación como tal ...

class MyCustomArrayList extends ArrayList<InnerClass>{ 

    public boolean containsString(String value){ 
     for(InnerClass item : this){ 
      if (item.getString().equals(value){ 
       return true; 
      } 
     } 
     return false; 
    } 

} 

entonces usted puede hacer algo como

List myList = new MyCustomArrayList() 
myList.containsString("some string"); 

Sugiero esto porque si anulas a los iguales también debes anular el código hash y parece que te falta un poco de conocimiento en esta área, así que simplemente lo evitaría.

Además, el método contains llama al método igual y es por eso que está viendo el "alcanzado aquí". De nuevo, si no entiendes el flujo de llamadas, simplemente lo evitaría.

+0

Entiendo el flujo de llamadas, pero olvidé que estaba usando String.equals en lugar de InnerClass.equals. Como ArrayList no está basado en hash, no vi la necesidad de incluir el hashCode en el fragmento. Naturalmente incluyo una anulación hashCode, incluso si todo lo que necesita aquí es devolver testKey.hashCode() –

0

en el otro sentido, se llama a su método igual si cambia su código de la siguiente manera. espero que esto borre el concepto.

package com.test; 

import java.util.ArrayList;  
import java.util.List; 

public class TestClass { 
    private static class InnerClass{  
     private final String testKey; 
     //data and such 

     InnerClass(String testKey, int dataStuff) { 
      this.testKey =testKey; 
      //etc 
     } 

     @Override 
     public boolean equals (Object in1) { 
      System.out.println("reached here"); 
      if(in1 == null) { 
       return false; 
      }else if(in1 instanceof InnerClass) { 
       return ((InnerClass) this).testKey == null ? false : ((InnerClass) this).testKey.equals(((InnerClass) in1).testKey); 
      }else { 
       return false; 
      }  
     }  
    } 

    public static void main(String[] args) {  
     ArrayList<InnerClass> objectList = new ArrayList<InnerClass>(); 
     InnerClass in1 = new InnerClass("UNIQUE ID1", 42); 
     InnerClass in2 = new InnerClass("UNIQUE ID1", 42); 

     //add some entries 
     objectList.add(in1); 
     System.out.println(objectList.contains(in2)); 
    }  
} 
0

Como muchos mensajes han dicho, el problema es que la función list.indexOf (obj) llama "es igual a" del obj, no a los elementos de la lista.

Tuve el mismo problema y "contains()" no me satisfizo, ya que necesito saber dónde está el elemento !. Mi enfoque es crear un elemento vacío con solo el parámetro para comparar, y luego llamar a indexOf.

Implementar una función como esta,

public static InnerClass empty(String testKey) { 
    InnerClass in = new InnerClass(); 
    in.testKey =testKey; 
    return in; 
} 

Y luego, llamar indexOf así:

ind position = list.indexOf(InnerClass.empty(key)); 
0

Hay dos errores en el código.

Primero: El método "contiene" llamado en el objeto "objectList" debe pasar un nuevo objeto InnerClass como parámetro.

Segundo: El método equals (debe aceptar el parámetro como Object, y es correcto) debe manejar el código correctamente de acuerdo con el objeto recibido. De esta manera:

@Override 
    public boolean equals (Object in) { 
     System.out.println("reached here"); 
     if(in == null) { 
     return false; 
     }else if(in instanceof InnerClass) { 
     String inString = ((InnerClass)in).testKey; 
     return testKey == null ? false : testKey.equals(inString); 
     }else { 
     return false; 
     }  
    } 
0

Este post fue escrito antes de Java 8 estaba disponible, pero ahora que es 2017 en lugar de utilizar el método las List.containts (...) se puede utilizar el nuevo Java 8 manera como esto:

System.out.println(objectList.stream().filter(obj -> obj.getTestKey().equals("UNIQUE ID1")).findAny().isPresent()); 

y darle a su TestClass un captador de su campo TestKey:

public String getTestKey() { 

    return testKey; 
} 

La ventaja de este enfoque es que usted no tiene que modificar los iguales o método de hash y te ¡Me veo como un jefe para tus compañeros!