Dubiose ClassCastException in typisierter Map

Status
Nicht offen für weitere Antworten.
G

Guest

Gast
Hallo liebes Java-Forum,

zum ersten Mal seit zwei Jahren oder so bringt mich Java (hier 1.5) so weit, eine Frage zu stellen: Ich möchte in einem Set garantiert nur eine Implementierung eines bestimmten Interfaces oder eines von dessen Subinterfaces haben. Da die equals()-Methode aber gelichzeitig erhalten bleiben soll, habe ich ein "Wrapperobjekt" geschrieben, welches die die Implementierung kapselt und eine eigene equals-Methode zur Verfügung stellt:

Code:
public class ConstraintTypeKey {
	
	private Constraint constraint;
	private static final ConstraintTypeComparator ctc = new ConstraintTypeComparator();

	public ConstraintTypeKey(Constraint _c) {
		this.constraint = _c;
	}

	@Override
	public boolean equals(Object obj) {
		if (obj instanceof ConstraintTypeKey) {
			ConstraintTypeKey ctk = (ConstraintTypeKey) obj;
			return ConstraintTypeKey.ctc.compare(
					ctk.constraint, this.constraint) == 0;
		}
		else {
			throw new ClassCastException("The object passed in was not a " +
					"ConstraintTypeKey: " + obj.getClass());
		}
	}
	
}

Wie der Name andeutet, soll dieses Objekt nun als Schlüssel fungieren, um das Set eben bzgl. der o.g. Bedingung konsistent zu halten. Nun tritt aber im dazugehörigen Test eine ClassCastException auf, und zwar stets beim Hinzufügen des zweiten Key-Value-Pairs:

Code:
	public void testCompare() {
		// create two different constraints that implement the same interface 
		// line.
		Constraint a = new AlternateLogicalConstraintImpl();
		Constraint b = new LogicalConstraintImpl(null, null, LogicalConstraint.LogicalOperator.AND);
		
		// a and b should be rated as equal according to this Comparator.
		ConstraintTypeComparator ctc = new ConstraintTypeComparator();
		int result = ctc.compare(a, b);
		assertTrue(result == 0);
		
		// a and c should not be rated as equal.
		Constraint c = new LanguageConstraintImpl(Locale.GERMAN);
		result = ctc.compare(a, c);
		assertTrue(result != 0);
		
		// if a, b and c are added to a Set in this order, only b and c should 
		// be in it.
		// this part test tests consistency with equals.
		Map<ConstraintTypeKey, Constraint> constraints_map = 
			new TreeMap<ConstraintTypeKey, Constraint>();
		constraints_map.put(new ConstraintTypeKey(a), a);
		constraints_map.put(new ConstraintTypeKey(b), b); // CCE!
		constraints_map.put(new ConstraintTypeKey(c), c);
		assertTrue(constraints_map.values().contains(b));
		assertTrue(constraints_map.values().contains(c));
		assertFalse(constraints_map.values().contains(a));
	}

Die ClassCastException tritt also in der auch mit CCE! markierten Zeile auf. Und genau das verstehe ich nicht - kann mir jemand helfen? Ich vermute ja, dass die Typisierung der Map für mein Anwendungsziel nicht richtig ist...

Viele Grüße,

Thorsten
--
 
S

SlaterB

Gast
mann oh meter, da musste ich aber ne Menge Code ergänzen, damit das ganze läuft,
das wäre deine Aufgabe gewesen..

kurzes Fazit:
equals wird gar nicht gebraucht, compareTo ist wichtig,
du bekommst die Exception, weil ConstraintTypeKey nicht Comparable ist


Code:
public class Test
{
    private static final ConstraintTypeComparator ctc = new Test().new ConstraintTypeComparator();

    public static void main(String[] args)
        throws Exception
    {
        new Test().testCompare();
    }

