DRY: minimizing repetitive code in Java

I am writing a method in Java:

List<Foo> computeFooList(/* arguments */) { /* snip */ } 

I would like to write a second method with exactly the same logic, but with a different return type:

 List<String> computeStringList(/* same arguments */) { /* snip */ } 

I am trying to find a simple way to minimize the amount of re-code between these two methods. The logical difference is that when adding an object to the returned list, the first method adds acutal Foo :

 List<Foo> computeFooList(/* arguments */) { List<Foo> toReturn = ... ... for (Foo foo : /* some other list of Foo */) { if (/* some condition */) { toReturn.add(foo); } } ... return toReturn; } 

and the second adds a String representation of Foo :

 List<String> computeStringList(/* same arguments */) { List<String> toReturn = ... ... for (Foo foo : /* some other list of Foo */) { if (/* some condition */) { toReturn.add(foo.toString()); } } ... } 

In fact, it is not so simple. I do not want to add Foo to toReturn unless I'm sure it is there. As a result, this decision is made using auxiliary functions. With two different versions of methods, I will need different versions of auxiliary functions - in the end, I would write two sets of the same type of methods, but for one small generic type.


Can I write one method that contains all the decision logic, but can generate either List<Foo> or List<String> ? Is it possible to do this without using the raw types of List (wrong practice in generics of the earth!) Or wildcard List<?> Types? I imagine an implementation that looks something like this:

 List<Foo> computeFooList(/* args */) { return computeEitherList(/* args */, Foo.class); } List<String> computeStringList(/* args */) { return computeEitherList(/* args */, String.class); } private List<???> computeEitherList(/* args */, Class<?> whichType) { /* snip */ } 

Is there any beautiful, elegant way to do this? I played with generic methods, but I see no way to do this. Even the reflection cheating didn’t get anywhere (maybe I need something like TypeToken ? ... eww).

+7
source share
3 answers

Can you transform the transformation logic into a separate strategy (e.g. Guava Function<F, T> ):

 public <T> List<T> computeList(/* arguments */, Function<? super Foo, T> f) { List<T> toReturn = ... ... for (Foo foo : /* some other list of Foo */) { if (/* some condition */) { toReturn.add(f.apply(foo)); } } return toReturn; } 

computeFooList:

 computeList(..., Functions.identity()); 

computeStringList:

 computeList(..., Functions.toStringFunction()); 
+7
source

I have a "SearchFilter" interface and an abstract FilterAdapter class, which I use in ways like this. Decision logic can be performed independently and in the general case from the actual addition of things to the returned list. I would check every Foo and say that “true” includes it or “false” excludes it.

 public interface SearchFilter<T> { public boolean include(T item); public Collection<T> apply(Collection<T> items); } 

A filter can be applied to an existing collection using the apply() method, returning a new collection that includes only the necessary elements.

 newCollection = myfilter.apply(originalItems); 

This may not be useful to you, but the concept of include() should work well to avoid repeating the logic of the solution.

You may have a FooFilter extends FilterAdapter<Foo> (sometimes I also create an instance of this anonymous string) that provides an implementation of include

 public FooFilter extends FilterAdapter<Foo> { public boolean include(Foo item) { if (item.isInvalid()) return false; // or any other complex logic you want return item.hasGoodThings(); } } 

The apply() method is almost always a loop over the original collection and the include test, so it has a default implementation in my FilterAdapter , but can be overridden.

0
source

This is a little ugly, but I think it might work:

 List<Foo> computeFooList(/* args */) { return computeEitherList(/* args */, Foo.class); } List<String> computeStringList(/* args */) { return computeEitherList(/* args */, String.class); } private <T> List<T> computeEitherList(/* args */, Class<T> whichType) { List<T> rval = new ArrayList<T>(); for (Foo foo : listOfFoo) { // process foo if (whichType.equals(Foo.class)) { rval.add(whichType.cast(foo)); } else if (whichType.equals(String.class)) { rval.add(whichType.cast(foo.toString())); } else { throw new SomeException("Cannot compute list for type " + whichType); } } return rval; } 
0
source

All Articles