Methodenverkettung + Vererbung passen nicht gut zusammen?
Lesezeit: 5 Minuten
Jason S
Diese Frage wurde in einem C++-Kontext gestellt, aber ich bin neugierig auf Java. Die Bedenken bezüglich virtueller Methoden gelten nicht (glaube ich), aber wenn Sie diese Situation haben:
abstract class Pet
{
private String name;
public Pet setName(String name) { this.name = name; return this; }
}
class Cat extends Pet
{
public Cat catchMice() {
System.out.println("I caught a mouse!");
return this;
}
}
class Dog extends Pet
{
public Dog catchFrisbee() {
System.out.println("I caught a frisbee!");
return this;
}
}
class Bird extends Pet
{
public Bird layEgg() {
...
return this;
}
}
{
Cat c = new Cat();
c.setName("Morris").catchMice(); // error! setName returns Pet, not Cat
Dog d = new Dog();
d.setName("Snoopy").catchFrisbee(); // error! setName returns Pet, not Dog
Bird b = new Bird();
b.setName("Tweety").layEgg(); // error! setName returns Pet, not Bird
}
Gibt es in dieser Art von Klassenhierarchie einen Weg zurück? this auf eine Weise, die den Objekttyp nicht (effektiv) umwandelt?
Jetzt verstehe ich, warum so viele Leute Java hassen.
– Ionuț G. Stan
1. Juli 09 um 15:11 Uhr
Wenn Sie ungeprüfte Cast-Warnungen von Ihrem Compiler vermeiden möchten (und @SuppressWarnings (“unchecked”) nicht möchten), müssen Sie etwas mehr tun:
Zunächst einmal muss Ihre Definition von Pet selbstreferenziell sein, da Pet immer ein generischer Typ ist:
abstract class Pet <T extends Pet<T>>
Zweitens die (T) this cast in setName ist ebenfalls deaktiviert. Um dies zu vermeiden, verwenden Sie die „getThis“-Technik im Excellent Generika-FAQ von Angelika Langer:
Der “getThis”-Trick bietet eine Möglichkeit, den genauen Typ dieser Referenz wiederherzustellen.
Dies führt zu folgendem Code, der ohne Warnungen kompiliert und ausgeführt wird. Wenn Sie Ihre Unterklassen erweitern möchten, gilt die Technik immer noch (obwohl Sie wahrscheinlich Ihre Zwischenklassen generisieren müssen).
Der resultierende Code lautet:
public class TestClass {
static abstract class Pet <T extends Pet<T>> {
private String name;
protected abstract T getThis();
public T setName(String name) {
this.name = name;
return getThis(); }
}
static class Cat extends Pet<Cat> {
@Override protected Cat getThis() { return this; }
public Cat catchMice() {
System.out.println("I caught a mouse!");
return getThis();
}
}
static class Dog extends Pet<Dog> {
@Override protected Dog getThis() { return this; }
public Dog catchFrisbee() {
System.out.println("I caught a frisbee!");
return getThis();
}
}
public static void main(String[] args) {
Cat c = new Cat();
c.setName("Morris").catchMice();
Dog d = new Dog();
d.setName("Snoopy").catchFrisbee();
}
}
Der Code wird auf diese Weise sauberer, und ich werde mir etwas Zeit nehmen, um den vollständigen Angelika-Artikel zu lesen, thx vm!
– Wassermannkraft
17. Februar 15 um 14:41 Uhr
class Snake extends Pet<Cat> {@Override protected Cat getThis() {return new Cat();}}
– Benutzer253751
18. Juli 15 um 6:44 Uhr
Dies wird jedoch etwas schwierig, wenn Sie nicht abstrakte, nicht endgültige Klassen haben, die sich in der Mitte befinden und eine Instanz erstellen müssen. Angenommen, Sie hätten z static class Poodle extends Dog<Poodle>, und geändert Dog sein static class Dog<T extends Dog<T>> extends Pet<T>. Erstellen Sie nun eine Rohinstanz von Dog wäre schwierig.
– Markus
15. November 15 um 0:09 Uhr
Gibt es eine Möglichkeit, wie es möglicherweise mit anonymen Klassen funktionieren könnte? Ich kann keinen Weg finden, es für die Generika T auf sich selbst verweisen zu lassen: /
– Wassermannkraft
30. April 17 um 0:05 Uhr
Ich habe eine Sache erstellt, die funktioniert hat: class PetAnnon extends Pet<PetAnnon>{} und bei jeder anonymen Klasse verwende ich es einfach gerne new Pet<PetAnnon>{... jetzt Methoden wie <T> T get(Class<T> cl){return (T)this.val;} wird wieder funktionieren.
– Wassermannkraft
30. April 17 um 0:29 Uhr
Wie wäre es mit diesem alten Trick:
abstract class Pet<T extends Pet>
{
private String name;
public T setName(String name) { this.name = name; return (T) this; }
}
class Cat extends Pet<Cat>
{
/* ... */
}
class Dog extends Pet<Dog>
{
/* ... */
}
+1, präziser ausgedrückt als ich. Aber wenn man bedenkt, wie lange es Java-Generika schon gibt, wie alt könnte ein Trick sein?
– Steve B.
1. Juli 09 um 15:14 Uhr
aha, ich dachte da wäre was mit Generika, wusste nur nicht was. Danke!
– Jason S
1. Juli 09 um 16:03 Uhr
@Steve B: Es ist nicht alt in Java (eigentlich glaube ich nicht, dass ich es in Java verwendet habe), aber es wird seit langem in C++ verwendet.
– Rasmus Faber
1. Juli 09 um 16:17 Uhr
Hmmm. Könnten Sie auch ein Upcasting- und Downcasting-Beispiel hinzufügen? zB Pet pet = c; ((Katze)haustier).catchMice(); (habe ich das recht?)
Nein nicht wirklich. Sie könnten dies umgehen, indem Sie kovariante Rückgabetypen verwenden (danke an McDowell für den korrekten Namen):
@Override
public Cat setName(String name) {
super.setName(name);
return this;
}
(Kovariante Rückgabetypen gibt es nur in Java 5 und höher, falls Sie das interessiert.)
Steve B.
Es ist ein bisschen kompliziert, aber Sie können dies mit Generika tun:
abstract class Pet< T extends Pet > {
private String name;
public T setName( String name ) {
this.name = name;
return (T)this;
}
public static class Cat extends Pet< Cat > {
public Cat catchMice() {
System.out.println( "I caught a mouse!" );
return this;
}
}
public static class Dog extends Pet< Dog > {
public Dog catchFrisbee() {
System.out.println( "I caught a frisbee!" );
return this;
}
}
public static void main (String[] args){
Cat c = new Cat();
c.setName( "Morris" ).catchMice(); // error! setName returns Pet, not Cat
Dog d = new Dog();
d.setName( "Snoopy" ).catchFrisbee(); // error! setName returns Pet, not Dog
}
}
Paul Adamson
public class Pet<AnimalType extends Pet> {
private String name;
public AnimalType setName(String name) {
this.name = name; return (AnimalType)this;
}
}
und
public class Cat extends Pet<Cat> {
public Cat catchMice() {return this;}
public static void main(String[] args) {
Cat c = new Cat().setName("bob").catchMice();
}
}
@Steve B. – +1, wette darauf!
– Paul Adamson
1. Juli 09 um 15:06 Uhr
BEARBEITEN: Kopieren / Einfügen Ihres Codes hat den Zweck erfüllt. Ich erkannte, dass meine Basisklasse war X extends Y anstatt X extends Y<X>. Das hat es gelöst!
– fIwJlxSzAPHEZIl
14. März 18 um 21:12 Uhr
@Steve B. – +1, wette darauf!
– Paul Adamson
1. Juli 09 um 15:06 Uhr
BEARBEITEN: Kopieren / Einfügen Ihres Codes hat den Zweck erfüllt. Ich erkannte, dass meine Basisklasse war X extends Y anstatt X extends Y<X>. Das hat es gelöst!
– fIwJlxSzAPHEZIl
14. März 18 um 21:12 Uhr
.
7164400cookie-checkMethodenverkettung + Vererbung passen nicht gut zusammen?yes
Jetzt verstehe ich, warum so viele Leute Java hassen.
– Ionuț G. Stan
1. Juli 09 um 15:11 Uhr