Domanda

http://leepoint.net/notes-java/data/expressions/22compareobjects.html

Si scopre che la definizione di uguali () non è banale;In effetti è moderatamente difficile farlo bene, soprattutto nel caso delle sottoclassi.Il migliore l'esame dei problemi è in Core Java di Horstmann Vol 1.

Se equals() deve essere sempre sovrascritto, qual è un buon approccio per non essere costretti a dover eseguire il confronto degli oggetti?Quali sono alcune buone alternative di "design"?

MODIFICARE:

Non sono sicuro che tutto questo stia andando come avevo previsto.Forse la domanda dovrebbe essere più simile a "Perché vorresti confrontare due oggetti?" In base alla sua risposta a questa domanda, esiste una soluzione alternativa al confronto?Non intendo una diversa implementazione di uguali.Voglio dire, non usare affatto l'uguaglianza.Penso che il punto chiave sia iniziare con questa domanda, perché dovresti confrontare due oggetti.

È stato utile?

Soluzione

  

Se equals () devono sempre essere sovrascritte,   allora che cosa è un buon approccio per non aver   viene accantonato in avere a che fare   confronto oggetto?

Ti sbagli. Si dovrebbe ignorare pari il meno possibile.


Tutte queste informazioni viene da Effective Java, Second Edition ( Josh Bloch ). Il primo capitolo edizione su questo è ancora disponibile come un .

Da Effective Java:

  

Il modo più semplice per evitare problemi è   Non eseguire l'override del metodo equals, in   qual caso ciascuna istanza della classe   è pari solo a se stesso.

Il problema con arbitrariamente sovrascrivendo eguali / hashCode è eredità. Alcuni uguale implementazioni avvocato testare in questo modo:

if (this.getClass() != other.getClass()) {
    return false; //inequal
}

In realtà, il href="http://www.eclipse.org/" rel="nofollow noreferrer"> Eclipse (3.4) editor di Liskov principio di sostituzione .

Da Effective Java:

  

Non v'è alcun modo per estendere una   classe creare istanze e aggiungere un valore   componente preservando le pari   contratto.

Due modi per ridurre al minimo i problemi di parità sono descritti in classi e interfacce capitolo:

  1. composizione favore per l'eredità
  2. Progettazione e documento per eredità o altrimenti vietare

Per quanto posso vedere, l'unica alternativa è quella di testare l'uguaglianza in una forma esterna alla classe, e come tale sarebbe stata eseguita dipenderà dalla progettazione del tipo e del contesto che stavi cercando di usarlo in.

Per esempio, si potrebbe definire un'interfaccia che documenta come era da confrontare. Nel codice di seguito, le istanze di servizio potrebbero essere sostituiti in fase di esecuzione con una versione più recente della stessa classe - in questo caso, avendo diversi ClassLoader, uguale confronti sarebbe sempre return false, in modo prioritario è uguale a / hashCode sarebbe ridondante

.
public class Services {

    private static Map<String, Service> SERVICES = new HashMap<String, Service>();

    static interface Service {
        /** Services with the same name are considered equivalent */
        public String getName();
    }

    public static synchronized void installService(Service service) {
        SERVICES.put(service.getName(), service);
    }

    public static synchronized Service lookup(String name) {
        return SERVICES.get(name);
    }
}

  

"Perché si vuole confrontare due oggetti?"

L'esempio ovvio è quello di verificare se due stringhe sono uguali (o due File o URI ). Per esempio, che se si voleva costruire un insieme di file da analizzare. Per definizione, il set contiene solo elementi unici. Impostare tipo si basa su di uguale / metodi hashCode per far rispettare l'unicità dei suoi elementi.

Altri suggerimenti

Non credo che sia vero che eguali dovrebbero sempre essere sovrascritti. La regola se ho capito bene è che prioritario è uguale è significativa solo nei casi in cui siete chiare su come definire gli oggetti semanticamente equivalenti. In questo caso, è l'override hashCode (), come pure in modo che non si dispone di oggetti che hai definito come equivalenti di ritorno codici hash diversi.

Se non è possibile definire l'equivalenza di significato, non vedo il vantaggio.

Come circa appena farlo bene?

Ecco il mio uguale modello che è conoscenza applicata dal Effective Java da Josh Bloch. Leggi il libro per maggiori dettagli:

