web-dev-qa-db-fra.com

Pourquoi l'interface Java.util.Set <V> ne fournit-elle pas de méthode get (Object o)?

Je comprends qu’une seule instance d’un objet quelconque, conformément à .equals (), est autorisée dans un ensemble et que vous ne devriez pas "avoir besoin" d’obtenir un objet de cet ensemble si vous avez déjà un objet équivalent, mais j’aimerais tout de même avoir une méthode .get () qui renvoie l'instance réelle de l'objet dans Set (ou null) à partir d'un objet équivalent en tant que paramètre.

Des idées/théories quant à pourquoi il a été conçu comme ça?

En général, je dois contourner ce problème en utilisant une carte et en rendant la clé et la valeur identiques, ou quelque chose du genre.

EDIT: Je ne pense pas que les gens comprennent ma question jusqu'à présent. Je veux l'instance d'objet exacte qui est déjà dans l'ensemble, pas une instance d'objet éventuellement différente où .equals () renvoie true.

Quant à la raison pour laquelle je voudrais ce comportement, typiquement .equals () ne prend pas en compte toutes les propriétés de l’objet. Je souhaite fournir un objet de recherche factice et récupérer l'instance d'objet réelle dans l'ensemble.

43
GreenieMeanie

Bien que l'argument de pureté rende la méthode get(Object) suspecte, l'intention sous-jacente n'est pas théorique. 

Il existe différentes familles de classes et d'interfaces qui redéfinissent légèrement equals(Object). Il ne faut pas chercher plus loin que les interfaces de collections. Par exemple, une ArrayList et une LinkedList peuvent être égales; leur contenu respectif doit simplement être identique et dans le même ordre.

Par conséquent, il existe de très bonnes raisons de trouver l'élément matching dans un ensemble. Une façon plus claire d’indiquer l’intention est peut-être d’avoir une méthode comme

public interface Collection<E> extends ... {
  ...
  public E findMatch(Object o) throws UnsupportedOperationException;
  ...
}

Notez que cette API a une valeur plus large que celle définie dans Set.

En ce qui concerne la question elle-même, je n'ai aucune théorie sur la raison pour laquelle une telle opération a été omise. Je dirai que l'argument minimal spanning set ne tient pas, car de nombreuses opérations définies dans les API de collections sont motivées par la commodité et l'efficacité.

30
Dilum Ranatunga

Le problème est le suivant: Set n'est pas pour "obtenir" des objets, mais pour ajouter et tester la présence. Je comprends ce que vous recherchez. Je me suis retrouvé dans une situation similaire et j’ai fini par utiliser une carte du même objet en clé et en valeur.

EDIT: Juste pour clarifier: http://fr.wikipedia.org/wiki/Set_(abstract_data_type)

12
GClaramunt

J'ai eu la même question sur le forum Java il y a quelques années. Ils m'ont dit que l'interface Set est définie. Il ne peut pas être modifié car cela romprait les implémentations actuelles de l'interface Set. Ensuite, ils ont commencé à réclamer des conneries, comme vous le voyez ici: "L'ensemble n'a pas besoin de la méthode get" et a commencé à me dire que Map doit toujours être utilisé pour obtenir des éléments d'un ensemble. 

Si vous utilisez l'ensemble uniquement pour des opérations mathématiques, telles que l'intersection ou l'union, il peut suffire de contenir (). Cependant, Set est défini dans les collections pour stocker les données. J'ai expliqué la nécessité pour get () dans Set à l'aide du modèle de données relationnel. 

Dans ce qui suit, une table SQL est comme une classe. Les colonnes définissent des attributs (appelés champs en Java) et les enregistrements représentent des instances de la classe. Pour qu'un objet soit un vecteur de champs. Certains des champs sont des clés primaires. Ils définissent l'unicité de l'objet. C'est ce que vous faites pour contient () en Java:

class Element {

