double dispatch und equals(Object)

hierUndDa

Mitglied
Hallo zusammen

Ich habe mich gerade etwas mit equals() rumgeschlagen. Ich hätte es im Grund gerne als double dispatch implementiert. Dies klappt auch solange ich mein eigenes Interface verwende.

Sobald ich jedoch meine Objekte einem Object zuweise, kriege ich eine StackOverflow Exception. Ein explizites cast vor this bringt ebenfalls nichts (Zeile 16 & Zeile 47).

Weiss jemand weshalb?

Java:
package examples;

public class SimpleDoubleDispatch {
}

interface IWoman {
	public boolean equals(Object obj);
	public boolean equals(IWoman woman);
	public boolean equals(Woman woman);
	public boolean equals(BeardedWoman beardedWomen);
}

class BeardedWoman implements IWoman {
	@Override
	public boolean equals(Object obj) {
		return obj.equals(this);
	}
	@Override
	public boolean equals(Woman woman) {
		return woman == null ? false : true;
	}
	@Override
	public boolean equals(IWoman obj) {
		return obj.equals(this);
	}
	@Override
	public boolean equals(BeardedWoman bearded) {
		return bearded == null ? false : true;
	}
}

class Woman implements IWoman {
	@Override
	public boolean equals(BeardedWoman bearded) {
		return bearded == null ? false : true;
	}
	@Override
	public boolean equals(Woman woman) {
		return woman == null ? false : true;
	}
	@Override
	public boolean equals(IWoman woman) {
		return woman.equals(this);
	}
	@Override
	public boolean equals(Object obj) {
		return obj.equals(this);
	}
}

Hier die Testklasse dazu. Wie gesagt, die drei letzten Tests fallen wegen einer StackOverflow Exception durch.

Java:
package examples;

import static org.junit.Assert.*;

import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;

import org.junit.Test;

@RunWith(JUnit4.class)
public class TestDoubleDispatch {
	
	@Test
	public void womanShouldBeEqualToItSelf() {
		Woman woman = new Woman();
		assertTrue(woman.equals(woman));
	}
	
	@Test
	public void womanAsIWomanShouldBeEqualToItSelf() {
		IWoman woman = new Woman();
		assertTrue(woman.equals(woman));
	}
	
	@Test
	public void womanAndNullShouldNotBeEqual() {
		Woman woman = new Woman();
		Woman woman2 = null;
		assertFalse(woman.equals(woman2));
	}
	
	@Test
	public void womanAndBeardedWomenShouldBeEqual() {
		Woman woman = new Woman();
		BeardedWoman bearded = new BeardedWoman();
		
		assertTrue(woman.equals(bearded));
		assertTrue(bearded.equals(woman));
	}
	
	@Test
	public void womanAndBeardedWomanAsIWomanShouldBeEqual() {
		Woman woman = new Woman();
		IWoman bearded = new BeardedWoman();
		
		assertTrue(woman.equals(bearded));
		assertTrue(bearded.equals(woman));
	}
	
	@Test
	public void womanAsIWomanAndBeardedWomanAsIWomanShouldBeEqual() {
		IWoman woman = new Woman();
		IWoman bearded = new BeardedWoman();
		
		assertTrue(woman.equals(bearded));
		assertTrue(bearded.equals(woman));
	}
	
	@Test
	public void womanAsObjectShouldBeEqualToItSelf() {
		Object woman = new Woman();
		assertTrue(woman.equals(woman));
	}
	
	@Test
	public void womanAsObjectAndBeardedWomanShouldBeEqual() {
		Object woman = new Woman();
		BeardedWoman bearded = new BeardedWoman();
		
		assertTrue(bearded.equals(woman));
		assertTrue(woman.equals(bearded));
	}
	