@Override
public boolean equals(Object obj) {
    if(this == obj) {
        return true;
    }

    // only do this if you are a subclass and care about equals of parent
    if(!super.equals(obj)) {
        return false;
    }
    if(obj == null || getClass() != obj.getClass()) {
        return false;
    }
    final YourTypeHere other = (YourTypeHere) obj;
    if(!instanceMember1.equals(other.instanceMember1)) {
       return false;
     }
     ... rest of instanceMembers in same pattern as above....
     return true;
 }

Mmhh

In alcuni casi è possibile effettuare l'immodificabile oggetto (in sola lettura) e lo hanno creato da un unico punto (un metodo di fabbrica)

Se sono necessari due oggetti con gli stessi dati di input (parametri di creazione) la fabbrica tornerà lo stesso arbitro per istanza e quindi utilizzando "==" sarebbe sufficiente.

Questo approccio è utile in solo determinate circostanze. E il più delle volte apparirebbe eccessivo.

Date un'occhiata a questa risposta sapere come implementare una cosa così.

Attenzione si tratta di un sacco di codice

Per brevi vedere come funziona classe wrapper dal momento che Java 1.5

Integer a = Integer.valueOf( 2 );
Integer b = Integer.valueOf( 2 );

a == b 

è vero, mentre

new Integer( 2 ) == new Integer( 2 )  

è falso.

Si mantiene internamente il riferimento e restituirlo se il valore di ingresso è la stessa.

Come sapete Integer è di sola lettura

Qualcosa di simile accade con la classe String da cui quella domanda era circa.

Forse mi manca il punto, ma l'unica ragione per usare uguali in contrapposizione a definire un proprio metodo con un nome diverso è perché molte delle collezioni (e probabilmente altre cose nel JDK o come si chiama in questi giorni) si aspettano il metodo equals per definire un risultato coerente. Ma oltre a questo, mi viene in mente di tre tipi di confronti che si desidera fare in eguali:

  1. I due oggetti sono davvero la stessa istanza. Questo non ha senso utilizzare è uguale perché è possibile utilizzare ==. Inoltre, e mi corregga se ho dimenticato come funziona in Java, il valore predefinito è uguale metodo fa questo utilizzando i codici hash generati automaticamente.
  2. I due oggetti hanno riferimenti alle stesse istanze, ma non sono la stessa istanza. Ciò è utile, uh, a volte ... soprattutto se vengono mantenute oggetti e si riferiscono allo stesso oggetto nel DB. Si dovrà definire il metodo equals per fare questo.
  3. I due oggetti hanno riferimenti a oggetti che sono uguali in valore, anche se possono o non possono essere le stesse istanze (in altre parole, si confrontano i valori tutto il percorso attraverso la gerarchia).

Perché si vuole confrontare due oggetti? Beh, se sono uguali, si vuole fare una cosa, e se non lo sono, si vuole fare qualcosa di diverso.

Detto questo, dipende dal caso in questione.

Il motivo principale per sovrascrivere equals () nella maggior parte dei casi è quello di controllare i duplicati all'interno di alcune collezioni. Per esempio, se si desidera utilizzare un set per contenere un oggetto che avete creato è necessario sovrascrivere equals () e hashCode () all'interno del vostro oggetto. Lo stesso vale se si desidera utilizzare il vostro oggetto personalizzato come una chiave in una mappa.

Questo è fondamentale, come ho visto molte persone fanno l'errore in pratica di aggiungere i loro oggetti personalizzati per Imposta o mappe senza sostituire equals () e hashCode (). La ragione per cui questo può essere particolarmente insidioso è il compilatore non si lamenterà e si può finire con più oggetti che contengono gli stessi dati, ma hanno diversi riferimenti in una collezione che non consente duplicati.

Per esempio, se si ha un semplice chicco chiamato NameBean con un singolo attributo String 'nome', si potrebbe costruire due istanze di NameBean (ad esempio nome1 e nome2), ciascuno con lo stesso 'nome' attributo value (ad esempio "Alice" ). Si potrebbe quindi aggiungere sia nome1 e nome2 per un set e il set sarebbe formato 2 piuttosto che taglia 1, che è ciò che è destinato. Allo stesso modo se si dispone di una mappa come mappa, al fine di mappare il nome di fagioli a qualche altro oggetto, e per la prima volta mappato nome1 alla stringa "prima" e poi mappato nome2 alla stringa "secondo" si avrà entrambe le coppie chiave / valore nella mappa (ad es nome1 -> "prima", nome2 -> "secondo"). Così, quando si fa una mappa occhiata verrà restituito il valore mappato al riferimento esatto si passa, che è o nome1, nome2, o un altro riferimento con il nome di "Alice" che restituirà null.

