How to "dynamically" cast an instance of Object type into its specific data type?
Solution 1
The best way to handle this efficiently and cleanly is to have foo return a holder class for the object.
abstract class Holder<T> {
private final T object;
protected Holder(T object) { this.object = object; }
public T get() { return object; }
public abstract void doSomething();
}
public Holder foo(int opt) {
if (opt == 0) return new Holder<String>("") {
public void doSomething() { }
};
else if (opt == 1) return new Holder<Integer>(1) {
public void doSomething() { }
};
else if (opt == 2) return new Holder<Double>(1.0) {
public void doSomething() { }
};
// many more
}
public static void main(String... args) throws IOException {
Holder h = foo(x); //x is a value obtained during runtime, e.g. from user input
//now I want to call doSomething method
h.doSomething();
}
Solution 2
Basically you want overload resolution performed at execution time - you're not going to be able to do that very simply.
In some cases, the visitor pattern can help, but I don't think it would here. I think you're stuck with either the code that you've got here, or reflection. I've never been as keen on the visitor pattern as some of my colleagues - it always feels a little messy - but it's worth a thought.
Could you make foo
call the right doSomething
overload instead of just returning the value? That's the bit of code which knows what's being constructed - if you could pass it an object to call doSomething
on with the appropriate overload, you'd end up with the type-specific logic all in one place.
In Java 7 it's possible that invokedynamic will be useful in this sort of situation - certainly the dynamic
type in C# 4 would help - but I haven't looked into invokedynamic enough to say for sure.
Solution 3
The problem here may be one of separation of concerns. Java is an object-oriented language, and it may help to try and solve the problem in an object-oriented way. In this case, you might ask why main should care what kind of Object o is. Instead, you might consider having a set of classes, each of which knows how to do something in its own way.
abstract class Thing {
abstract void doSomething();
}
class IntegerThing extends Thing {
public void doSomething() { /*whatever*/ };
}
class FloatThing extends Thing {
public void doSomething() { /*whatever*/ };
}
//Then later:
int foo(int type) {
if(type == 0) return new IntegerThing(0);
if(type == 1) return new FloatThing(7.5);
if(type == 3) return new StringThing("Florence");
}
int main(String args[]) {
Thing something = foo(x);
something.doSomething();
}
Your foo() method effectively becomes a factory, and from that point forwards you no longer need to care about what kind of Thing has been returned by foo.
Solution 4
Java reflection helps somewhat, but there is a missing piece of data. Also, reflection typically throws MANY checked exceptions that you will need to catch. (I included a list after the code)
What is the object that holds the "doSomething" methods? In this example, I use the variable name "someObject" to represent the object holding the "doSomething" method. You need to substitute this for something more sensical.
Also, just a warning, this will not catch derived types, so if the method definition doesn't match the type given, you will get a method not found exception.
//now I want to call doSomething method
// (1)
Method method = someObject.getClass.getMethod("doSomething",new Class[] {o.getClass()});
method.invoke(someObject, new Object[] {o});
// (2)
Warning: You need to deal with the following exceptions when using reflection this way: (This is not an unusual list incidentally, reflection is typically very noisy in terms of exceptions)
NoSuchMethodException - if a matching method is not found or if the name is "<init>"or "<clinit>".
NullPointerException - if name is null
SecurityException - if access to the information is denied.
IllegalAccessException - if this Method object enforces Java language access control and the underlying method is inaccessible.
IllegalArgumentException - if the method is an instance method and the specified object argument is not an instance of the class or interface declaring the underlying method (or of a subclass or implementor thereof); if the number of actual and formal parameters differ; if an unwrapping conversion for primitive arguments fails; or if, after possible unwrapping, a parameter value cannot be converted to the corresponding formal parameter type by a method invocation conversion.
InvocationTargetException - if the underlying method throws an exception.
NullPointerException - if the specified object is null and the method is an instance method.
ExceptionInInitializerError - if the initialization provoked by this method fails.
Comments
-
Pahlevi Fikri Auliya almost 2 years
public Object foo(int opt){ if (opt == 0) return new String(); else if (opt == 1) return new Integer(1); else if (opt == 2) return new Double(1); else if ... .. and many more } public void doSomething(String s){..} public void doSomething(Integer i){..} public void doSomething(Double d){..} ... and many more doSomething method public static void main(String[] args){ ... Object o = foo(x); //x is a value obtained during runtime, e.g. from user input //now I want to call doSomething method // (1) if (o instanceof String) doSomething((String) o); else if (o instanceof Integer) doSomething((Integer) o); else if (o instanceof Double) doSomething((Double) o); ... // (2) }
Is there any better way to simplify statements enclosed by (1) ... (2)?
Does Java Reflection help? -
Joachim Sauer about 13 yearsAs far as I know the Java language doesn't have any way to actually use the
invokedynamic
bytecode. I'd like to be proven wrong on this one, 'though. -
Jon Skeet about 13 years@Joachim: Ah, I thought that was one of the proposed language changes in Java 7. I've lost track of what's in and what's out, to be honest...
-
jeffry copps almost 7 yearsReturn type of foo has to be Thingy. :)