"We are back" « oc.at

[Java] Set ignoriert .equals?

prayerslayer 25.05.2010 - 16:08 1494 7
Posts

prayerslayer

Oar. Mh.
Avatar
Registered: Sep 2004
Location: vorm Sucher
Posts: 4073
Hiho,

ich hab hier eine Klasse SearchItem, welche eine Eigenschaft eines realen Objekts repräsentiert, nach der man suchen kann. Die besteht nur aus einem Namen und einer Methode equals:

Code:
package search;

public class SearchItem {
	private String name;

	public String getName() {
		return name;
	}

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

	@Override
	public String toString() {
		return "SearchItem [name=" + name + "]";
	}

	public SearchItem(String name) {
		this.name = name;
	}

	@Override
	public boolean equals(Object obj) {
		if (obj == null)
			return false;
		if (obj instanceof SearchItem) {
			System.out.println("iof SearchItem");
			System.out.println(name+" equals "+((SearchItem)obj).getName()+"?");
			return ((SearchItem)obj).getName().equalsIgnoreCase(this.getName());
		}
		if (obj instanceof String) {
			System.out.println("iof String");
			System.out.println(name+" equals "+obj+"?");
			return name.equalsIgnoreCase((String)obj);
		}
		return false;
	}
}

Diese SearchItems werden in einem SearchItemSystem verwaltet.

Code:
package search;

import java.util.*;

public class SearchItemSystem {
	private Set<SearchItem> searchItems;
	private static SearchItemSystem instance;
	
	private SearchItemSystem() {
		searchItems = new HashSet<SearchItem>();
	}
	
	/**
	 * Returns the instance of SearchItemSystem
	 * 
	 * @return instance
	 */
	public static SearchItemSystem getInstance() {
		if (instance==null)
			instance=new SearchItemSystem();
		return instance;
	}
	
	/**
	 * Deletes an Item from the system
	 * @param name The name of the item
	 * @return true if something was removed
	 */
	public boolean deleteItem(String name) {
		if (name==null)
			return false;
		SearchItem old = new SearchItem(name);
		return searchItems.remove(old);
	}
	
	/**
	 * Edit an SearchItem
	 * 
	 * @param name The name of the SearchItem
	 * @param newName The new name
	 * @return The new item. Null if something went wrong or there was no old item
	 */
	public SearchItem editItem(String name, String newName) {
		if (name==null)
			return null;
		if (newName==null)
			return null;
		if (name.equalsIgnoreCase(newName))
			return null;
		SearchItem oldItem = new SearchItem(name);
		if (!searchItems.contains(oldItem))
			return null;
		searchItems.remove(oldItem);
		return createItem(newName);
	}
	
	/**
	 * Returns all items in the system
	 * @return all items
	 */
	public Set<SearchItem> getAllItems() {
		return searchItems;
	}
	
	/**
	 * Creates a new SearchItem, adds it to the system and returns it.
	 * 
	 * @param name The name of the SearchItem
	 * @return The created item, null if there is already such an item
	 */
	public SearchItem createItem(String name) {
		System.out.println("Items: "+searchItems);
		if (name==null)
			return null;
		if (searchItems.contains(name))
			return null;
		SearchItem si = new SearchItem(name);
		System.out.println("new item: "+si);
		if (searchItems.contains(si))
			return null;
		//TODO some evil voodoo magic happens here
		if (searchItems.add(si))
			return si;
		return null;
	}
}

Das Problem ist, dass dieser JUnit-Test funktioniert:

Code:
package search.test;

import search.SearchItem;
import search.SearchItemSystem;
import junit.framework.*;

public class SearchItemSystemTest extends TestCase{

	SearchItemSystem sis = null;
	SearchItem a = new SearchItem("A");
	SearchItem b = new SearchItem("B");
	SearchItem c = new SearchItem("C");
	
	public void setUp() {
		sis = SearchItemSystem.getInstance();
	}
	
