Как перебрать этот универсальный список с подстановочными знаками? - PullRequest
10 голосов
/ 12 декабря 2010

У меня есть список объектов, расширяющих другой класс:

List<? extends Fruit> arguments;

Теперь я хочу вызвать метод для этих объектов.Вызывающий класс имеет метод wash для каждого из классов, расширяющих Fruit, но НЕ для абстрактного класса Fruit:

void wash( Apple a);
void wash( Peach p);

Как применить метод wash ко всем элементамв arguments?Это НЕ РАБОТАЕТ, так как мои методы стирки не принимают Fruit аргументы:

for( Fruit f: arguments)
    this.wash( f); // the wash() method is not a member of Fruit

Есть ли способ решить эту проблему без необходимости создания зонтичного метода wash( Fruit)?Потому что существуют десятки wash( ? extends Fruit) методов ...

.

EDIT : «Класс вызова», о котором я говорю, является посетителем.И я не могу изменить ни один из Fruit классов / подклассов.Я могу только программировать посетителя.Это означает, что невозможно добавить метод wash() (или любые другие методы в этом отношении) в абстрактный класс Fruit.

Ответы [ 3 ]

7 голосов
/ 12 декабря 2010

Добро пожаловать в мир Двойная динамическая отправка .

AFAIK, вы не можете сделать это легко на Java. Вы можете сделать это двумя способами: Quick'n'dirty и Visitor way:

Quick'n'dirty

Вам необходимо задать тип объекта, поэтому вам понадобится метод стирки для Fruit, который перенаправит вызов нужной функции в соответствии с ее типом:

public void wash(Fruit f)
{
   if(f instanceof Apple)
   {
      wash((Apple) f) ;
   }
   else if(f instanceof Peach)
   {
      wash((Peach) f) ;
   }
   else
   {
      // handle the error, usually through an exception
   }
}

Проблема с quick'n'dirty заключается в том, что компилятор не скажет вам, что существует новый действующий Fruit (например, Orange), который в настоящее время не обрабатывается методом стирки.

посетителей

Вы можете использовать шаблон посетителя для фруктов:

public abstract class Fruit
{
   // etc.
   public abstract void accept(FruitVisitor v) ;
}

public class Apple extends Fruit
{
   // etc.
   public void accept(FruitVisitor v)
   {
      v.visit(this) ;
   }
}

public class Peach extends Fruit
{
   // etc.
   public void accept(FruitVisitor v)
   {
      v.visit(this) ;
   }
}

И определить посетителя как:

public interface class FruitVisitor
{
   // etc.

   // Note that there are no visit method for Fruit
   // this is not an error

   public void visit(Apple a) ;
   public void visit(Peach p) ;
}

А потом, посетитель вашего кейса:

public class FruitVisitorWasher : implements FruitVisitor
{
   // etc.

   // Note that there are no visit method for Fruit
   // this is not an error

   // Note, too, that you must provide a wash method in
   // FruitVisitorWasher (or use an anonymous class, as
   // in the example of the second edit to access the
   // wash method of the outer class)

   public void visit(Apple a)
   {
      wash(a) ;
   }

   public void visit(Peach p)
   {
      wash(p) ;
   }
}

В итоге ваш код может быть

FruitVisitorWasher fvw = new FruitVisitorWasher() ;

for( Fruit f: arguments)
{
   f.accept(fvw) ;
}

Et voilà ...

Шаблон Visitor имеет то преимущество, что компилятор сообщит вам, если вы добавили другой Fruit (например, Orange), в котором вы закодировали метод accept, и если вы забыли обновить шаблон FruitVisitor для его поддержки.

И, наконец, шаблон Visitor расширяемый: вы можете иметь FruitVisitorWasher, FruitVisitorEater, FruitVisitorWh независимо, добавляя их без необходимости изменять Fruit, Apple, Peach и т.д ..

Одна ловушка, однако, вы должны вручную написать в каждом классе Fruit метод accept (который является действием копирования / вставки), потому что именно этот метод выполняет всю работу по «знанию» правильного типа Fruit.

Редактировать

Если вы выберете решение Quick'n'dirty, решение Самуэля Парсонэга может оказаться даже лучше, чем у меня:

Как перебрать этот универсальный список с подстановочными знаками?

, который использует рефлексию Java (я программист на C ++, поэтому рефлексия не является естественным решением ... Мне плохо в этом ...). Я нахожу его решение довольно элегантным, даже если оно пахнет каким-то образом (все проверки будут выполняться во время выполнения, так что вам лучше убедиться, что все в порядке ... Опять же, на фоне C ++: если что-то можно сделать, или ошибка может быть обнаружен во время компиляции, поэтому следует избегать его перемещения во время выполнения)

Редактировать 2

Джон Ассимптот прокомментировал:

Шаблон Visitor, когда вы пишете, не подходит, так как я не могу добавить метод стирки в Fruit.

Так что я предложу встроенный код, чтобы доказать, что wash () не должен находиться внутри Fruit для работы.

(я превратил FruitVisitor из абстрактного класса в интерфейс, что лучше)

Давайте представим, что цикл for находится внутри метода bar класса Foo, который имеет собственный метод мытья:

public class Foo
{
   public wash(Apple a) { /* etc. */ }
   public wash(Peach p) { /* etc. */ }
   public bar(List<? extends Fruit> arguments)
   {
      for( Fruit f: arguments)
      {
         wash(f) ; // we wand the right wash method called.
      }
   }
}

Вы хотите вызвать правильный метод стирки, поэтому приведенный выше код не будет работать правильно.

Давайте повторно использовать шаблон FruitVisitor для исправления этого кода. Мы будем использовать анонимный класс внутри метода bar:

public class Foo
{
   public void wash(Apple a) { System.out.println("Apple") ; }
   public void wash(Peach p) { System.out.println("Peach") ; }

   public void bar(List<? extends Fruit> arguments)
   {
      FruitVisitor fv = new FruitVisitor()
      {
         public void visit(Apple a)
         {
            wash(a) ; // will call the wash method
                      // of the outer class (Foo)
         }

         public void visit(Peach p)
         {
            wash(p) ; // will call the wash method
                      // of the outer class (Foo)
         }
      } ;

      for(Fruit f: arguments)
      {
         f.accept(fv) ;
      }
   }
}

Теперь это работает, и во Фруктах нет метода стирки.

Обратите внимание, что этот код был протестирован на 1.6 JVM, поэтому я могу предоставить полный код при необходимости.

4 голосов
/ 12 декабря 2010

Это возможно, используя отражение

Попробуйте это

Method m=this.getClass().getMethod("wash", f.getClass());
m.invoke(this, f.getClass().cast(f));
0 голосов
/ 12 декабря 2010

Попробуйте изменить

void wash( Apple a);

до

void wash(List<? extends Fruit> list);

А затем используйте первый элемент в методе стирки.

Вам все еще нужен метод wash () в классе Fruit.

метод стирки в классе Fruit будет абстрактным и должен быть определен в подклассах.

...