web-dev-qa-db-fra.com

LinkedHashSet - ordre d'insertion et doublons - garder le plus récent "en haut"

J'ai besoin d'une collection qui garde l'ordre d'insertion et a des valeurs uniques. LinkedHashSet ressemble à la voie à suivre, mais il y a un problème - lorsque deux éléments sont égaux, il supprime le plus récent (ce qui est logique), voici un exemple:

set.add("one");
set.add("two");
set.add("three");
set.add("two");

LinkedHashSet affichera:

one, two, three

Mais ce dont j'ai besoin c'est:

one, three, two

Quelle serait la meilleure solution ici? Existe-t-il une méthode de collecte/collections qui peut le faire ou dois-je l'implémenter manuellement?

32
rafakob

La plupart des Java Collections peuvent être étendus pour peaufiner.

Sous-classe LinkedHashSet , remplaçant la méthode add .

class TweakedHashSet<T> extends LinkedHashSet<T> {

    @Override
    public boolean add(T e) {
        // Get rid of old one.
        boolean wasThere = remove(e);
        // Add it.
        super.add(e);
        // Contract is "true if this set did not already contain the specified element"
        return !wasThere;
    }

}
34
OldCurmudgeon

Vous pouvez simplement utiliser une fonction spéciale de LinkedHashMap:

Set<String> set = Collections.newSetFromMap(new LinkedHashMap<>(16, 0.75f, true));
set.add("one");
set.add("two");
set.add("three");
set.add("two");
System.out.println(set); // prints [one, three, two]

Dans le JRE d'Oracle, le LinkedHashSet est de toute façon soutenu par un LinkedHashMap, donc il n'y a pas beaucoup de différence fonctionnelle, mais le constructeur spécial utilisé ici configure le LinkedHashMap pour changer l'ordre à chaque - accès non seulement sur insertion. Cela peut sembler trop, mais affecte en fait l'insertion de clés déjà contenues (valeurs au sens de Set) uniquement. Les autres opérations Map affectées (à savoir get) ne sont pas utilisées par le Set retourné.

Si vous n'utilisez pas Java 8, vous devez aider un peu le compilateur en raison de l'inférence de type limitée:

Set<String> set
    = Collections.newSetFromMap(new LinkedHashMap<String, Boolean>(16, 0.75f, true));

mais la fonctionnalité est la même.

18
Holger

Lors de l'initialisation de votre LinkedHashSet, vous pouvez remplacer la méthode add.

Set<String> set = new LinkedHashSet<String>(){
    @Override
    public boolean add(String s) {
        if(contains(s))
            remove(s);
        return super.add(s);
    }
};

Maintenant, cela vous donne:

set.add("1");
set.add("2");
set.add("3");
set.add("1");
set.addAll(Collections.singleton("2"));

// [3, 1 ,2]

même la méthode addAll fonctionne.

6
Toonijn

Toutes les solutions fournies ci-dessus sont excellentes mais si nous ne voulons pas remplacer les collections déjà implémentées. Nous pouvons résoudre ce problème simplement en utilisant une ArrayList avec une petite astuce

Nous pouvons créer une méthode que vous utiliserez pour insérer des données dans votre liste

public static <T> void addToList(List<T> list, T element) {
    list.remove(element); // Will remove element from list, if list contains it
    list.add(element); // Will add element again to the list 
}

Et nous pouvons appeler cette méthode pour ajouter un élément à notre liste

List<String> list = new ArrayList<>();

addToList(list, "one");
addToList(list, "two");
addToList(list, "three");
addToList(list, "two");

Le seul inconvénient ici est que nous devons appeler notre méthode addToList() personnalisée à chaque fois au lieu de list.add()

1
Naresh Joshi