	public void testItems() {
		assertTrue(".equals isn't working", (new SearchItem("D")).equals(new SearchItem("d")));
		assertTrue(".equals isn't working", (new SearchItem("D")).equals("d"));
		assertFalse(".equals isn't working", b.equals("C"));
		assertTrue(".equals isn't working", b.equals("b"));
		assertTrue(".equals isn't working", b.equals(b));
		assertFalse(".equals isn't working", b.equals(c));
		assertEquals("Null-Item was created", null, sis.createItem(null));
		assertEquals("Item A was not created", a, sis.createItem("A"));
		assertEquals("Item A was created twice", a, sis.createItem("a"));
		assertEquals("Item A was created three times", a, sis.createItem("a"));
	}
	
	public static void main(String[] args) {
		// TODO Auto-generated method stub
		junit.textui.TestRunner.run(SearchItemSystemTest.class);
	}
}

Output:
Code:
iof SearchItem
D equals d?
iof String
D equals d?
iof String
B equals C?
iof String
B equals b?
iof SearchItem
B equals B?
iof SearchItem
B equals C?
Items: []
Items: []
new item: SearchItem [name=A]
iof SearchItem
A equals A?
Items: [SearchItem [name=A]]
new item: SearchItem [name=a]
iof SearchItem
A equals a?
Items: [SearchItem [name=a], SearchItem [name=A]]
new item: SearchItem [name=a]
iof SearchItem
A equals a?

Eigentlich sollte beim 2. und 3. Mal createItem("a") ein Nullpointer herauskommen. Lustigerweise passiert JUnit aber die Passage, wo die equals() Methode der SearchItems überprüft wird. contains() und add() des HashSets überprüfen nicht richtig, ob ein SearchItem schon vorhanden ist - ich dachte immer, die greifen auf equals() zurück, aber dann müsste es ja funktionieren.

Entweder ist der Fehler offensichtlich und ich steh auf der Leitung, oder aber hier passiert wirklich evil voodoo magic. :confused:

Bitte helfen! tia
Bearbeitet von prayerslayer am 25.05.2010, 20:56

DKCH

Administrator
...
Registered: Aug 2002
Location: #
Posts: 3308
ohne jetzt was genauer angeschaut zu haben: für ein HashSet solltest du auch hashCode überschreiben, der aus den properties, die in equals verwendet werden, halt einen hash berechnet...

Nico

former person of interest
Registered: Sep 2006
Location: -
Posts: 4082
glaube auch dass deine equals version nie aufgerufen wird..

prayerslayer

Oar. Mh.
Avatar
Registered: Sep 2004
Location: vorm Sucher
Posts: 4073
Zitat von DKCH
ohne jetzt was genauer angeschaut zu haben: für ein HashSet solltest du auch hashCode überschreiben, der aus den properties, die in equals verwendet werden, halt einen hash berechnet...

Das hab ich noch nie müssen :/ Ich probier das mal.

Zitat von Nico
glaube auch dass deine equals version nie aufgerufen wird..

Nawohl, beim add() im Set laut Konsolenoutput schon.

//Danke DKCH, diese Methode im SearchItem...
Code:
@Override
	public int hashCode() {
		return this.name.toLowerCase().hashCode();
	}

...führt zum gewünschten Ergebnis :)
Bearbeitet von prayerslayer am 25.05.2010, 20:33

meepmeep

Here to stay
Avatar
Registered: Feb 2006
Location: Wien
Posts: 2338
ich weiss nicht mit welcher ide du arbeitest, aber eclipse zum beispiel kann dir equals und hashcode generieren (allerdings nur bis zu einem gewissen grad sinnvoll, in deinem fall aber wahrscheinlich ausreichend)

lg
Bearbeitet von meepmeep am 25.05.2010, 21:05

semteX

begehrt die rostschaufel
Avatar
Registered: Oct 2002
Location: Pre
Posts: 14738
generell ist das überschreiben von equals alleine selten sinnvoll und wird völlig zurecht von "findbugs" (super plugin btw) als bug identifiziert... sobald das ding in nem hashset oder hashmap drin ist, kommst in teufels küche...

Nico

former person of interest
Registered: Sep 2006
Location: -
Posts: 4082
plugin für welche ide?

semteX

begehrt die rostschaufel
Avatar
Registered: Oct 2002
Location: Pre
Posts: 14738
eclipse
Kontakt | Unser Forum | Über overclockers.at | Impressum | Datenschutz