2009-07-01 14 views
30

This question has been asked in a C++ context ale jestem ciekawy o Javie. Obawy o wirtualnych metod nie stosuje się (chyba), ale jeśli masz taką sytuację:Metoda łańcuchowa + dziedziczenie nie grają dobrze razem?

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 
} 

w tego rodzaju klasowej hierarchii, czy jest jakiś sposób, aby powrócić this w sposób, który nie (skutecznie) upcast the typ obiektu?

+4

Teraz rozumiem, dlaczego tak wielu ludzi nienawidzi Java. –

Odpowiedz

47

Jeśli chcesz uniknąć niesprawdzony ostrzeżeń odlewanych z kompilatora (i nie chcą @SuppressWarnings ("niekontrolowanych")), to trzeba zrobić trochę więcej:

Przede wszystkim, twoja definicja Pet musi być self-więzy, ponieważ Pet zawsze jest typ ogólny:

abstract class Pet <T extends Pet<T>> 

Po drugie, odznaczenie (T) this rzutowania w setName również nie jest zaznaczone. Aby tego uniknąć, należy zastosować technikę „getThis” w doskonałej Generics FAQ by Angelika Langer:

W „getThis” trick zapewnia sposób odzyskać dokładny typ tego odniesienia.

Powoduje to powstanie kodu poniżej, który kompiluje i działa bez ostrzeżeń. Jeśli chcesz rozszerzyć swoje podklasy, technika ta nadal trwa (chociaż prawdopodobnie będziesz potrzebował uogólnić swoje klasy pośrednie).

Otrzymany kod jest:

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

kod staje się w ten sposób czystszy, a ja zajmie mi trochę czasu, aby przeczytać cały artykuł Angeliki, thx vm! –

+2

'class Snake extends Pet {@Override protected Cat getThis() {return new Cat();}}' – immibis

+1

To staje się nieco trudne, gdy masz nie abstrakcyjne, nierealne klasy, które są w środku i potrzebują aby utworzyć instancję. Np. Przypuśćmy, że masz "pudel klasy statycznej, który rozszerza Doga " i zmieniłeś 'Pies' na 'statyczną klasę Dog > extends Pet '. Teraz tworzenie surowej instancji "psa" byłoby trudne. – Marcus

9

Nie, niezupełnie. Można to obejść za pomocą kowariantna Rodzaje zwrotu (dzięki McDowell dla prawidłowego nazwa):

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

(typy powrotne kowariantna są tylko w Javie 5 i wyżej, jeśli jest to problem dla ciebie.)

16

Jak o tym starym 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, wyrażone bardziej zwięźle niż ja. Ale biorąc pod uwagę, jak długo istniały generyczne java, ile lat to może być sztuczka? –

+0

aha, pomyślałem, że będzie coś z rodzajami, po prostu nie wiem co. Dzięki! –

+0

@Steve B: Nie jest stary w Javie (właściwie nie sądzę, bym go widział w Javie), ale był używany w C++ przez długi czas. –

4

to trochę skomplikowane, ale można to zrobić z rodzajowych:

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; 
    }   
} 

i

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, dodaj mnie! –

Powiązane problemy