    public void testCompare()
    {
        // create two different constraints that implement the same interface
        // line.
        Constraint a = new AlternateLogicalConstraintImpl();
        Constraint b = new LogicalConstraintImpl();

        // a and b should be rated as equal according to this Comparator.
        int result = Test.ctc.compare(a, b);
        assertTrue(result == 0);

        // a and c should not be rated as equal.
        Constraint c = new LanguageConstraintImpl();
        result = ctc.compare(a, c);
        assertTrue(result != 0);

        // if a, b and c are added to a Set in this order, only b and c should
        // be in it.
        // this part test tests consistency with equals.
        Map<ConstraintTypeKey, Constraint> constraints_map = new TreeMap<ConstraintTypeKey, Constraint>();
        constraints_map.put(new ConstraintTypeKey(a), a);
        constraints_map.put(new ConstraintTypeKey(b), b); // CCE!
        constraints_map.put(new ConstraintTypeKey(c), c);
        assertTrue(constraints_map.values().contains(b));
        assertTrue(constraints_map.values().contains(c));
        assertFalse(constraints_map.values().contains(a));
    }

    private static void assertTrue(boolean b)
    {
        if (!b)
        {
            throw new RuntimeException("assertTrue");
        }
    }

    private static void assertFalse(boolean b)
    {
        if (b)
        {
            throw new RuntimeException("assertFalse");
        }
    }

    public class ConstraintTypeKey
        implements Comparable<ConstraintTypeKey>
    {

        private Constraint constraint;


        public ConstraintTypeKey(Constraint _c)
        {
            this.constraint = _c;
        }

        @Override
        public boolean equals(Object obj)
        {
            if (obj instanceof ConstraintTypeKey)
            {
                ConstraintTypeKey ctk = (ConstraintTypeKey)obj;
                return Test.ctc.compare(ctk.constraint, this.constraint) == 0;
            }
            else
            {
                throw new ClassCastException("The object passed in was not a " + "ConstraintTypeKey: " + obj.getClass());
            }
        }

        public int compareTo(ConstraintTypeKey o)
        {
            return Test.ctc.compare(o.constraint, this.constraint);
        }

    }

    class ConstraintTypeComparator
    {

        public int compare(Constraint constraint, Constraint constraint2)
        {
            return constraint.hashCode() - constraint2.hashCode();
        }
    }

    interface Constraint
    {
    }

    class LogicalConstraintImpl
        implements Constraint
    {

        public int hashCode()
        {
            return 1;
        }
    }

    class AlternateLogicalConstraintImpl
        implements Constraint
    {
        public int hashCode()
        {
            return 1;
        }
    }

    class LanguageConstraintImpl
        implements Constraint
    {
        public int hashCode()
        {
            return 2;
        }
    }
}

bei Verwendung einer HashMap wäre wiederum equals wichtig,
aber dann unbedingt auch hashCode() implementieren,
wenn die hashCode()-Werte zweier Keys der Map nicht gleich sind,
dann bringt auch equals() nichts
 
M

maki

Gast
Lass dir in deiner euqals Methode den Parameter ausgeben, zB. über logging oder System.out.

Du solltest die euals Methode nicht allein und nicht so überladen:
Neben der equals Methode wird auch noch die hashCode Methode von allen(!) Collection Klassen aufgerufen, auch müssen diese beiden Methoden einen Vertrag einhalten: http://www.geocities.com/technofundo/tech/java/equalhash.html

Für deinen Anwendungsfall eignet sich ein Comparator Objekt vielleicht besser und ist einfacher.

Oder du erbst von AbstractSet, dann musst du allerdings auch so wie in deinem jetztigen Ansatz, den impliziten Vertrag zwischen euals und hashcode einhalten.

Edit: SlaterB war schneller ;)
 
G

Guest

Gast
Hallo,

erstmal vielen Dank für die Antworten. Comparable zu implementieren hat den offensichtlichen Fehler behoben, und auch der Hinweis, dass die Konsistenz zu hashCode() sichergestellt sein muss, hat geholfen.

Eine kleine Nachfrage hatte ich noch: Tatsächlich habe ich als erstes einen Comparator implementiert, und diesen mit einem SortedSet (TreeSet) verwendet. Aber dieser stellt wohl nur die Sortierung sicher, d.h. es dürfen sehr wohl Elementpaare enthalten sein, für die compare den Wert 0 zurückgibt. Oder gibt es eine Möglichkeit, den Comparator so zu verwenden, da keine zwei Elemente eines Sets bezüglich compare identisch sind, also 0 zurückgeben?

Viele Grüße,

Thorsten
 
S

SlaterB

Gast
> d.h. es dürfen sehr wohl Elementpaare enthalten sein, für die compare den Wert 0 zurückgibt.

nein, dies kann nicht sein (bzw. ich meine nur Constraints, einzelne Elemente , keine Paare),
hier mal ein Beispiel,


Unterschiede zu oben:
das erst-eingefügte Element bleibt drinnen, spätere gleiche kommen nicht mehr rein,

ein contains()-Aufruf mit gleichen, aber nicht identischen Aufrufen liefert logischerweise true

Code:
public class Test
{

    public static void main(String[] args)
        throws Exception
    {
        new Test().testCompare();
    }

    public void testCompare()
    {
        ConstraintTypeComparator ctc = new ConstraintTypeComparator();

        // create two different constraints that implement the same interface
        // line.
        Constraint a = new AlternateLogicalConstraintImpl();
        Constraint b = new LogicalConstraintImpl();

        // a and b should be rated as equal according to this Comparator.
        int result = ctc.compare(a, b);
        assertTrue(result == 0);

        // a and c should not be rated as equal.
        Constraint c = new LanguageConstraintImpl();
        result = ctc.compare(a, c);
        assertTrue(result != 0);

        // if a, b and c are added to a Set in this order, only b and c should
        // be in it.
        // this part test tests consistency with equals.
        SortedSet<Constraint> set = new TreeSet<Constraint>(ctc);
        set.add(a);
        set.add(b);
        set.add(c);
        assertTrue(set.contains(a));
        assertTrue(set.contains(b));
        assertTrue(set.contains(c));
    }

    private static void assertTrue(boolean b)
    {
        if (!b)
        {
            throw new RuntimeException("assertTrue");
        }
    }

    private static void assertFalse(boolean b)
    {
        if (b)
        {
            throw new RuntimeException("assertFalse");
        }
    }

    class ConstraintTypeComparator
        implements Comparator<Constraint>
    {

        public int compare(Constraint constraint, Constraint constraint2)
        {
            return constraint.hashCode() - constraint2.hashCode();
        }
    }

    interface Constraint
    {
    }

    class LogicalConstraintImpl
        implements Constraint
    {

        public int hashCode()
        {
            return 1;
        }
    }

    class AlternateLogicalConstraintImpl
        implements Constraint
    {
        public int hashCode()
        {
            return 1;
        }
    }

    class LanguageConstraintImpl
        implements Constraint
    {
        public int hashCode()
        {
            return 2;
        }
    }
}
 
Status
Nicht offen für weitere Antworten.
Ähnliche Java Themen
  Titel Forum Antworten Datum
L ClassCastException (JavaMail API) Allgemeine Java-Themen 3
P ClassCastException bei Verwendung eines Interfaces Allgemeine Java-Themen 7
G ClassCastException Allgemeine Java-Themen 17
A Vererbung Klassen-Downcasting wirft ClassCastException Allgemeine Java-Themen 2
S OOP ClassCastException bei casting von eigener Klasse aus Iterator Allgemeine Java-Themen 3
F Interpreter-Fehler ClassCastException wird bei Schleifenaufruf geworfen Allgemeine Java-Themen 8
T java.lang.ClassCastException: Allgemeine Java-Themen 4
C java.lang.ClassCastException Allgemeine Java-Themen 3
T java.lang.ClassCastException Allgemeine Java-Themen 7
SuperSeppel13 ClassCastException Allgemeine Java-Themen 6
W java.lang.ClassCastException Allgemeine Java-Themen 10
H setWrappedData -> classcastexception Allgemeine Java-Themen 2
G java.lang.ClassCastException Allgemeine Java-Themen 3
B ClassCastException Allgemeine Java-Themen 2
V ClassCastException bei ArrayList Allgemeine Java-Themen 5
R ClassCastException Allgemeine Java-Themen 14
S Problem mit generics -> ClassCastException und ka wieso Allgemeine Java-Themen 20
B ClassCastException Allgemeine Java-Themen 17
S classCastException Allgemeine Java-Themen 4
S ClassCastException beim Casten in eine Klasse Allgemeine Java-Themen 7
H Aus typisierter Liste ein typisiertes Array erhalten Allgemeine Java-Themen 10

Ähnliche Java Themen

Neue Themen


Oben