Fonction Jeu de goyaves = Carte

Jeu de goyave + Fonction = Carte

1. Vue d'ensemble

Dans ce didacticiel, nous allons illustrer l'une des nombreuses fonctionnalités utiles du packagecollect deGuava:how to apply a Function to a Guava Set and obtain a Map.

Nous allons discuter de deux approches: créer une carte immuable et une carte en direct basée sur les opérations de goyave intégrées, puis une implémentation d'une implémentation personnalisée deMapen direct.

2. Installer

Tout d'abord, nous allons ajouter la bibliothèqueGuava en tant que dépendance danspom.xml:


    com.google.guava
    guava
    19.0

Une note rapide - vous pouvez vérifier s'il y a unnewer version here.

3. La cartographieFunction

Définissons d'abord la fonction que nous appliquerons aux éléments des ensembles:

    Function function = new Function() {
        @Override
        public String apply(Integer from) {
            return Integer.toBinaryString(from.intValue());
        }
    };

La fonction convertit simplement la valeur d'unInteger en sa représentation binaireString.

4. GoyavetoMap()

Guava propose une classe utilitaire statique relative aux instancesMap. Entre autres, il a deux opérations qui peuvent être utilisées pour convertir unSet en unMap en appliquant lesFunction de Guava définis.

L'extrait de code suivant montre la création d'unMap immuable:

Map immutableMap = Maps.toMap(set, function);

Les tests suivants indiquent que l'ensemble est correctement converti:

@Test
public void givenStringSetAndSimpleMap_whenMapsToElementLength_thenCorrect() {
    Set set = new TreeSet(Arrays.asList(32, 64, 128));
    Map immutableMap = Maps.toMap(set, function);
    assertTrue(immutableMap.get(32).equals("100000")
      && immutableMap.get(64).equals("1000000")
      && immutableMap.get(128).equals("10000000"));
}

Le problème avec la carte créée est que, si un élément est ajouté à l'ensemble source, la carte dérivée n'est pas mise à jour.

4. GoyaveasMap()

Si nous utilisons l'exemple précédent et créons une carte en utilisant la méthodeMaps.asMap:

Map liveMap = Maps.asMap(set, function);

Nous obtiendronsa live map view, ce qui signifie que les modifications apportées à l'ensemble d'origine seront également reflétées dans la carte:

@Test
public void givenStringSet_whenMapsToElementLength_thenCorrect() {
    Set set = new TreeSet(Arrays.asList(32, 64, 128));
    Map liveMap = Maps.asMap(set, function);
    assertTrue(liveMap.get(32).equals("100000")
            && liveMap.get(64).equals("1000000")
            && liveMap.get(128).equals("10000000"));

    set.add(256);
    assertTrue(liveMap.get(256).equals("100000000") && liveMap.size() == 4);
}

Notez que les tests affirment correctement même si nous avons ajouté un élément à un ensemble et l'avons examiné à l'intérieur de la carte.

5. Création deMap en direct personnalisés

Quand nous parlons de la vueMap d'unSet, nous étendons fondamentalement la capacité desSet en utilisant unGuavaFunction.

Dans la vueMap en direct, les modifications apportées auxSet devraient mettre à jour lesMapEntrySet en temps réel. Nous allons créer nos propresMap génériques, sous-classantAbstractMap<K,V>, comme ceci:

public class GuavaMapFromSet extends AbstractMap {
    public GuavaMapFromSet(Set keys,
        Function function) {
    }
}

Il convient de noter que le principal contrat de toutes les sous-classes deAbstractMap est d’implémenter la méthodeentrySet, comme nous l’avons fait. Nous examinerons ensuite 2 parties critiques du code dans les sous-sections suivantes.

5.1. Les entrées

Un autre attribut dans nosMap seraentries, représentant nosEntrySet:

private Set> entries;

Le champentries sera toujours initialisé à l'aide de l'entréeSet duconstructor:

public GuavaMapFromSet(Set keys,Function function) {
    this.entries=keys;
}

Une note rapide ici - afin de maintenir une vue en direct, nous utiliserons les mêmesiterator dans l'entréeSet pour lesMapEntrySet. suivants

En remplissant le contrat deAbstractMap<K,V>, nous implémentons la méthodeentrySet dans laquelle nous retournons ensuiteentries:

@Override
public Set> entrySet() {
    return this.entries;
}

5.2. Cache

CeMap stocke les valeurs obtenues parapplying Function to Set:

private WeakHashMap cache;

6. LesSet Iterator

Nous utiliserons l'entréeSetiterator pour lesMapEntrySet suivants. Pour ce faire, nous utilisons une classeEntrySet personnalisée ainsi qu'une classeEntry personnalisée.

