Come definire quali campi controllare per l'uguaglianza?

2

Ho una strana domanda concettuale, non si tratta di un incidente specifico, ma solo di un approccio generale alle migliori pratiche.

Mi sono chiesto talvolta di definire i metodi java equal, ciò che rende uguali due oggetti. L'approccio semplice è quello di controllare piuttosto che ogni campo è uguale, tuttavia, ci sono occasioni in cui questo non è sempre logico. Ecco tre esempi a cui potrei pensare quando non sono sicuro che un campo dovrebbe essere parte di un metodo di uguaglianza:

  1. campi transitori e dati di stato. Ad esempio, qualsiasi campo transitorio su un oggetto JPA. oppure dire che ho una classe plugin con una posizione jar e alcuni altri dati di identificazione per definire in modo univoco dove trovare un plugin da caricare, e un transient isLoaded Boolean che mi dice piuttosto che ho già provato a caricare il plugin. Dovrebbero essere uguali due riferimenti allo stesso plugin ma con variabili isLoaded differenti?

  2. Costosi controlli di uguaglianza. Diciamo che ho una persona con un array di membri della famiglia. Se controllo tutti nell'array dei membri di famiglia, devo controllare tutti nel loro array ecc. E improvvisamente il mio metodo di uguaglianza controlla ogni persona in memoria, e deve controllare i cicli ecc.

  3. Dati che potrebbero non essere caricati al momento del controllo di uguaglianza. Diciamo che ho un oggetto JPA composto da due soli campi, un nome univoco e un campo UUID generato dal database. Il nome identifica in modo univoco la riga del database, ma l'UUID esiste solo quando carico dal database. Come posso verificare se un oggetto che voglio salvare nel database è uguale a quelli che ho già caricato nel database, il fatto che un oggetto non abbia l'UUID generato dal database li rende unici, ecc? / p>

Mi rendo conto che a volte dipende da come prevedi di utilizzare il metodo di qualità. Tuttavia, in situazioni in cui stai scrivendo un oggetto e non sei ancora sicuro di come verrà utilizzato, come decidi cosa includere in un metodo uguale? Non ne sovrascrivi mai uno se non puoi includere tutti i campi nel controllo?

Se hai bisogno che la tua funzione di hash si comporti in un certo modo, che indirettamente definisce il modo in cui il tuo metodo di uguaglianza dovrebbe funzionare, ma qualcuno potrebbe facilmente assumere una diversa definizione di uguaglianza significa che devi semplicemente annotare bene il tuo metodo di parità, o è un odore di codice che hai fatto qualcosa di sbagliato?

    
posta dsollen 19.10.2015 - 18:23
fonte

2 risposte

3

Penso che se ci fosse un modo unico per confrontare gli oggetti, allora Java (o qualsiasi altra lingua) lo renderebbe il default, no?

L'uguaglianza - come l'hashing e il confronto - dovrebbe essere veloce, semplice e, soprattutto, referenzialmente trasparente.

Alla fine, la domanda su cosa sia esattamente il metodo equals è importante. Se non lo sai, lascia semplicemente così com'è. Il confronto fisico ha senso in molti casi d'uso, in particolare se si ha il controllo su quando gli oggetti vengono creati rispetto a quando vengono riutilizzati.

Inoltre, piuttosto che cercare di progettare funzioni di confronto significative per tutti i casi, scrivere codice usando cose come Comparator e lascia che il codice di chiamata inietti ciò che ha più senso nel sito di chiamata.

    
risposta data 19.10.2015 - 19:21
fonte
3

Prima di tutto, ci sono due tipi di classi:

  • Classi con semantica "valore-tipo"
  • Classi con semantica "di riferimento"

Puoi utilizzare questi termini su Google per trovare informazioni su ciò che intendono esattamente e su come differiscono tra loro, per la lingua che preferisci.

Ogni singola classe con semantica "valore-tipo" deve avere un metodo equals() , e questo metodo deve prendere in considerazione ogni singolo suo campo, o almeno dare l'illusione che lo faccia. Se è costoso, morde il proiettile e spendi generosamente il ciclo dell'orologio, perché è necessario .

Le classi con semantica "di riferimento" non hanno bisogno di un metodo equals() . Non direi che non dovrebbe avere un tale metodo, ma se ne devono avere uno, dovrebbe essere pensato come un metodo di utilità / aiuto che verrà usato in qualche modo strano che non è correlato all'intenzione originale di equals . Questo perché in circostanze normali, questi oggetti vengono confrontati per riferimento , non in base al valore.

L'uguaglianza di riferimento viene controllata usando l'operatore == in Java, mentre in C #, dove l'operatore == può essere sovraccaricato, usiamo System.Object.ReferenceEquals() . (Anche se, ancora una volta, non dovresti sovraccaricare l'operatore == di una classe semantica di riferimento.)

Per inciso, i tipi di classi che sono complesse e costose da controllare per l'uguaglianza di solito tendono ad essere classi con semantica di riferimento, quindi non hanno bisogno di un metodo equals() . E in generale, se hai qualche dubbio o ripensamento su come dovrebbe essere implementato il metodo equals() , questa è una buona indicazione che quello che hai tra le mani è una classe semantica di riferimento, non una classe semantica di valore .

Come per hashCode() , gli unici oggetti che dovrebbero implementarlo sono oggetti che non hanno solo semantica del valore, ma sono anche immutabili. Questo perché i contenitori di hash in genere ottengono il codice hash di un oggetto una volta e quindi lo memorizzano nella cache fino a quando l'oggetto risiede nel contenitore, quindi se l'oggetto subisce una mutazione, il contenuto dell'oggetto sarà in conflitto con la cache codice hash. È un bug newbie molto comune usare un oggetto non immutabile come chiave per una hashmap, e molto difficile da rintracciare a meno che non si sappia dove guardare prima: l'immutabilità della classe usata come chiave.

Quindi, ogni classe che non è immutabile dovrebbe avere un metodo hashCode() codificato come segue:

public int hashCode()
{
    assert false; //OMG! hashCode() was invoked on mutable object!
}

Quindi, poiché solo le classi immutabili devono implementare hashCode() , accertarsi che funzioni in un modo che è in accordo con il modo in cui equals() funziona è piuttosto semplice e probabilmente molto più semplice di quanto si possa temere.

    
risposta data 19.10.2015 - 20:30
fonte

Leggi altre domande sui tag