Java Forum / General / April 2007
how to instantiate new return object of generic type
tom forsmo - 16 Apr 2007 13:49 GMT Hi
I have a problem with generics I cant seem to solve.
Consider this example pseudo-code:
public class Table<K, V> {
public class Elem<V> { String key; V value;
public getValue(); }
public V getElem(key, locale) {
Elem e = findElem(key);
V val = e.getValue(key);
return ( val ) } }
I want to add to some code to getElem() (not findElem()) that creates a completely new val instance/value of type V under certain circumstances. The problem is, how do I do this when I dont know the actual type of V is?
(For bizarre reasons this is the only way to do it, I think. The underlying functionality of findElem() can not be extended/changed to do what I want, because that would require an entire project of its own. But if anybody has any other suggestion on how to solve the problem without starting a new subproject, I'd be happy to hear it.)
regards
tom
Robert Klemme - 16 Apr 2007 14:23 GMT > Hi > [quoted text clipped - 25 lines] > completely new val instance/value of type V under certain circumstances. > The problem is, how do I do this when I dont know the actual type of V is? You can either use reflection or you define a generic interface for a factory object that will create a new object from the given instance. Something along the lines of
interface Factory<V> { V create(V template); }
Then your Table needs a member with this type to which the creation is delegated. A generic default implementation would probably just return the argument.
I'd use the factory approach as it is more flexible and simpler.
> (For bizarre reasons this is the only way to do it, I think. The > underlying functionality of findElem() can not be extended/changed to do > what I want, because that would require an entire project of its own. > But if anybody has any other suggestion on how to solve the problem > without starting a new subproject, I'd be happy to hear it.) Kind regards
robert
tom forsmo - 17 Apr 2007 07:29 GMT > You can either use reflection or you define a generic interface for a > factory object that will create a new object from the given instance. [quoted text clipped - 3 lines] > V create(V template); > } Ok, so how do I do actually create the new object and how do I find out what type of object I am to create?
creation example:
V val = new String(message);
is not legal, nor is casting it to V since V is unknown.
tom
Daniel Pitts - 17 Apr 2007 07:50 GMT > > You can either use reflection or you define a generic interface for a > > factory object that will create a new object from the given instance. [quoted text clipped - 14 lines] > > tom You have to create a explicit Factory for the types you want to create. interface Factory<V> { V create(V template); }
class Table<K, V> { private final Factory<V> factory; public Table(Factory<V> factory) { this.factory = factory; }
public V get(K key, Locale locale) { return factory.create(find(key)); } }
new Table<String, Number>( new Factory<Number>() { Number create(Number template) { return template == null ? Integer.valueOf(0) : Integer.valueOf(template.intValue()); } } ).get("something");
tom forsmo - 17 Apr 2007 09:16 GMT > You have to create a explicit Factory for the types you want to > create. [quoted text clipped - 7 lines] > } > ).get("something"); Ok, I see but unfortunately thats not going to work, because it depends on the client controlling the instantion of the Table its values. In this case its Hibernate that controls this, by reflection and such. Hibernate spits out a table of elements with values for which I have no idea what kind of type they are, and I have to modify the returned value if need be, without knowing the type.
(arrgh... If anybody wants a truck load of java consultants from BigIntlConsultingCo, you can have them for free!!! Just grab them and bag them and put them in your local zoo, out of harms way... Or send them to a zoo in Siberia, if you prefer.)
I'll be more specific, the underlying system stores value messages in a database controlled by hibernate. I am working on an architectural framework that does not control the specifics of the diverse implementations. the value messages are by default in our local langauge. And somebody forgot to consider that these messages must be available in other langauges as well. Since things are being used by many projects already, a rewrite of the original code is out of the question. So my solution is to add the locale functionality in the outermost code, so we dont have to change the underlying code. But as you might have gathered by now, since everything is in a generic type I can not instantiate a correct return object and pass it through the generic return type. This is where the problem lies.
Suggestions are wholeheartedly welcomed
tom
Hendrik Maryns - 17 Apr 2007 10:58 GMT tom forsmo schreef:
>> You have to create a explicit Factory for the types you want to >> create. [quoted text clipped - 34 lines] > > Suggestions are wholeheartedly welcomed I think you might get inspired by browsing through Jakarta Commons Collections, specifically the Functors package and how it is used in maps, e.g. DefaultedMap or LazyMap. Unfortunately, they aren’t generic. I have generified most of it for my own use and would be happy to share it here. Hm, maybe I should put that on my website. There is a sourceforge project that has generified JCC, but not as stringent as I’d like it.
H.
- -- Hendrik Maryns http://tcl.sfs.uni-tuebingen.de/~hendrik/ ================== http://aouw.org Ask smart questions, get good answers: http://www.catb.org/~esr/faqs/smart-questions.html
Robert Klemme - 17 Apr 2007 12:06 GMT > I'll be more specific, the underlying system stores value messages in a > database controlled by hibernate. I am working on an architectural [quoted text clipped - 8 lines] > can not instantiate a correct return object and pass it through the > generic return type. This is where the problem lies. The complete architecture is not fully clear to me, but maybe your framework is too generic.
The only other option that comes to mind at the moment is to try to clone the object or serialize and deserialize and use whatever succeeds.
robert
Hendrik Maryns - 17 Apr 2007 15:37 GMT Robert Klemme schreef:
>> I'll be more specific, the underlying system stores value messages in >> a database controlled by hibernate. I am working on an architectural [quoted text clipped - 14 lines] > The only other option that comes to mind at the moment is to try to > clone the object or serialize and deserialize and use whatever succeeds. Hm, here are some snippets from my generified JCC, they might give you some ideas. You’ll notice that @SuppressWarnings("unchecked") is used quite often. It is inevitable. For Javadoc, see the Jakarta website.
public interface Factory<T> {
public T create();
}
public class ConstantFactory<T> implements Factory<T>, Serializable {
/** Returns null each time */ @SuppressWarnings("unchecked") public static final Factory NULL_INSTANCE = new ConstantFactory(null);
private final T iConstant;
@SuppressWarnings("unchecked") public static <T> Factory<T> getInstance(T constantToReturn) { if (constantToReturn == null) { return NULL_INSTANCE; } return new ConstantFactory<T>(constantToReturn); }
public ConstantFactory(T constantToReturn) { super(); iConstant = constantToReturn; }
/** * Always return constant. * * @return the stored constant value */ public T create() { return iConstant; }
}
public class InstantiateFactory<T> implements Factory<T>, Serializable {
/** The class to create */ private final Class<? extends T> iClassToInstantiate; /** The constructor parameter types */ private final Class<?>[] iParamTypes; /** The constructor arguments */ private final Object[] iArgs; /** The constructor */ private transient Constructor<? extends T> iConstructor = null;
/** * Factory method that performs validation. * * @param classToInstantiate the class to instantiate, not null * @param paramTypes the constructor parameter types * @param args the constructor arguments * @return a new instantiate factory */ public static <T> Factory<T> getInstance(Class<T> classToInstantiate, Class<?>[] paramTypes, Object[] args) { if (classToInstantiate == null) { throw new IllegalArgumentException("Class to instantiate must not be null"); } if (((paramTypes == null) && (args != null)) || ((paramTypes != null) && (args == null)) || ((paramTypes != null) && (args != null) && (paramTypes.length != args.length))) { throw new IllegalArgumentException("Parameter types must match the arguments"); }
if (paramTypes == null || paramTypes.length == 0) { return new InstantiateFactory<T>(classToInstantiate); } else { paramTypes = paramTypes.clone(); args = args.clone(); return new InstantiateFactory<T>(classToInstantiate, paramTypes, args); } }
/** * Constructor that performs no validation. * Use <code>getInstance</code> if you want that. * * @param classToInstantiate the class to instantiate */ public InstantiateFactory(Class<? extends T> classToInstantiate) { super(); iClassToInstantiate = classToInstantiate; iParamTypes = null; iArgs = null; findConstructor(); }
/** * Constructor that performs no validation. * Use <code>getInstance</code> if you want that. * * @param classToInstantiate the class to instantiate * @param paramTypes the constructor parameter types, not cloned * @param args the constructor arguments, not cloned */ public InstantiateFactory(Class<? extends T> classToInstantiate, Class<?>[] paramTypes, Object[] args) { super(); iClassToInstantiate = classToInstantiate; iParamTypes = paramTypes; iArgs = args; findConstructor(); }
/** * Find the Constructor for the class specified. */ private void findConstructor() { try { iConstructor = iClassToInstantiate.getConstructor(iParamTypes);
} catch (NoSuchMethodException ex) { throw new IllegalArgumentException("InstantiateFactory: The constructor must exist and be public "); } }
public T create() { // needed for post-serialization if (iConstructor == null) { findConstructor(); }
try { return iConstructor.newInstance(iArgs);
} catch (InstantiationException ex) { throw new FunctorException("InstantiateFactory: InstantiationException", ex); } catch (IllegalAccessException ex) { throw new FunctorException("InstantiateFactory: Constructor must be public", ex); } catch (InvocationTargetException ex) { throw new FunctorException("InstantiateFactory: Constructor threw an exception", ex); } }
}
==> I guess this is probably what you want.
A more complicated approach (I got a lot of problems getting this to compile, it does in Eclipse):
public class PrototypeFactory {
@SuppressWarnings("unchecked") public static <T> Factory<T> getInstance(T prototype) { if (prototype == null) { return ConstantFactory.getInstance(null); } try { Method method = prototype.getClass().getMethod("clone", (Class[]) null); return new PrototypeCloneFactory<T>(prototype, method);
} catch (NoSuchMethodException ex) { try { prototype.getClass().getConstructor(new Class[] { prototype.getClass()}); return new InstantiateFactory<T>( (Class<T>) prototype.getClass(), // this used to compile without the cast?? new Class[] { prototype.getClass()}, new Object[] { prototype });
} catch (NoSuchMethodException ex2) { if (prototype instanceof Serializable) { return (Factory<T>) new PrototypeSerializationFactory<Serializable>((Serializable)prototype); } } } throw new IllegalArgumentException("The prototype must be cloneable via a public clone method"); }
private PrototypeFactory() { super(); }
// PrototypeCloneFactory
//----------------------------------------------------------------------- /** * PrototypeCloneFactory creates objects by copying a prototype using the clone method. */ static class PrototypeCloneFactory<T> implements Factory<T>, Serializable {
/** The object to clone each time */ private final Object iPrototype; /** The method used to clone */ private transient Method iCloneMethod;
private PrototypeCloneFactory(Object prototype, Method method) { super(); iPrototype = prototype; iCloneMethod = method; }
/** * Find the Clone method for the class specified. */ private void findCloneMethod() { try { iCloneMethod = iPrototype.getClass().getMethod("clone", (Class[]) null);
} catch (NoSuchMethodException ex) { throw new IllegalArgumentException("PrototypeCloneFactory: The clone method must exist and be public "); } }
@SuppressWarnings("unchecked") public T create() { // needed for post-serialization if (iCloneMethod == null) { findCloneMethod(); }
try { return (T) iCloneMethod.invoke(iPrototype, (Object[])null);
} catch (IllegalAccessException ex) { throw new FunctorException("PrototypeCloneFactory: Clone method must be public", ex); } catch (InvocationTargetException ex) { throw new FunctorException("PrototypeCloneFactory: Clone method threw an exception", ex); } } }
// PrototypeSerializationFactory
//----------------------------------------------------------------------- /** * PrototypeSerializationFactory creates objects by cloning a prototype using serialization. */ static class PrototypeSerializationFactory<T extends Serializable> implements Factory<T>, Serializable {
/** The object to clone via serialization each time */ private final T iPrototype;
private PrototypeSerializationFactory(T prototype) { super(); iPrototype = prototype; }
/** * Creates an object using serialization. * * @return the new object */ @SuppressWarnings("unchecked") public T create() { ByteArrayOutputStream baos = new ByteArrayOutputStream(512); ByteArrayInputStream bais = null; try { ObjectOutputStream out = new ObjectOutputStream(baos); out.writeObject(iPrototype);
bais = new ByteArrayInputStream(baos.toByteArray()); ObjectInputStream in = new ObjectInputStream(bais); return (T) in.readObject();
} catch (ClassNotFoundException ex) { throw new FunctorException(ex); } catch (IOException ex) { throw new FunctorException(ex); } finally { try { if (bais != null) { bais.close(); } } catch (IOException ex) { // ignore } try { if (baos != null) { baos.close(); } } catch (IOException ex) { // ignore } } } }
}
This illustrates how they are used, e.g. in a List. Similar for LazySet, LazyMap etc. For Maps, a similar approach is used, but with Transformers instead of Factories, e.g. they get an input: public interface Transformer<I,O> {
public O transform(I input);
}
public class LazyList<E> extends AbstractSerializableListDecorator<E> {
protected final Factory<E> factory;
public static <E> List<E> decorate(List<E> list, Factory<E> factory) { return new LazyList<E>(list, factory); }
//----------------------------------------------------------------------- protected LazyList(List<E> list, Factory<E> factory) { super(list); if (factory == null) { throw new IllegalArgumentException("Factory must not be null"); } this.factory = factory; }
//----------------------------------------------------------------------- /** * Decorate the get method to perform the lazy behaviour. * <p> * If the requested index is greater than the current size, the list will * grow to the new size and a new object will be returned from the factory. * Indexes in-between the old size and the requested size are left with a * placeholder that is replaced with a factory object when requested. * * @param index the index to retrieve */ @Override public E get(int index) { int size = getList().size(); if (index < size) { // within bounds, get the object E object = getList().get(index); if (object == null) { // item is a place holder, create new one, set and return object = factory.create(); getList().set(index, object); return object; } else { // good and ready to go return object; } } else { // we have to grow the list for (int i = size; i < index; i++) { getList().add(null); } // create our last object, set and return E object = factory.create(); getList().add(object); return object; } }
}
I hope you get some inspiration from this. H. - -- Hendrik Maryns http://tcl.sfs.uni-tuebingen.de/~hendrik/ ================== http://aouw.org Ask smart questions, get good answers: http://www.catb.org/~esr/faqs/smart-questions.html
Daniel Pitts - 18 Apr 2007 00:10 GMT > > You have to create a explicit Factory for the types you want to > > create. [quoted text clipped - 36 lines] > > tom Perhaps you should instead of adding a "get" method, create a "LocalizedTable" type, which delegates to Tablle.find, but it translates using the technique I've shown you.
Free MagazinesGet these publications absolutely FREE for up to 12 months. There are no hidden fees and no obligation. Simply choose a title, complete the application form and submit it. Read more ...
|
|
|