6.1. La classeEntry

Tout d'abord, voyons à quoi ressemblera une seule entrée dans lesMap:

private class SingleEntry implements Entry {
    private K key;
    public SingleEntry( K key) {
        this.key = key;
    }
    @Override
    public K getKey() {
        return this.key;
    }
    @Override
    public V getValue() {
        V value = GuavaMapFromSet.this.cache.get(this.key);
  if (value == null) {
      value = GuavaMapFromSet.this.function.apply(this.key);
      GuavaMapFromSet.this.cache.put(this.key, value);
  }
  return value;
    }
    @Override
    public V setValue( V value) {
        throw new UnsupportedOperationException();
    }
}

Clairement, dans ce code, nous n'autorisons pas la modification desSet de la vueMapas un appel àsetValue jette unUnsupportedOperationException.

Faites très attention àgetValue - c'est le nœud de nos fonctionnalitéslive view. Nous vérifionscache dans notreMap pour l'élémentkey (Set actuel).

Si nous trouvons la clé, nous la retournons, sinonwe apply our function to the current key and obtain a value, puis la stockons danscache.

De cette façon, chaque fois que leSet a un nouvel élément, la carte est à jour puisque les nouvelles valeurs sont calculées à la volée.

6.2. LesEntrySet

Nous allons maintenant implémenter lesEntrySet:

private class MyEntrySet extends AbstractSet> {
    private Set keys;
    public MyEntrySet(Set keys) {
        this.keys = keys;
    }
    @Override
    public Iterator> iterator() {
        return new LiveViewIterator();
    }
    @Override
    public int size() {
        return this.keys.size();
    }
}

Nous avons rempli le contrat d'extension deAbstractSet en remplaçant les méthodesiterator etsize. Mais il y a plus.

Souvenez-vous que l'instance de ceEntrySet formera lesentries dans notre vueMap. Normalement, leEntrySet d'une carte renvoie simplement unEntry complet pour chaque itération.

Cependant, dans notre cas, nous devons utiliser lesiterator de l'entréeSet pour maintenir notre vue en direct. Nous savons bien qu'il ne retournera que les éléments deSet , nous avons donc également besoin d'uniterator personnalisé.

6.3. LesIterator

Voici l'implémentation de nositerator pour lesEntrySet ci-dessus:

public class LiveViewIterator implements Iterator> {
    private Iterator inner;

    public LiveViewIterator () {
        this.inner = MyEntrySet.this.keys.iterator();
    }

    @Override
    public boolean hasNext() {
        return this.inner.hasNext();
    }
    @Override
    public Map.Entry next() {
        K key = this.inner.next();
        return new SingleEntry(key);
    }
    @Override
    public void remove() {
        throw new UnsupportedOperationException();
    }
}

LiveViewIterator doit résider dans la classeMyEntrySet, de cette façon, nous pouvons partager lesiterator deSet lors de l'initialisation.

Lorsque vous parcourez les entrées deGuavaMapFromSet en utilisant lesiterator, un appel ànext récupère simplement la clé desiterator deSet et construit unSingleEntry.

7. Mettre tous ensemble

Après avoir assemblé ce que nous avons couvert dans ce didacticiel, prenons un remplacement de la variableliveMap des échantillons précédents et la remplaçons par notre carte personnalisée:

@Test
public void givenIntSet_whenMapsToElementBinaryValue_thenCorrect() {
    Set set = new TreeSet<>(Arrays.asList(32, 64, 128));
    Map customMap = new GuavaMapFromSet(set, function);

    assertTrue(customMap.get(32).equals("100000")
      && customMap.get(64).equals("1000000")
      && customMap.get(128).equals("10000000"));
}

En changeant le contenu de l'entréeSet, nous verrons que leMap se met à jour en temps réel:

@Test
public void givenStringSet_whenMapsToElementLength_thenCorrect() {
    Set set = new TreeSet(Arrays.asList(32, 64, 128));
    Map customMap = Maps.asMap(set, function);

    assertTrue(customMap.get(32).equals("100000")
      && customMap.get(64).equals("1000000")
      && customMap.get(128).equals("10000000"));

    set.add(256);
    assertTrue(customMap.get(256).equals("100000000") && customMap.size() == 4);
}

8. Conclusion

Dans ce tutoriel, nous avons examiné les différentes façons dont on peut tirer parti des opérationsGuava etobtain a Map view from a Set by applying a Function.

L'implémentation complète de tous ces exemples et extraits de codecan be found in my Guava github project - il s'agit d'un projet basé sur Eclipse, il devrait donc être facile à importer et à exécuter tel quel.