Создание абстрактной коллекции из абстрактной коллекции - PullRequest
4 голосов
/ 30 ноября 2011

Эта проблема меня давно беспокоила.Говоря абстрактно, независимо от языка, часто бывают ситуации, когда вы хотите иметь метод, подобный этому:

Collection method(Collection c) {
    // select some elements from c based on some filter 
    // and return a new collection
}

Теперь Collection в этом случае является неким абстрактным классом (как, скажем, IList в C #).или List на Java) с несколькими реализациями.Мне было интересно, что именно является правильной процедурой для создания абстрактной коллекции?

Можно ли создать конкретную коллекцию внутри метода и вернуть ее?Например:

Collection method(Collection c) {
   Collection cc = new ConcreteCollection();
   // select some elements from c based on some filter 
   return cc;
}

Это, конечно, накладывает ограничение на результирующую коллекцию и создает проблемы в случае, если по какой-то причине мы хотим привести результат метода к другой конкретной коллекции, чем та, которая использоваласьвнутри метода.

Или используйте отражение, чтобы определить фактический конкретный тип c и создать экземпляр этого класса:

Collection method(Collection c) {
   Collection cc = c.getClass().newInstance();
   // select some elements from c based on some filter 
   return cc;
}

По некоторым причинам это не кажется очень "элегантным"мне.Я был бы очень признателен за понимание этого вопроса.

Ответы [ 7 ]

4 голосов
/ 30 ноября 2011

(выступая за Java).Причина, по которой вы возвращаете Collection (интерфейс), а не конкретный тип (такой как ArrayList), заключается в том, что вы говорите пользователям, что им не нужно заботиться о том, какой именно конкретный тип используется.Это позволяет вам выбрать подходящий тип для вашей библиотеки / API.

Если вы применяете конкретный конкретный класс, то вам следует возвращать этот конкретный класс, а не интерфейс.

Таким образом, они не должны приводить ваш тип возврата к чему-либо другому, кроме Collection.См. Когда я должен вернуть интерфейс и когда конкретный класс? .

3 голосов
/ 30 ноября 2011

В Java есть несколько хороших примеров того, как это сделать в классе java.util.Collections.Вместо того, чтобы брать коллекцию и возвращать коллекцию, ключевые методы берут две коллекции: «src» и «dest».Например, посмотрите на подпись метода copy :

public static <T> void copy(List<? super T> dest, List<? extends T> src)

Это возлагает ответственность за создание списка получателей на вызывающего.

Я думаю, вы могли бы сделать то же самое, когда захотите создать метод, который будет работать с коллекцией src и помещать результаты в целевую коллекцию (а не в списки).

Я согласен сОтвет Мэтью Фаруэлла о том, что вы, вероятно, просто хотите вернуть интерфейс и использовать его, но для тех случаев, когда вам действительно нужно работать с конкретным реализующим классом, вы можете сделать это так же, как это делает класс Collections.

1 голос
/ 30 ноября 2011

Один из подходов, который вы можете использовать, - это создать реализацию Collection, которая делегирует вызовы исходной Collection.Это откладывает потенциально дорогостоящую операцию фильтрации больших Collection до тех пор, пока вам не понадобится явно прочитать элементы.Это также экономит память.

Пример

public interface Filter<T> {
  boolean include(T t);
}

public class FilterCollection<T> implements Collection<T> {
  private final Collection<T> orig;
  private final Filter<T> filter;

  public FilterCollection(Collection<T> orig, Filter<T> filter) {
    this.orig = orig;
    this.filter = filter;
  }

  public int size() {
    int sz = 0;

    for (T t : orig) {
      if (filter.include(t)) {
        ++sz;
      }
    }

    return sz;
  }

  public boolean contains(Object o) {
    return o instanceof T && filter.include((T) o) && orig.contains(o);
  }

  public boolean add(T t) {
    if (!filter.include(t)) {
      throw new IllegalArgumentException("Element lies outside filter bounds.");
    }

    orig.add(t);
  }
}
0 голосов
/ 30 ноября 2011

Если вы хотите, чтобы ваш метод принимал как можно больше разных типов коллекций, и вы хотите быть уверены, что результат будет того же типа реализации, что и вы, вы можете использовать метод void, который напрямуюизменяет предоставленную коллекцию.Например:

import com.google.common.base.Predicate;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;

public class Testy {

    private static <T> void filter(Iterable<T> collection, Predicate<T> filter) {
        Iterator<T> iterator = collection.iterator();
        while (iterator.hasNext()) {
            if (!filter.apply(iterator.next())) { // Condition goes here
                iterator.remove();
            }
        }
    }

    public static void main(String... args) {
        List<String> list = new ArrayList<String>();
        list.addAll(Arrays.asList("A", "B", "C", "D"));

        filter(list, new Predicate<String>() { // Anonymous filter (predicate)
            @Override public boolean apply(String input) {
                return input.equals("B");
            }
        });

        System.out.println(list); // Prints ["B"]
    }

}

Вспомогательный метод filter принимает Iterable, самый простой тип, необходимый для итерации по чему-либо.Примените фильтр к каждому элементу, и если предикат (фильтр) вернет false, удалите этот элемент из базовой коллекции с помощью Iterator.remove().

. Интерфейс Predicate<T> здесь исходит от Google.Вы можете легко написать свой собственный, если вы не хотите импортировать его.Единственный обязательный метод - apply(T), который возвращает логическое значение.Либо так, либо просто напишите ваше условие непосредственно в цикле и избавьтесь от второго параметра.

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

Другой вариант - использовать Google Collections Collections2.filter(Collection<E>, Predicate<E>), который возвращает Collection<E>, как и в вашем вопросе.Точно так же класс Iterables сделает то же самое, но создаст ленивые итерации, где фильтры применяются только при выполнении итерации.

0 голосов
/ 30 ноября 2011

На вопрос о ConcreteCollection это определенно допустимо.
Если вы хотите, чтобы ожидалась другая сборка бетона, есть несколько способов обойти проблему:

Изменить возвратТип метода.Пример:

ConcreteCollection method(Collection c){
    ConcreteCollection cc=new ConcreteCollection
    for(Object x: c){
        //do something
    }
    return cc
}

Используйте полиморфизм.Пример:

Collection x=method(c)
x.add(new Object) //add is a method defined within the abstract Collection

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

LinkedList h=Collections.toLinkedList(method(c))

Надеюсь, мой ответ помог.^^

0 голосов
/ 30 ноября 2011

Насколько я понимаю, вы хотите знать, как создать метод, который принимает универсальный список и возвращает другой модифицированный универсальный список.

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

IList<object> list = new List<object>();

list.Add(new object());
list.Remove(obj);

Или, как показано выше, создать экземпляр списка, который реализует IList (или эквивалент Java) работы с этим экземпляром и вернуть результат как IList

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

Если вы хотите отфильтровать какой-либо элемент из списка в новый, generics может помочь (я не знаю, существует ли эта функция вЯва).

    public IList<T> Filter<T>(IList<T> list)
    {
        var result = new List<T>();
        result.Add(list[0]); // Or whatever filtering method
        return result;
    }
0 голосов
/ 30 ноября 2011

Вызывающий должен предположить, что возвращен данный тип коллекции.

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

, например

Set<T> set2 = new HashSet<T>(filter(set));
List<T> list2 = new ArrayList<T>(filter(list));

или

filter(set2, set); // the target collection is passed.
filter(list2, list);
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...