        public int hashCode() {return sumOfKeyFields()}
        public boolean equals(Object e) {keyField1.equals(e) && keyField2.equals(e) && ..}

Je ne suis pas au courant des internes de la base de données. Mais vous ne spécifiez qu'une seule fois les champs de clé lorsque vous définissez une table. Vous venez d'annoter les champs clés avec @primary. Vous ne spécifiez pas les clés une deuxième fois lorsque vous ajoutez un enregistrement à la table. Vous ne séparez pas les clés des données, comme dans le mappage. Les tables SQL sont des ensembles. Ce ne sont pas des cartes. Pourtant, ils fournissent get () en plus de maintenir l'unicité et contient () check.

Dans "Art of Computer Programming", introduisant la recherche, D. Knuth dit la même chose:

La plus grande partie de ce chapitre est consacrée à l’étude d’un problème très simple de recherche : Comment retrouver les données stockées avec une identification Donnée. 

Vous voyez, les données sont stockées avec identification. Pas d'identification pointant vers des données mais données avec identification . Il continue:

Par exemple, dans une application numérique, nous pourrions vouloir que Trouve f (x), étant donné x et un tableau des valeurs de f; Dans une application non numérique, il peut être utile de trouver la traduction anglaise d'un mot russe donné.

On dirait qu'il commence à parler de cartographie. cependant, 

En général, nous supposerons qu'un ensemble de N enregistrements a été stocké, Et le problème est de localiser celui qui convient. Nous avons généralement besoin que Les N touches soient distinctes, de sorte que chaque touche identifie de manière unique son enregistrement . La collection de tous les enregistrements s'appelle un table ou fichier, Où le mot "table" est généralement utilisé pour indiquer un petit fichier et "fichier" est généralement utilisé pour indiquer une grande table. Un fichier volumineux ou un groupe de fichiers Est souvent appelé un base de données.

Les algorithmes de recherche sont présentés avec un soi-disant argument, K, Et le problème est de trouver quel enregistrement a K comme clé. Bien que l'objectif De la recherche soit de rechercher les informations stockées dans l'enregistrement Associé à K, les algorithmes de ce chapitre ignorent généralement tout À l'exception des clés elles-mêmes. En pratique, nous pouvons trouver les données associées à Une fois que nous avons localisé K; par exemple, si K apparaît dans emplacement TABLE + i, les données associées (ou un pointeur sur celle-ci) pourraient être à l'emplacement TABLE + i + 1

En d’autres termes, la recherche localise la clé archivée de l’enregistrement et ne doit pas "mapper" la clé sur les données. Les deux sont situés dans le même enregistrement, en tant que fichiers d'objet Java. En d’autres termes, l’algorithme de recherche examine les champs de clé de l’enregistrement, comme dans l’ensemble, plutôt que certaines clés distantes, comme dans la carte.

On nous donne N articles à trier; nous les appellerons enregistrements, et l'intégralité de la collection de N enregistrements sera appelée un fichier. Chaque enregistrement Rj a une clé Kj qui régit le processus de tri. Des données supplémentaires , En plus de la clé, sont généralement également présentes; ces "informations par satellite" supplémentaires n'ont aucun effet sur le tri, si ce n'est qu'elles doivent être acheminées dans le cadre de chaque enregistrement.

Ni l'un ni l'autre, je ne vois pas la nécessité de dupliquer les clés dans un "jeu de clés" supplémentaire dans sa discussion sur le tri.

... ["L'art de la programmation informatique", Chapitre 6, Introduction]

ensemble d'entités collectionne ou définit toutes les entités d'un type d'entité particulier [http://wiki.answers.com/Q/What_is_entity_and_entity_set_in_dbms] Les objets du partage de classe unique leurs attributs de classe. De même, enregistrez-vous dans la base de données. Ils partagent les attributs de colonne. 

Un cas particulier d'une collection est une extension de classe, qui est la collection De tous les objets appartenant à la classe. Les étendues de classe permettent à D’être traitées comme des relations

... ["Database System Concepts", 6ème édition]

Fondamentalement, classe décrit les attributs communs à toutes ses instances. Une table dans une base de données relationnelle fait la même chose. "Le mappage le plus simple que vous aurez jamais est un mappage de propriété d'un attribut unique à une colonne unique." C'est le cas dont je parle.

Je suis tellement prolixe sur la démonstration de l'analogie (isomorphisme) entre les objets et les enregistrements de base de données, car il y a des personnes stupides qui ne l'acceptent pas (pour prouver que leur ensemble ne doit pas avoir la méthode get

Vous voyez dans les reportages comment les gens, qui ne comprennent pas cela, disent que Set with get serait redondant? C'est parce que leur carte abusée, qu'ils imposent d'utiliser à la place de l'ensemble, introduit la redondance. Leur appel à put (obj.getKey (), obj) stocke deux clés: la clé d'origine faisant partie de l'objet et une copie de celui-ci dans l'ensemble de clés de la carte. La duplication est la redondance. Cela implique également une augmentation du volume de code et un gaspillage de la mémoire utilisée à l'exécution. Je ne connais pas les composants internes de la base de données, mais les principes de bonne conception et de normalisation de la base de données indiquent qu’une telle duplication est une mauvaise idée - il ne doit y avoir qu’une source de vérité . La redondance signifie qu'une incohérence peut se produire: la clé est mappée sur un objet ayant une clé différente. L'incohérence est une manifestation de la redondance. Edgar F. Codd a proposé la normalisation de la base de données simplement pour éliminer les redondances et leurs incohérences inférées. Les enseignants sont explicites sur la normalisation: La normalisation ne générera jamais deux tables avec une relation un à un entre elles. Il n'y a aucune raison théorique de séparer une seule entité comme celle-ci avec certains champs dans un seul enregistrement d'une table et d'autres dans un seul enregistrement d'une autre table

Nous avons donc 4 arguments, pourquoi utiliser une carte pour implémenter get in set est mauvais:

  1. la carte est inutile lorsque nous avons un ensemble d'objets uniques
  2. map introduit la redondance dans le stockage Runtime
  3. map introduit le code bloat dans la base de données (dans les collections)
  4. l'utilisation de la carte contredit la normalisation du stockage des données

Même si vous n'êtes pas au courant de l'idée d'ensemble d'enregistrements et de la normalisation des données en jouant avec des collections, vous pouvez découvrir cette structure de données et cet algorithme vous-même, comme nous, les concepteurs org.Eclipse.KeyedHashSet et C++ STL.

J'ai été banni du forum Sun pour avoir signalé ces idées. La bigoterie est le seul argument contre la raison et ce monde est dominé par les bigots. Ils ne veulent pas voir les concepts et comment les choses peuvent être différentes/améliorées. Ils ne voient que le monde réel et ne peuvent imaginer que la conception de Java Collections puisse présenter des faiblesses et être améliorée. Il est dangereux de rappeler des raisons à de telles personnes. Ils vous enseignent leur aveuglement et punissent si vous n'obéissez pas.

Ajouté en décembre 2013: Le SICP indique également que DB est un ensemble d’enregistrements à clé plutôt qu’une carte :

Un système de gestion de données typique passe beaucoup de temps À accéder aux données des enregistrements ou à les modifier, et nécessite donc une méthode efficace pour accéder aux enregistrements. Ceci est fait en identifiant Une partie de chaque enregistrement pour servir de clé d'identification. Maintenant, nous représentons La base de données en tant qu’ensemble d’enregistrements.

7
Val

Je ne sais pas si vous cherchez une explication sur la raison pour laquelle les sets se comportent de cette manière, ou une solution simple au problème que cela pose. D'autres réponses traitaient du premier cas, alors voici une suggestion pour le dernier.

Vous pouvez parcourir les éléments de l'ensemble et tester leur égalité à l'aide de la méthode equals (). Il est facile à implémenter et peu sujet aux erreurs. Évidemment, si vous n'êtes pas sûr de savoir si l'élément est dans le jeu ou non, vérifiez préalablement avec la méthode contains ().

Ce n'est pas efficace comparé, par exemple, à la méthode contains () de HashSet, qui "trouve" l'élément stocké, mais ne le retourne pas. Si vos ensembles peuvent contenir de nombreux éléments, cela pourrait même être une raison d'utiliser une solution de contournement "plus lourde" comme celle de la mise en oeuvre de la carte que vous avez mentionnée. Cependant, si c'est aussi important pour vous (et je vois l'avantage de pouvoir utiliser cette capacité), cela en vaut probablement la peine.

1
Oren Shalev

Je comprends donc que vous pouvez avoir deux objets égaux mais ils ne sont pas la même instance. 

Tel que 

Integer a = new Integer(3);
Integer b = new Integer(3);

Dans ce cas, a.equals (b) car ils font référence à la même valeur intrinsèque mais a! = B car ils sont deux objets différents.

Il existe d'autres implémentations de Set, telles que IdentitySet , qui effectuent une comparaison différente entre les éléments. 

Cependant, je pense que vous essayez d'appliquer une philosophie différente à Java. Si vos objets sont égaux (a.equals (b)) bien que a et b aient un état différent ou une signification différente, il y a quelque chose qui ne va pas ici. Vous pouvez diviser cette classe en deux ou plusieurs classes sémantiques qui implémentent une interface commune - ou peut-être reconsidérer .equals et .hashCode.

Si vous avez Effective Java de Joshua Bloch, consultez les chapitres "Obéir au contrat général en cas d'égalité" et "Minimiser la mutabilité".

1
Sorin Mocanu

Eh bien, si vous avez déjà "eu" la chose de l'ensemble, vous n'avez pas besoin de l'obtenir, n'est-ce pas? ;-)

Votre approche d'utiliser une carte est la bonne chose, je pense. On dirait que vous essayez de "canoniser" des objets via leur méthode equals (), ce que j'ai toujours accompli en utilisant une Map comme vous le suggérez.

1
andersoj

Si vous le considérez comme un ensemble mathématique, vous pouvez en déduire un moyen de trouver l'objet.
Intersectez le jeu avec une collection d’objets contenant uniquement l’objet que vous souhaitez rechercher. Si l'intersection n'est pas vide, le seul élément restant dans l'ensemble est celui que vous recherchiez.

public <T> T findInSet(T findMe, Set<T> inHere){
   inHere.retainAll(Arrays.asList(findMe));
   if(!inHere.isEmpty){
       return inHere.iterator().next();
   }
   return null;
}

Ce n’est pas l’utilisation la plus efficace de la mémoire, mais elle est correcte du point de vue fonctionnel et mathématique.

1
DanMan0715

Utilisez simplement la solution Carte ... un TreeSet et un HashSet le font aussi car ils sont sauvegardés par un TreeMap et un HashMap, il n'y a donc aucune pénalité à le faire (en réalité, cela devrait représenter un gain minimal). 

Vous pouvez également étendre votre ensemble favori pour ajouter la méthode get ().

[]]

1
Carlos Heuberger

Je pense que votre seule solution, compte tenu de l'implémentation de Set, consiste à parcourir ses éléments pour en trouver un qui soit égal à () - vous obtenez alors l'objet réel correspondant dans le Set.

K target = ...;
Set<K> set = ...;
for (K element : set) {
  if (target.equals(element)) {
    return element;
  }
}
1
Sean Owen

Je conviens que j'aimerais voir les implémentations de Set fournir une méthode get ().

En tant qu'option, dans le cas où vos objets implémentent (ou peuvent implémenter) Java.lang.Comparable, vous pouvez utiliser un TreeSet. Ensuite, la fonction de type get () peut être obtenue en appelant ceiling () ou floor (), puis en vérifiant que le résultat est non nul et égal à l'objet de comparaison, par exemple:

TreeSet myTreeSet<MyObject> = new TreeSet();
:
:

// Equivalent of a get() and a null-check, except for the incorrect value sitting in
// returnedMyObject in the not-equal case.
MyObject returnedMyObject = myTreeSet.ceiling(comparisonMyObject);

if ((null != returnedMyObject) && returnedMyObject.equals(comparisonMyObject)) {
:
:
}
0
Eric_B

La liste est une structure de données ordonnée . Donc, cela suit l'ordre d'insertion. Par conséquent, les données que vous mettez seront disponibles à la position exacte au moment de l'insertion.

List<Integer> list = new ArrayList<>();
list.add(1);
list.add(2);
list.add(3);

list.get(0); // will return value 1

Rappelez-vous ceci comme un tableau simple.

L'ensemble n'est pas une structure de données ordonnée . Donc, il ne suit aucun ordre. Les données que vous insérez à une certaine position seront disponibles à n’importe quelle position. 

Set<Integer> set = new HashSet<>();
set.add(1);
set.add(2);
set.add(3);
//assume it has get method
set.get(0); // what are you expecting this to return. 1?.. 

Mais cela va retourner quelque chose d'autre. Par conséquent, créer une méthode get dans Set n'a aucun sens.

** Note **** Pour l'explication que j'ai utilisée, int type, ceci est également valable pour le type Object. 

0
Kannan Msk
Object fromSet = set.tailSet(obj).first();

if (! obj.equals(fromSet)) fromSet = null;

fait ce que vous recherchez. Je ne sais pas pourquoi Java le cache.

0
greg

Java fonctionnel a une implémentation d'un ensemble persistant (soutenu par un arbre rouge/noir) qui inclut accessoirement une méthode split qui semble faire ce que vous voulez. Il retourne un triplet de:

  1. L'ensemble de tous les éléments qui apparaissent avant l'objet trouvé.
  2. Un objet de type Option qui est vide ou contient l'objet trouvé s'il existe dans l'ensemble.
  3. L'ensemble de tous les éléments qui apparaissent après l'objet trouvé.

Vous feriez quelque chose comme ça:

MyElementType found = hayStack.split(needle)._2().orSome(hay);
0
Apocalisp

J'ai eu le même problème. Je l'ai corrigé en convertissant mon ensemble en carte, puis en les récupérant à partir de la carte. J'ai utilisé cette méthode:

public Map<MyObject, MyObject> convertSetToMap(Set<MyObject> set)
{
    Map<MyObject, MyObject> myObjectMap = new HashMap<MyObject, MyObject>();

    for(MyObject myObject: set){
        myObjectMap.put(myObject, myObject);
    }
    return myObjectMap
}

Vous pouvez maintenant obtenir des éléments de votre ensemble en appelant cette méthode comme suit:

convertSetToMap(myset).get(myobject);

Vous pouvez remplacer les équivalents dans votre classe pour lui permettre de vérifier uniquement certaines propriétés telles que Id ou name.

0
Ben

La raison pour laquelle il n'y a pas de get est simple:

Si vous avez besoin d'obtenir l'objet X de l'ensemble, c'est parce que vous avez besoin de quelque chose de X et que vous n'avez pas l'objet.

Si vous n’avez pas l’objet, vous avez besoin d’un moyen (clé) pour le localiser. ..son nom, un nombre quoi que jamais. C'est ce que les cartes sont pour le bien.

map.get ("clé") -> X!

Les ensembles n'ont pas de clés, vous devez les traverser pour récupérer les objets.

Alors, pourquoi ne pas ajouter un get pratique get (X) -> X

Cela n’a aucun sens, car vous avez déjà X, dira le puriste.

Mais maintenant, considérez-le comme non puriste et voyez si vous voulez vraiment ceci:

Supposons que je crée l'objet Y, qui correspond aux égaux de X, de sorte que set.get (Y) -> X. Volia, alors je peux accéder aux données de X que je n'avais pas. Disons par exemple que X a une méthode appelée get flag () et que j'en veux le résultat. 

Maintenant, regardez ce code.

Y

X = map.get (Y);

Donc Y.equals (x) vrai!

mais..

Y.flag () == X.flag () = false. (N'étaient-ils pas égaux?)

Donc, voyez-vous, si cela vous permettait d’obtenir les objets comme celui-ci, c’est sûrement de casser la sémantique de base des égaux. Plus tard, vous allez vivre avec de petits clones de X, tous affirmant qu'ils sont identiques quand ils ne le sont pas.

Vous avez besoin d’une carte pour stocker des éléments et utiliser une clé pour les récupérer.

0
Alex Vaz

Dites, j'ai un utilisateur POJO avec ID et nom. ID conserve le contrat entre égaux et hashcode. Nom ne fait pas partie de l'égalité des objets. Je souhaite mettre à jour le nom. de l'utilisateur basé sur l'entrée de quelque part, l'interface utilisateur.

Comme le jeu Java ne fournit pas de méthode get, je dois parcourir le jeu dans mon code et mettre à jour le nom lorsque je trouve l'objet égal (c'est-à-dire lorsque l'identifiant correspond).

Si vous aviez eu la méthode, ce code aurait pu être raccourci.

Java est maintenant livré avec toutes sortes de choses stupides comme javadb et amélioré pour la boucle, je ne comprends pas pourquoi, dans ce cas particulier, ils sont puristes.

0
tapasvi

La simple interface/API donne plus de liberté lors de la mise en œuvre. Par exemple, si l'interface Set est réduite à une seule méthode contains(), nous obtenons une définition d'ensemble typique pour la programmation fonctionnelle: il s'agit simplement d'un prédicat, aucun objet n'est réellement stocké. C'est également vrai pour Java.util.EnumSet - il ne contient qu'un bitmap pour chaque valeur possible.

0
user158037

C'est juste un avis. Je pense que nous devons comprendre que nous avons plusieurs classes Java sans champs/propriétés, c’est-à-dire uniquement des méthodes. Dans ce cas, les équivalents ne peuvent pas être mesurés en comparant la fonction, par exemple, requestHandlers. Voir l'exemple ci-dessous d'une application JAX-RS. Dans ce contexte, SET est plus logique que n'importe quelle structure de données. 

@ApplicationPath("/")
public class GlobalEventCollectorApplication extends Application {
    @Override
    public Set<Class<?>> getClasses() {
        Set<Class<?>> classes = new HashSet<Class<?>>();
        classes.add(EventReceiverService.class);
        classes.add(VirtualNetworkEventSerializer.class);
        return classes;
    }
}

Pour répondre à votre question, si vous avez un objet employé peu profond (c’est-à-dire un seul EMPID, qui est utilisé dans la méthode equals pour déterminer l’unicité), et si vous souhaitez obtenir un objet profond en effectuant une recherche dans un ensemble, SET n’est pas le structure de données, car son but est différent.

0
Manjesh

si vous en avez fait la demande en parade de bogues Java, indiquez-le ici et nous pourrons le voter. Je pense au moins à la classe de commodité Java.util.Collections qui prend juste un ensemble et un objet Et qui est implémentée comme 

searchSet(Set ss, Object searchFor){

        Iterator it = ss.iterator();
        while(it.hasNext()){
            Object s = it.next();
            if(s != null && s.equals(searchFor)){
                return s;
            }
        }
0
tgkprog

Ceci est évidemment une lacune de l’API Set.

Simplement, je veux rechercher un objet dans mon ensemble et mettre à jour sa propriété.

Et je dois parcourir mon (Hash) Set pour atteindre mon objet ... Sigh ...

0
Michael Dang

Je comprends qu’une seule instance d’un objet quelconque, conformément à .equals (), est autorisée dans un ensemble et que vous ne devriez pas "avoir besoin" d’obtenir un objet de cet ensemble si vous avez déjà un objet équivalent, mais j’aimerais tout de même avoir une méthode .get () qui renvoie l'instance réelle de l'objet dans Set (ou null) à partir d'un objet équivalent en tant que paramètre.

0
Ganesh S

"Je veux l'instance d'objet exacte qui est déjà dans l'ensemble, pas une instance d'objet éventuellement différente où .equals () renvoie true."

Cela n'a pas de sens. Dis que tu fais:

Set<Foo> s = new Set<Foo>();
s.Add(new Foo(...));
...
Foo newFoo = ...;

Vous faites maintenant:

s.contains(newFoo)

Si vous voulez que cela ne soit vrai que si un objet de l'ensemble est == newFoo, implémentez les équations de Foo et hashCode avec l'identité de l'objet. Ou, si vous essayez de mapper plusieurs objets égaux sur un original canonique, une carte peut être le bon choix.

0
Matthew Flaschen

Je pense que l'on s'attend à ce que les égaux représentent vraiment une certaine égalité, pas simplement que les deux objets aient la même clé primaire, par exemple. Et si égaux représentaient deux objets vraiment égaux, alors un get serait redondant. Le cas d'utilisation souhaité suggère une carte, et peut-être une valeur différente pour la clé, quelque chose qui représente une clé primaire plutôt que l'objet entier, puis implémente correctement égaux et hashcode en conséquence.

0
Yishai