简体   繁体   中英

How to “dynamically” cast an instance of Object type into its specific data type?

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?

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();
}

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.

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.

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.

It doesn't make any sense to do this in Java. Java is statically typed, if you were to cast it dynamically you MUST have a switch statement to do so in order to call the different methods on the different objects.

Example--if you have a string or an int and you want to "Dynamically" cast it (without a switch), What operation can you do to both of them that doesn't require different code.

I guess I'm saying that if you have to cast because you want access to something that is different about the two objects (a different method), then how can you actually access that different method without a switch?

The one exception might be intrinsic variables--for those you want generics, but using intrinsic variables outside of classes is a bad idea anyway.

Oh, What you probably really want is to have all the classes implement the same interface--then you don't cast.

Casting should be extremely rare.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM