Methodenverkettung + Vererbung passen nicht gut zusammen?

Lesezeit: 5 Minuten

Methodenverkettung Vererbung passen nicht gut zusammen
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?)

    – Jason S

    1. Juli 09 um 16:33 Uhr

  • stackoverflow.com/questions/149336/… stackoverflow.com/questions/9138027/…

    – Ray Tayek

    11. März 12 um 13:53 Uhr

1643648227 900 Methodenverkettung Vererbung passen nicht gut zusammen
Michael myers

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.)

1643648227 145 Methodenverkettung Vererbung passen nicht gut zusammen
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
    }

}

1643648228 291 Methodenverkettung Vererbung passen nicht gut zusammen
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


.

716440cookie-checkMethodenverkettung + Vererbung passen nicht gut zusammen?

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy