2009-07-01 12 views
30

This question has been asked in a C++ context aber ich bin neugierig auf Java. Die Bedenken über virtuelle Methoden gelten nicht (glaube ich), aber wenn Sie diese Situation haben:Methodenketten + Vererbung spielen nicht gut zusammen?

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 
} 

In dieser Art von Klassenhierarchie, ist es eine Möglichkeit, this in einer Weise zurückzugeben, die nicht (effektiv) upcast den Objekttyp?

+4

Jetzt verstehe ich, warum so viele Menschen Java hassen. –

Antwort

47

Wenn Sie nicht markiert Guss Warnungen von Ihrem Compiler (und wollen nicht @SuppressWarnings („ungeprüft“)) vermeiden wollen, dann müssen Sie ein tun wenig mehr:

Zunächst einmal Ihre Definition von Pet muss selbstbezogen sein, weil Pet immer ein generischer Typ ist:

abstract class Pet <T extends Pet<T>> 

Zweitens ist die (T) this Umwandlung in setName ebenfalls deaktiviert. Um dies zu vermeiden, verwenden Sie die „getThis“ Technik in den ausgezeichneten Generics FAQ by Angelika Langer:

Der „getThis“ Trick bietet eine Möglichkeit, den genauen Typen der diese Referenz erholen.

Dies führt zu dem folgenden Code, der kompiliert und ohne Warnungen ausgeführt wird. Wenn Sie Ihre Unterklassen erweitern möchten, ist die Technik immer noch gültig (obwohl Sie wahrscheinlich Ihre Zwischenklassen generisch machen müssen).

Der resultierende Code ist:

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(); 
    } 
} 
+0

der Code wird auf diese Weise sauberer, und ich werde einige Zeit dauern, um Angelika Artikel zu lesen, thx vm! –

+2

'Klasse Snake erweitert Pet {@Override geschützt Cat getThis() {return new Cat();}} – immibis

+1

Das wird ein bisschen schwierig, wenn Sie nicht abstrakte, nicht-finale Klassen haben, die in der Mitte sind und brauchen um eine Instanz zu erstellen. Nehmen wir beispielsweise an, Sie hätten eine 'statische Klasse Pudel, die Hund ' erweitert und 'Hund' zu' statische Klasse Hund > Haustier '' geändert hat. Es wäre schwierig, eine rohe Instanz von "Hund" zu erstellen. – Marcus

9

Nein, nicht wirklich. Sie könnten durch die Verwendung kovarianten Rückgabetypen (dank McDowell für den richtigen Namen) um es:

@Override 
public Cat setName(String name) { 
    super.setName(name); 
    return this; 
} 

(Covariant Rückgabetypen sind nur in Java 5 und höher, wenn das ein Problem für Sie.)

16

Wie diesen 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> 
{ 
    /* ... */ 
} 
+0

+1, ausgedrückt prägnanter als ich. Aber wie lange gibt es einen generischen Trick? –

+0

Aha, ich dachte, es würde etwas mit Generika geben, wusste nur nicht was. Vielen Dank! –

+0

@Steve B: Es ist nicht alt in Java (eigentlich glaube ich nicht, ich habe es in Java verwendet), aber es wurde in C++ für eine lange Zeit verwendet. –

4

Es ist ein bisschen gewunden, aber Sie dies mit Generika tun können:

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 
    } 

} 
2
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(); 
    } 

}

+0

@Steve B. - +1, wette mich darauf! –