2011-09-27 6 views
4

J'ai un problème étrange survenant dans mon application, je vais rapidement expliquer l'architecture globale, puis mon problème en profondeur.Java HashMap ne trouve pas la clé, mais il devrait

J'utilise un service pour remplir un HashMap<DomainObject,Boolean> provenant de ma base de données (pilotée par JPA) qui est à son tour retourné à ma vue, via un appel de méthode à distance EJB (en utilisant Apache Wicket). Dans cette partie, j'ajoute un nouveau DomainObject à la carte retournée afin de stocker toute nouvelle valeur de mon utilisateur final. Le problème se produit lorsque l'utilisateur clique sur le bouton "Ajouter" dans son navigateur, j'essaie de récupérer l'élément nouvellement créé dans ma carte, mais il échoue. En jouant avec le débogueur, je fais face aux choses suivantes.

En supposant HashMap<DomainObject, Boolean> map et DomainObject do sont les deux variables intéressantes, j'ai les résultats suivants dans le débogueur

map.keySet(); me donne un objet correspondant à do (même Simili-référence @whatever est identique), hashcode() à la fois retourne des objets valeur similaire et equals() entre les deux rendements true

map.containsKey(do); rendements false

map.get(do); renvoie null, bizarre parce que ma clé semble être dans le map.

En supposant que mon élément nouvellement créé est la première clé énumérée par keySet(), je fais ce qui suit: map.get(new ArrayList(map.keySet()).get(0)), et il renvoie null.

Si elle peut aider, en attachant des points d'arrêt à mes DomainObject.equals() et DomainObject.hashcode() méthodes que je trouve que map.get() est seulement et non hashcode() appellent equals().

La seule solution de contournement que j'ai trouvée est de recréer une nouvelle carte en plus de celle existante new HashMap(map), dans cette nouvelle carte, je n'ai aucun problème à rechercher un objet par sa clé.

J'espère que quelqu'un ici peut donner mon un pointeur sur ce qui se passe, merci.

Environnement utilisé:

  • Sun Java 1.6.0_26 sous x64 OS X 10.7.1
  • OpenJDK 1.6.0_18 x64 sous Debian 6.0.2 (2.6.32)
  • Apache Wicket 1.4.17
  • Oracle Glassfish 3.1.1
  • JBoss Hibernate 3.6.5

DomainObject Code:

public class AssetComponentDetailTemplate extends BaseEntite<Long> { 
public enum DataType { 
    TXT, 
    DATE, 
    INT, 
    JOIN, 
    LIST, 
    COULEURS, 
    REFERENCE 
} 

public enum Tab { 
    IDENTITE, 
    LOCALISATION, 
    CYCLE_DE_VIE, 
    FINANCE, 
    RESEAU, 
    DETAIL 
} 

@Id 
@GeneratedValue(strategy = GenerationType.AUTO) 
private Long id; 
@Column(nullable = false) 
private String name; 
@Column(nullable = false) 
@Enumerated(EnumType.STRING) 
private DataType dataType; 
private Integer classNameId; 
private Long orderId; 
private Long nextAssetComponentDetailTemplateId; 
private String unit; 
@Enumerated(EnumType.STRING) 
private Tab tab; 

@Column(nullable = false) 
private Long uniqueOrganizationId; 

@OneToMany(fetch = FetchType.LAZY) 
@JoinColumn(name = "idAssetComponentDetailTemplate", insertable = false, updatable = false) 
private List<AssetComponentDetailJoin> assetComponentDetailJoins; 

private Boolean mandatory = false; 

public AssetComponentDetailTemplate() { 
} 

public Long getId() { 
    return id; 
} 

public void setId(final Long id) { 
    this.id = id; 
} 

public String getName() { 
    return name; 
} 

public void setName(final String name) { 
    this.name = name; 
} 

public DataType getDataType() { 
    return dataType; 
} 

public void setDataType(final DataType dataType) { 
    this.dataType = dataType; 
} 

public Integer getClassNameId() { 
    return classNameId; 
} 

public void setClassNameId(final Integer classNameId) { 
    this.classNameId = classNameId; 
} 

public Long getUniqueOrganizationId() { 
    return uniqueOrganizationId; 
} 

public void setUniqueOrganizationId(final Long uniqueOrganizationId) { 
    this.uniqueOrganizationId = uniqueOrganizationId; 
} 

public Long getNextAssetComponentDetailTemplateId() { 
    return nextAssetComponentDetailTemplateId; 
} 

public void setNextAssetComponentDetailTemplateId(final Long nextAssetComponentDetailTemplateId) { 
    this.nextAssetComponentDetailTemplateId = nextAssetComponentDetailTemplateId; 
} 

public String getUnit() { 
    return unit; 
} 

public void setUnit(final String unit) { 
    this.unit = unit; 
} 

public Tab getTab() { 
    return tab; 
} 

public void setTab(final Tab tab) { 
    this.tab = tab; 
} 

public Long getOrder() { 
    return orderId; 
} 

public void setOrder(final Long order) { 
    this.orderId = order; 
} 

public Boolean isMandatory() { 
    return mandatory; 
} 

@Override 
public String toString() { 
    return name; 
} 

@Override 
public boolean equals(final Object o) { 
    if (this == o) { 
     return true; 
    } 
    if (o == null || getClass() != o.getClass()) { 
     return false; 
    } 

    final AssetComponentDetailTemplate that = (AssetComponentDetailTemplate) o; 

    if (classNameId != null ? !classNameId.equals(that.classNameId) : that.classNameId != null) { 
     return false; 
    } 
    if (dataType != that.dataType) { 
     return false; 
    } 
    if (id != null ? !id.equals(that.id) : that.id != null) { 
     return false; 
    } 
    if (name != null ? !name.equals(that.name) : that.name != null) { 
     return false; 
    } 
    if (nextAssetComponentDetailTemplateId != null ? 
     !nextAssetComponentDetailTemplateId.equals(that.nextAssetComponentDetailTemplateId) : 
     that.nextAssetComponentDetailTemplateId != null) { 
     return false; 
    } 
    if (orderId != null ? !orderId.equals(that.orderId) : that.orderId != null) { 
     return false; 
    } 
    if (tab != that.tab) { 
     return false; 
    } 
    if (uniqueOrganizationId != null ? !uniqueOrganizationId.equals(that.uniqueOrganizationId) : 
     that.uniqueOrganizationId != null) { 
     return false; 
    } 
    if (unit != null ? !unit.equals(that.unit) : that.unit != null) { 
     return false; 
    } 

    return true; 
} 

@Override 
public int hashCode() { 
    int result = id != null ? id.hashCode() : 0; 
    result = 31 * result + (name != null ? name.hashCode() : 0); 
    result = 31 * result + (dataType != null ? dataType.hashCode() : 0); 
    result = 31 * result + (classNameId != null ? classNameId.hashCode() : 0); 
    result = 31 * result + (orderId != null ? orderId.hashCode() : 0); 
    result = 31 * result + 
      (nextAssetComponentDetailTemplateId != null ? nextAssetComponentDetailTemplateId.hashCode() : 0); 
    result = 31 * result + (unit != null ? unit.hashCode() : 0); 
    result = 31 * result + (tab != null ? tab.hashCode() : 0); 
    result = 31 * result + (uniqueOrganizationId != null ? uniqueOrganizationId.hashCode() : 0); 
    return result; 
} 
+2

Vous devriez montrer du code. Au moins comment vous gérez la carte ainsi que l'implémentation de 'equals' et' hashCode'. – home

+0

Pouvons-nous voir des codes à regarder? – user482594

+0

Je crains que l'extraction du code ne soit pas très facile (elle est étroitement couplée dans l'application). J'ajoute le code 'DomainObject'. –

Répondre

4

[Ce développe essentiellement sur la réponse de Jesper, mais les détails peuvent vous aider]

Depuis recréant la carte en utilisant new HashMap(map) est en mesure de trouver l'élément je soupçonnais que le hashCode() du DomainObject changé après l'avoir ajouté à la carte .

Par exemple, si votre DomainObject regarde la

class DomainObject { 
    public String name; 
    long hashCode() { return name.hashCode(); } 
    boolean equals(Object other) { /* compare name in the two */' 
} 

suivantes,

Map<DomainObject, Boolean> m = new HashMap<DomainObject, Boolean>(); 
    DomainObject do = new DomainObject(); 
    do.name = "ABC"; 
    m.put(do, true); // do goes in the map with hashCode of ABC 
    do.name = "DEF"; 
    m.get(do); 

La dernière déclaration retournera au-dessus null. Parce que l'objet do que vous avez à l'intérieur de la carte se trouve sous le seau "ABC".hashCode(); il n'y a rien dans le compartiment "DEF".hashCode().

Le hashCode des objets dans la carte ne doit pas changer une fois ajouté à la carte. La meilleure façon de s'assurer que les champs dont dépend hashCode doit être immuable.

+0

C'est un pointeur intéressant, je vais regarder plus loin ... –

3

Voici votre idée:

hashcode() sur les deux objets retourne une valeur similaire

Pour les objets à considérés comme égaux, leurs codes de hachage ne devraient pas être semblables, ils doit être identique.

Si deux objets ont des codes de hachage différents, alors en ce qui concerne le conteneur, les objets sont différents. Il n'est même pas nécessaire d'appeler le equals().

De l'Javadoc:

Le contrat général de hashCode est:

  • Si deux objets sont égaux selon la méthode equals(Object), puis appeler la méthode hashCode sur chacun des deux objets doit produire le même résultat entier.

Si je vous, je prendrais un coup d'oeil près DomainObject.hashcode() et DomainObject.equals() pour voir ce qui cause le contrat rompu.

+0

Quand je veux dire identique, je veux dire égale valeur. La chose que j'ai trouvée surprenante est que cela fonctionne comme attendu d'un objet provenant de ma couche Service mais pas d'une vue créée. C'est un cas largement utilisé je pense. Si mon 'hashcode()' était faux, la création d'une nouvelle carte en utilisant l'ancienne ne fonctionnerait pas non plus, ai-je raison? –

3

Votre classe DomainObject est-elle immuable? At-il correctement mis en œuvre les méthodes hashCode et equals?

Notez que vous aurez des ennuis si votre classe DomainObject n'est pas immuable et que vous modifiez l'état de l'objet alors qu'il est dans la carte d'une manière qui changerait le résultat de l'appel hashCode ou equals.

hashCode doit être implémenté de telle sorte qu'il renvoie la même valeur pour deux objets chaque fois que equals renvoie la valeur true lors de la comparaison de ces objets. Voir la documentation API de java.lang.Object.hashCode() pour des informations détaillées.

0

map.get(do) retour null pourrait être facilement expliqué en supposant que la valeur Boolean pour cette clé pourrait être null mais map.containsKey(do) retour false aurait besoin do « s hashCode être différent au moment de l'appel containsKey(do) à sa hashCode au moment de la récupération à partir du keySet.

Pour voir ce qui se passe, vous pouvez (temporairement) utiliser une mise en œuvre plus bavard de HashMap ... Peut-être quelque chose comme ceci:

public class VerboseHashMap<K, V> implements Map<K, V> { 

    private transient final static Logger logger = Logger.getLogger(VerboseHashMap.class); 
    private HashMap<K, V> internalMap = new HashMap<K, V>(); 

    public boolean containsKey(Object o) { 
     logger.debug("Object HashCode: " + o.hashCode()); 
     logger.debug("Map contents:"); 
     for (Entry<K, V> entry : internalMap.entrySet()) { 
      logger.debug(entry.getKey().hashCode() + " - " + entry.getValue().toString()); 
     } 
     return internalMap.containsKey(o); 
    } 

    public V get(Object key) { 
     logger.debug("Object HashCode: " + key.hashCode()); 
     logger.debug("Map contents:"); 
     for (Entry<K, V> entry : internalMap.entrySet()) { 
      logger.debug(entry.getKey().hashCode() + " - " + entry.getValue().toString()); 
     } 
     return internalMap.get(key); 
    } 

} 

Vous aurez besoin de la carte toutes les autres exigences de la carte interface à votre carte interne aussi bien.

Note: Ce code ne vise pas à la production, ni en aucune façon la performance orientée, ou bien unsmelly ....

2ème note (après avoir vu votre code): Pour utiliser votre domaine objet comme une clé pour votre hashMap, vous devez seulement utiliser les parties immuables de votre objet pour hashCode et égales (dans ce cas, la valeur d'id). Else-chargement paresseux autres valeurs changerait la hashCode ...

En réponse à votre commentaire:

public class Demo extends TestCase { 

public void testMap() { 

    Map<DomainObject, String> map = new HashMap<DomainObject, String>(); 
    DomainObject sb = new DomainObject(); 
    map.put(sb, "Some value"); 
    System.out.println(map.containsKey(sb)); 
    sb.value = "Some Text"; 
    System.out.println(map.containsKey(sb)); 

} 

    private static class DomainObject { 

     public String value = null; 

     @Override 
     public int hashCode() { 
      final int prime = 31; 
      int result = 1; 
      result = prime * result + ((value == null) ? 0 : value.hashCode()); 
      return result; 
     } 

     @Override 
     public boolean equals(Object obj) { 
      if (this == obj) 
       return true; 
      if (obj == null) 
       return false; 
      if (getClass() != obj.getClass()) 
       return false; 
      DomainObject other = (DomainObject) obj; 
      if (value == null) { 
       if (other.value != null) 
        return false; 
      } else if (!value.equals(other.value)) 
       return false; 
      return true; 
     } 

    } 

} 

impressions

true 
false 

Le HashCode de la clé est calculée au moment de la mise dans la carte.

+0

Si je calcule 'hashCode()' sur mon 'do' et sur la touche correspondant à' do' en même temps j'obtiens exactement le même résultat, donc je dirais que ce n'est pas un élément changé dans mon objet 'do' (comme il fait référence au même objet). Les valeurs de droite dans ma carte sont soit 'true' ou' false', jamais 'null' et le débogueur montre que la valeur est' false' dans mon cas. –

+0

Voir mon dernier edit – Nicktar

+0

Merci, je comprends maintenant ce qui se passe –

Questions connexes