Create an abstract collection from an abstract collection

This problem has been listening to me for a while. Absolutely speaking, regardless of the language, there are often situations when you want to have this method:

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

Collection is now in this case an abstract class (e.g. IList in C # or List in Java) with several implementations. I was wondering what exactly is the correct procedure for creating an abstract collection?

Is it possible to create a specific collection inside a method and return it? How:

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

This, of course, creates a restriction on the resulting set and will create problems if, for some reason, we want to distinguish the result of the method from another specific collection than the one used inside the method.

Or use reflection to define a specific concrete type c and instantiate this class:

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

For some reason, this does not seem to me very "elegant." I would really appreciate an understanding of this issue.

+4
source share
7 answers

(Speaking of java). The reason you are returning a Collection (interface), rather than a specific type (e.g. ArrayList), is because you are telling the user that they do not need to care about which actual specific type is used. This gives you the opportunity to choose the appropriate type for your library / api.

If you are applying a specific concrete class, you should return that particular class, not the interface.

Thus, they should not discard your return type for anything other than Collection. See When should I return an interface and when is a particular class? .

+4
source

In Java, there are actually some good examples of how to do this in the java.util.Collections class. Instead of taking the collection and returning the collection, the key methods take two collections: "src" and "dest". For example, look at the signature of the copy method:

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

It is the responsibility to create an instance of the destination for the caller.

I think you could do the same if you want to create a method that acts in the src collection and puts the results in the target collection (and not in the lists).

I agree with Matthew Farwell's answer that you probably just want to return the interface and use it, but at a time when you really need to work with a specific implementation class, you can do it the same way as the Collections class does it.

+3
source

One approach you can take is to create a Collection implementation that delegates calls to the original Collection . This puts off the potentially expensive filtering operation of a large Collection until you need to explicitly read the elements. It also saves memory.

Example

 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); } } 
+1
source

The caller must accept the specified collection type.

Instead, it must either copy the desired type or pass the desired type.

eg.

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

or

 filter(set2, set); // the target collection is passed. filter(list2, list); 
0
source

As far as I understand, you want to know how to create a method that accepts a shared list and returns another modified shared list.

So, my advice will be to use an abstract type that implements a method to change its state.

 IList<object> list = new List<object>(); list.Add(new object()); list.Remove(obj); 

Or, as shown above, create an instance of a list that implements IList (or the Java equivalent) with that instance and returns the result as IList

Edit

If you want to filter an element from the list for a new one, general tools can help (I don't know if this function exists in Java).

  public IList<T> Filter<T>(IList<T> list) { var result = new List<T>(); result.Add(list[0]); // Or whatever filtering method return result; } 
0
source

When asked about ConcreteCollection , this is definitely valid.
To worry about a specific collection being expected, there are several ways to solve the problem:

Change the return type of the method. Example:

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

Use polymorphism. Example:

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

Use some utilities to create the type. Example:

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

My answer helped. ^^

0
source

If you want your method to accept as many different types of collections as possible, and you want to be sure that the result is the same type of implementation as you, you can use the void method, which directly modifies the supplied collection. For instance:

 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"] } } 

The filter helper method accepts Iterable , the simplest type needed to iterate over something. Apply a filter to each element, and if the predicate (filter) returns false, remove this element from the base collection using Iterator.remove() .

The Predicate<T> interface here comes from Google. You can easily write your own if you do not want to import it. The only method required is apply(T) , which returns a boolean value. Either this, or just write your condition directly inside the loop and get rid of the second parameter.

This method is most effective if your source collection is volatile and you do not want to keep intermediate results.

Another option is to use Google Collections Collections2.filter(Collection<E>, Predicate<E>) , which returns Collection<E> , as in your question. Similarly, the Iterables class will do the same, but create lazy iterations where filters are applied only when the iteration is actually performed.

0
source

Source: https://habr.com/ru/post/1383868/


All Articles