Often is the case that you really do want a method to return multiple values as different client require different parts back. This is often simpler than creating multiple methods; but it might not be worth creating a bean class for. For simple two value return types you can use a nice generic version of Pair; but things get harder if you have more value. (You can use a holder class to simulate IN/OUT parameters, but it doesn't result in pretty code) I was working on a method that needed to return four values depending on the calling context so I turned to a type safe heterogeneous container from the Effective java book:
/** * An implementation of a type safe heterogeneous container, taken from * effective java */ public class THC { final private Map<Class<?>, Object> container = new HashMap<Class<?>, Object>(); public <T> void put(Class<T> key, T value) { if (key==null) throw new NullPointerException("Key cannot be null"); container.put(key, value); } public <T> T get(Class<T> key) { // Return the value of think the thunk if required // Object object = container.get(key); return key.cast(object); } }
It doesn't provide full compile time safety; but it is close enough for our purposes. You can write code that looks like:
THC thc = getSSLConfiguration(...); SSLContext sslContext = thc.get(SSLContext.class); // or in another case KeyManager kms[] = thc.get(KeyManager[].class); TrustManager tms[] = thc.get(TrustManager[].class);
So this is fine, except as you see from the example above that depending on the context I need different subset of the objects. Some are derived from others so I could end up doing extra work to populate a part of the return value that is never used.
I decided to look at using a Thunk as a way to optimize the work done on the return value of this method. So I modified the THC class to include a new public interface Thunk with one method that takes a reference to the original container.
/** * An implementation of a type safe heterogeneous container, taken from * effective java */ public class THC { /** * Allow the client to provide a derived value * @param <T> */ public interface Thunk<T> { public T get(THC that); } /** * Place a value in the structure that has yet to be derived. * @param key * @param value The value to be thought of in the future */ public <T> void put(Class<T> key, Thunk<T> value) { if (key==null) throw new NullPointerException("Key cannot be null"); container.put(key, value); } public <T> T get(Class<T> key) { // Return the value of thinking the thunk if required // Object object = container.get(key); if (object instanceof Thunk) { object = ((Thunk)object).get(this); container.put(key, object); } return key.cast(object); } }
You find that because Thunk takes a parameter of the THC the adapter classes can be constants, this saves a little bit on object creation. So I can write something like:
static final private Thunk<SSLContext> contextT = new Thunk<SSLContext>() { public SSLContext get(THC that) { SSLContext c = SSLContext.getInstance("SSL"); c.init(that.get(KeyManager[].class) that.get(TrustManager[].class), null); return c; } } static final private Thunk<SSLSocketFactory> factoryT = new Thunk<SSLSocketFactory>() { public SSLSocketFactory get(THC that) { return that.get(SSLSocketFactory.class).getSSLSocketFactory(); } } public THC getSSLConfiguration(...) { ... THC thc = new THC(); thc.put(KeyManager[].class, kms); thc.put(TrustManager[].class, tms); thc.put(SSLContext.class, contextT); thc.put(SSLSocketFactory.class, factoryT); }
So I can now write my client code as follows and the SSLContext and SSLSocketFactory are created on demand:
THC context = getSSLConfiguration(...); SSLSocketFactory sf = context.get(SSLSocketFactory.class);
Perhaps not a technique that is applicable to all situations; but interesting all the same.
Update: Some time later it occurred that the THC interface if of course not particularly pretty, so an obvious extension is to have THC return a Proxy based on a interface that can be used to define the return types. Not written this yet; but it would be pretty trivial to do.