Цепочка методов + наследование не очень хорошо играют вместе?

Этот вопрос был задан в контексте C ++ но мне любопытно узнать о Java. Опасения по поводу виртуальных методов не применимы (я думаю), но если у вас есть такая ситуация:

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
}

В такого рода иерархии классов есть ли способ вернутьthis таким образом, что (эффективно) не вытесняет тип объекта?

 Ionuț G. Stan01 июл. 2009 г., 17:11
Теперь я понимаю, почему так много людей ненавидят Java.

Ответы на вопрос(5)

public class Pet<AnimalType extends Pet> {

private String name;
    public AnimalType setName(String name) {
       this.name = name; return (AnimalType)this; 
    }        
}

а также

public class Cat extends Pet<Cat> {

    public Cat catchMice() {return this;}

    public static void main(String[] args) {
        Cat c = new Cat().setName("bob").catchMice();
    }

}

 01 июл. 2009 г., 17:06
@ Стив Б. - +1, поспори со мной!
 14 мар. 2018 г., 22:12
РЕДАКТИРОВАТЬ: скопировать / вставить ваш код сделал свое дело. Я понял, что мой базовый класс былX extends Y вместоX extends Y<X>, Это решило это!

Нет, не совсем. Вы можете обойти это, используя ковариантные возвращаемые типы (спасибо McDowell за правильное имя):

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

(Ковариантные возвращаемые типы доступны только в Java 5 и выше, если это вас беспокоит.)

Это немного запутанно, но вы можете сделать это с помощью обобщений:

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
    }

}
Решение Вопроса

Если вы хотите избежать непроверенных предупреждений о приведении от вашего компилятора (и не хотите @SuppressWarnings (& quot; unchecked & quot;)), то вам нужно сделать немного больше:

Прежде всего, ваше определение Pet должно быть самореференциальным, потому что Pet всегда является родовым типом:

abstract class Pet <T extends Pet<T>>

Во-вторых,(T) this приведение в setName также не проверяется. Чтобы избежать этого, используйте & quot; getThis & quot; техника в отличномGenerics FAQ от Angelika Langer:

The "getThis" trick provides a way to recover the exact type of the this reference.

Это приводит к приведенному ниже коду, который компилируется и запускается без предупреждений. Если вы хотите расширить свои подклассы, этот метод все еще остается в силе (хотя вам, вероятно, потребуется обобщить ваши промежуточные классы).

Полученный код:

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();
  }
}
 18 июл. 2015 г., 08:44
class Snake extends Pet<Cat> {@Override protected Cat getThis() {return new Cat();}}
 15 нояб. 2015 г., 01:09
Это становится немного сложнее, когда у вас есть неабстрактные, не финальные классы, которые находятся посередине и должны создать экземпляр. Например, предположим, у вас былstatic class Poodle extends Dog<Poodle>и изменилDog бытьstatic class Dog<T extends Dog<T>> extends Pet<T>, Теперь создаем сырой экземплярDog было бы сложно.
 17 февр. 2015 г., 15:41
таким образом код становится чище, и я потрачу некоторое время на прочтение полной статьи Angelika, thx vm!
 30 апр. 2017 г., 02:29
Я создал вещь, которая работала:class PetAnnon extends Pet<PetAnnon>{} и на каждом анонимном классе я просто использую его какnew Pet<PetAnnon>{... сейчас такие методы<T> T get(Class<T> cl){return (T)this.val;} будет работать снова.
 30 апр. 2017 г., 02:05
Есть ли способ, которым он может работать с анонимными классами? Я не могу найти способ, чтобы позволить себе ссылку на дженерики T: /

Как насчет этого старого трюка:

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>
{
    /* ... */
}
 01 июл. 2009 г., 18:17
@ Стив Б: Он не старый в Java (на самом деле, я не думаю, что видел его в Java), но он долгое время использовался в C ++.
 01 июл. 2009 г., 17:14
+1, выразился более лаконично, чем я. Но с учетом того, как давно существуют дженерики java, сколько лет может быть трюком?
 Jason S01 июл. 2009 г., 18:03
ага, я подумал, что будет что-то с дженериками, просто не знаю что. Спасибо!
 Jason S01 июл. 2009 г., 18:33
Хммм. Не могли бы вы также добавить пример с апскейтингом и апскейтингом? например Домашние животные & л;? & GT; пэт = с; ((Кошка) домашнее животное) .catchMice (); (у меня есть это право?)

Ваш ответ на вопрос