	@Test
	public void womanAsObjectAndBeardedWomanAsObjectShouldBeEqual() {
		Object woman = new Woman();
		Object bearded = new BeardedWoman();
				
		assertTrue(bearded.equals(woman));
		assertTrue(woman.equals(bearded));
	}
}
 
public boolean equals(Object obj) {
return obj.equals(this);
}
Das ist nunmal eine Endlosschleife, wenn obj von der Klasse Woman ist.
Lass equals() am besten von der IDE generieren oder verwende einen EqualsBuilder.
Viele equals-Methoden zu überladen halte ich für nicht sinnvoll.
 
Ich find das nicht so logisch, wie du es jetzt beschreibst.

Wenn obj von der Klasse Woman ist, dann weiss das obj doch. Das bedeutet, es wird in Woman die Klasse equals() aufgerufen, da ich als Argument this übergebe, sollte doch dann auch klar sein, was ich übergebe.

Ich stelle mir das so vor:

Java:
Object woman = new Woman();
Object beardedWoman = new BeardedWoman();

Java:
woman.equals(beardedWoman);
=> woman weiss welche equals Methode zu ihr passt und ruft deshalb Woman#equals(Object obj) auf, da sie nicht weiss, dass beardedWoman eine BeardedWoman ist. Im Woman#equals(Object obj) stell ich mir den Aufruf dann so vor:

Java:
beardedWoman.equals(this);
=> this ist nun klar ein Woman Objekt, also sollte doch in BeardedWoman die Methode BeardedWoman#equals(Woman woman) aufrufen und nicht BeardedWoman#equals(Object obj)?

Irgendwas habe ich grundsätzlich noch nicht begriffen... Ich würde gerne wissen, weshalb sich das ganze nicht so verhält, wie ich mir das vorstelle.
 
Java ist eine Single Dispatch Sprache, das kann so also nicht funktionieren. Du müsstest das Argument explizit casten damit es in einer spezifischeren Methode landet.
Viele equals-Methoden zu überladen halte ich für nicht sinnvoll.
Absolut, das ist eine ganz schlechte Idee und jetzt solltest du auch wissen warum...
 
Der Hinweis Single-Dispatch hat geholfen.

PS: Auch ein explizites Casting nützt nichts, z.B. obj.equals((Woman) this). Java ist nicht in der Lage in Subklassen überladene Methoden aufzurufen.

Java:
package examples;

public class JavaDispatch {
	public static void main(String[] args) {
		Human human = new Human();
		Human sam = new Sam();
		
		sam.meet(human);
		sam.meet(sam);
		sam.meet((Sam) sam);
	}
}

class Human {
	public void meet(Human hu) {
		System.out.println("Human meets Human");
	}
}

class Sam extends Human {
	public void meet(Sam sam) {
		System.out.println("Sam meets Sam");
	}
}

Code:
Output:
Human meets Human
Human meets Human
Human meets Human
 
So:
Java:
((Sam)sam).meet((Sam) sam);

Wenn du es anders schreibst reicht auch der einfache cast:

Java:
package examples;
 
public class JavaDispatch {
    public static void main(String[] args) {
        Human human = new Human();
        Human sam = new Sam();
        
        sam.meet(human);
        sam.meet(sam);
        sam.meet((Sam) sam);
    }
}
 
class Human {
    public void meet(Human hu) {
        System.out.println("Human meets Human");
    }
    
    public void meet(Sam sam) {
        System.out.println("Sam meets Sam");
    }
}
 
class Sam extends Human {

}

Solche Konstrukte sollte man nach Möglichkeit vermeiden, denn es entstehen leicht Fehler (wohl auch weil viele Entwickler das Konzept nicht vollständig verstehen).
In Java verwendet man statt Double Dispatch normalerweise das Visitor Pattern.

Frage: Multiple Dispatch ist in den meisten modernen Programmiersprachen nicht direkt vorgesehen. Von welcher Programmiersprache kommst du ursprünglich?
 
Zuletzt bearbeitet:

Zurück
Oben