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

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

List<? extends Fruit> arguments;

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

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

Как я могу применить метод для всех элементов в 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/subclasses. Я могу запрограммировать посетителя. Это означает, что невозможно добавить метод wash() (или любые другие методы, если на то пошло) к абстрактному классу Fruit.

Ответ 1

Добро пожаловать в мир Double Dynamic Dispatch.

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), который в настоящее время не обрабатывается методом стирки.

для посетителей

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

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à...

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

И тогда шаблон посетителя расширяем: у вас может быть FruitVisitorWasher, FruitVisitorEater, FruitVisitor, независимо от того, добавив их без необходимости изменять Fruit, Apple, Peach и т.д.

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

Изменить

Если вы займетесь решением Quick'n'dirty, решение Samuel Parsonage может быть даже лучше моего:

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

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

Изменить 2

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

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

Итак, я предлагаю встроенный код для подтверждения стирания(), как ожидается, не будет внутри Fruit to work.

(я изменил 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) ;
      }
   }
}

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

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

Ответ 2

Это возможно с помощью отражения

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

Method m=this.getClass().getMethod("wash", f.getClass());
m.invoke(this, f.getClass().cast(f));

Ответ 3

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

void wash( Apple a);

to

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

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

Вы все равно должны иметь метод wash() в классе Fruit.

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