2009-07-01 11 views
30

This question has been asked in a C++ context pero tengo curiosidad acerca de Java. Las preocupaciones acerca de los métodos virtuales no se aplican (creo), pero si usted tiene esta situación:Método de encadenamiento + herencia no funcionan bien juntas?

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 
} 

En este tipo de jerarquía de clases, ¿hay alguna manera de volver this de una manera que no lo hace (efectivamente) upcast el tipo de objeto?

+4

Ahora entiendo por qué tanta gente odia Java. –

Respuesta

47

Si se quiere evitar advertencias elenco sin control de su compilador (y no desea @SuppressWarnings ("sin marcar")), entonces usted tiene que hacer una poco más:

en primer lugar, su definición de mascotas deben ser auto-referencial, debido a las mascotas es siempre un tipo genérico:

abstract class Pet <T extends Pet<T>> 

En segundo lugar, el molde (T) this en setName también está desmarcado. Para evitar esto, utilice la técnica de "getThis" en el excelente Generics FAQ by Angelika Langer:

El truco "getThis" proporciona una manera de recuperar el tipo exacto de la referencia this .

Esto da como resultado el siguiente código, que compila y funciona sin advertencias. Si desea extender sus subclases, entonces la técnica aún se mantiene (aunque probablemente necesite genéricos sus clases intermedias).

El código resultante es:

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

el código se vuelve más limpio de esta manera, y me tomaré un tiempo para leer el artículo completo de Angelika, thx vm! –

+2

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

+1

Esto se vuelve un poco complicado cuando tienes clases no abstractas, no finales que están en el medio y necesitan para crear una instancia. Por ejemplo, supongamos que tiene una 'clase estática Poodle extiende perro ', y cambió 'Perro' para que sea' estática clase Perro > extiende mascota '. Ahora sería difícil crear una instancia sin formato de 'Perro '. – Marcus

9

No, realmente no. Se podría evitar mediante el uso de tipos de retorno covariantes (gracias a McDowell para el nombre correcto):

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

(tipos de retorno covariantes están sólo en Java 5 o superior, si eso es una preocupación para usted.)

16

¿Qué hay de este viejo truco:

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, expresado de manera más concisa que yo. Pero dado el tiempo que han pasado los genéricos de java, ¿qué tan viejo truco podría ser? –

+0

aha, pensé que habría algo con los genéricos, simplemente no sabía qué. ¡Gracias! –

+0

@Steve B: No es antiguo en Java (en realidad, no creo haberlo visto utilizado en Java), pero se ha utilizado en C++ durante mucho tiempo. –

4

es un poco complicado, pero se puede hacer esto con los genéricos:

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

y

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, ¡apueste a eso! –

Cuestiones relacionadas