2012-04-27 16 views
15

Sto scrivendo un semplice HashMap cache basata su che funziona come segue:Java - Estendere HashMap - Oggetto vs. generici comportamento

  1. Se la richiesta keyè nella cache, restituisce il suo value.
  2. Se la richiesta keynon è lì, eseguire un metodo che produce value sulla base di key, memorizzare sia, tornare value.

Il codice:

import java.util.HashMap; 

abstract class Cache<K, V> extends HashMap<K, V> { 
    @Override 
    public V get(Object key) { 
     if (containsKey(key)) { 
      return super.get(key); 
     } else { 
      V val = getData(key); 
      put((K)key, val); // this is the line I'm discussing below 
      return val; 
     } 
    } 

    public abstract V getData(Object key); 
} 

E 'piuttosto semplice e funziona bene. Tuttavia, I odio la decisione di Sun per get() di prendere uno Object come argomento e non K. Ho letto abbastanza su questo per sapere che ha qualche fondamento logico (con cui non sono d'accordo, ma questa è un'altra storia).

Il mio problema è nella riga commentata, perché sembra che il cast abbia deselezionato. A causa della cancellazione del tipo, non è possibile verificare se key è di tipo K (che è necessario per la corretta funzionalità put()) e il metodo è, pertanto, soggetto a errori.

Una soluzione potrebbe essere quella di passare da "è un" a "ha un" HashMap rapporto che è molto più bello e pulito, ma poi Cache non può attuare Map che sarebbe bello per diversi motivi. Il codice:

import java.util.HashMap; 
import java.util.Map; 

abstract class Cache<K, V> { 
    private final Map<K, V> map = new HashMap<K, V>(); 

    public V get(K key) { 
     if (map.containsKey(key)) { 
      return map.get(key); 
     } else { 
      V val = getData(key); 
      map.put(key, val); 
      return val; 
     } 
    } 

    public abstract V getData(K key); 
} 

Qualcuno può venire con qualsiasi altra soluzione (anche hacker), in modo da poter mantenere Cache di essere un Map ed essere ancora al sicuro nei termini di get(Object key) e put(K key, V val) tipo?

L'unica cosa che posso pensare è di fare un altro metodo chiamato getValue(Key k) che delegherebbe a get(Object key), ma poi non posso costringere nessuno a usare il nuovo metodo invece del solito.

+1

È possibile implementare 'getA' o' myget' Non è possibile modificare il comportamento di Map.get() come non si può forzare direttamente la ricompilazione del codice che usa l'interfaccia invece della propria classe. –

risposta

15

No. Hai trovato la soluzione giusta per passare a una relazione "ha-a". (Francamente, il fatto che il metodo get calcoli un nuovo valore se non esiste già è sorprendente, viola il contratto Map e può portare a comportamenti estremamente strani per un certo numero di altri metodi.Questa è stata una parte importante del perché Guava si è allontanato da MapMaker, che offriva quasi questo comportamento esatto - perché era solo così riddled con problemi.)

Detto questo, ad esempio Cache di Guava fa è espone uno Map<K, V> asMap()vista, che è una cosa che si potrebbe fare. Questo ti dà la maggior parte dei vantaggi di un Map senza compromettere la sicurezza del tipo.

+0

Sembra che il 'MapMaker' abbia effettivamente fatto quello che faccio :). Sono parzialmente consapevole del comportamento strano. Il metodo 'V getData (chiave K)' è 'abstract' e quindi è inteso per essere specificato (implementato) solo sull'istanza di Cache. Ho anche provato a fare l'abstract finale della classe (anche se sapevo al 100% che non funzionasse) o semplicemente "final" (con il metodo superabile della classe interna anonima), ovviamente non funziona nemmeno) per garantire che nessuno proverà a sottoclasse questo pezzo di codice pericoloso con alcune logiche di business. In ogni caso, grazie mille, la relazione "ha-un" con 'asMap()' che dà un _view_ è. –

+0

A proposito, non posso credere di aver perso la Cava di Guava. Non lo userò ora (quando ho la mia implementazione) per orgoglio, ma se lo sapessi ieri ... :) –

+5

... dovresti prendere in considerazione l'uso di 'Cache' di Guava comunque. Viene utilizzato nella produzione di Google, quindi è stato pesantemente testato e ottimizzato, e ha [lotti] (http://code.google.com/p/guava-libraries/wiki/CachesExplained) di funzionalità a portata di mano. –

0

definitivamente la relazione has-a è una corretta implementazione. la logica aziendale di come viene generato il valore deve essere rimossa dalla classe cache.

+0

Questa è la parte divertente - non c'è logica! Il metodo è 'abstract' e pertanto dovrebbe essere specificato (implementato, sovrascritto, a seconda di quello che ti piace di più) sull'istanza di Cache. Ho anche provato a fare l'abstract finale della classe (anche se sapevo al 100% che non funzionasse) o semplicemente "final" (con il metodo superabile della classe interna anonima), ovviamente non funziona nemmeno) per garantire che nessuno proverà a sottoclasse questo pezzo di codice pericoloso con alcune logiche di business. –