Ecco un esempio concreto preceduto dall'uscita del eseguirlo:

Output:

Adding duplicates to a map (bad):
Result of map.get(bean1):first
Result of map.get(bean2):second
Result of map.get(new NameBean("Alice"): null

Adding duplicates to a map (good):
Result of map.get(bean1):second
Result of map.get(bean2):second
Result of map.get(new ImprovedNameBean("Alice"): second

Codice:

// This bean cannot safely be used as a key in a Map
public class NameBean {
    private String name;
    public NameBean() {
    }
    public NameBean(String name) {
        this.name = name;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    @Override
    public String toString() {
        return name;
    }
}

// This bean can safely be used as a key in a Map
public class ImprovedNameBean extends NameBean {
    public ImprovedNameBean(String name) {
        super(name);
    }
    @Override
    public boolean equals(Object obj) {
        if (this == obj) {
            return true;
        }
        if(obj == null || getClass() != obj.getClass()) {
            return false;
        }
        return this.getName().equals(((ImprovedNameBean)obj).getName());
    }
    @Override
    public int hashCode() {
        return getName().hashCode();
    }
}

public class MapDuplicateTest {
    public static void main(String[] args) {
        MapDuplicateTest test = new MapDuplicateTest();
        System.out.println("Adding duplicates to a map (bad):");
        test.withDuplicates();
        System.out.println("\nAdding duplicates to a map (good):");
        test.withoutDuplicates();
    }
    public void withDuplicates() {
        NameBean bean1 = new NameBean("Alice");
        NameBean bean2 = new NameBean("Alice");

        java.util.Map<NameBean, String> map
                = new java.util.HashMap<NameBean, String>();
        map.put(bean1, "first");
        map.put(bean2, "second");
        System.out.println("Result of map.get(bean1):"+map.get(bean1));
        System.out.println("Result of map.get(bean2):"+map.get(bean2));
        System.out.println("Result of map.get(new NameBean(\"Alice\"): "
                + map.get(new NameBean("Alice")));
    }
    public void withoutDuplicates() {
        ImprovedNameBean bean1 = new ImprovedNameBean("Alice");
        ImprovedNameBean bean2 = new ImprovedNameBean("Alice");

        java.util.Map<ImprovedNameBean, String> map
                = new java.util.HashMap<ImprovedNameBean, String>();
        map.put(bean1, "first");
        map.put(bean2, "second");
        System.out.println("Result of map.get(bean1):"+map.get(bean1));
        System.out.println("Result of map.get(bean2):"+map.get(bean2));
        System.out.println("Result of map.get(new ImprovedNameBean(\"Alice\"): "
                + map.get(new ImprovedNameBean("Alice")));
    }
}

L'uguaglianza è fondamentale per la logica (vedi legge dell'identità) e non c'è molta programmazione di cui puoi fare a meno.Per quanto riguarda il confronto delle istanze delle classi che scrivi, beh dipende da te.Se devi riuscire a trovarli nelle raccolte o utilizzarli come chiavi in ​​Maps, avrai bisogno di controlli di uguaglianza.

Se hai scritto più di qualche libreria non banale in Java, saprai che l'uguaglianza è difficile da ottenere, specialmente quando gli unici strumenti a disposizione sono equals E hashCode.L’uguaglianza finisce per essere strettamente collegata alle gerarchie di classi, il che rende il codice fragile.Inoltre, non viene fornito alcun controllo del tipo poiché questi metodi accettano solo parametri di tipo Object.

Esiste un modo per rendere il controllo (e l'hashing) di uguaglianza molto meno soggetto a errori e più sicuro per i tipi.Nel Java funzionale biblioteca, troverai Equal<A> (e un corrispondente Hash<A>) dove l'uguaglianza è disaccoppiata in un'unica classe.Ha metodi per comporre Equal istanze per le tue classi da istanze esistenti, nonché wrapper per raccolte, Iterabili, HashMap, E HashSet, quell'uso Equal<A> E Hash<A> invece di equals E hashCode.

La cosa migliore di questo approccio è che non puoi mai dimenticare di scrivere il metodo equals e hash quando sono richiesti.Il sistema di tipi ti aiuterà a ricordare.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top