@Seth -- Here is your example with generics. Since generics don't exist at runtime, I do not understand why you fear a loss of "flexibility". If you use generics, then you are just using Objects.
If you want F's behavior to vary based on the return type of G, then you would just declare your F to do something like F, easy peasy.
//=== Function.java
public interface Function<ReturnType, Type> {
ReturnType doWork(Type arg);
}
//=== SomethingWeird.java
import java.util.*;
// yo dawg, i heard you liked functions. so i put a function in yo'
// function, so you can derive while you derive.
public class SomethingWeird {
public static <FReturnType, FType, GType> List<FReturnType> collateOrSomething(
Iterable<GType> objects,
Iterable<Function<FReturnType, FType>> fList,
Iterable<Function<FType, GType>> gList
) {
List<FReturnType> results = new ArrayList<FReturnType>();
for (GType garg : objects) {
for (Function<FReturnType, FType> f : fList) {
for (Function<FType, GType> g : gList) {
results.add(f.doWork(g.doWork(garg)));
}
}
}
return results;
}
}
//=== SomethingWeirdTest.java
import java.util.*;
import org.junit.*;
import static org.junit.Assert.*;
public class SomethingWeirdTest {
// this is kinda silly, and...
public static class F1 implements Function<Integer, Double> {
@Override
public Integer doWork(Double arg) {
return arg.intValue();
}
}
// ...this has all kinds of autoboxing madness, but...
public static class F2 implements Function<Integer, Double> {
@Override
public Integer doWork(Double arg) {
double ceil = Math.ceil(arg);
return (int) ceil;
}
}
// ...why you'd want to do something like this is quite beyond me...
public static class G1 implements Function<Double, String> {
@Override
public Double doWork(String arg) {
return Math.PI * arg.length();
}
}
// ...ditto this...
public static class G2 implements Function<Double, String> {
@Override
public Double doWork(String arg) {
return Math.E * arg.length();
}
}
// oh, yeah, it was so we could test this weird thing
@Test
public void testCollateOrSomething() {
List<String> data = Arrays.asList("x", "xx", "xxx");
List<Function<Integer, Double>> fList = Arrays.asList(new F1(), new F2());
List<Function<Double, String>> gList = Arrays.asList(new G1(), new G2());
List<Integer> results = SomethingWeird.collateOrSomething(data, fList, gList);
assertEquals(12, results.size());
// x
assertEquals(3, (int) results.get(0));
assertEquals(2, (int) results.get(1));
assertEquals(4, (int) results.get(2));
assertEquals(3, (int) results.get(3));
// xx
assertEquals(6, (int) results.get(4));
assertEquals(5, (int) results.get(5));
assertEquals(7, (int) results.get(6));
assertEquals(6, (int) results.get(7));
// xxx
assertEquals(9, (int) results.get(8));
assertEquals(8, (int) results.get(9));
assertEquals(10, (int) results.get(10));
assertEquals(9, (int) results.get(11));
}
}