Search code examples
javagenericsgeneric-programming

returning different types from a java method


Let's say I've a java base class:

class base {
    public Class<? extends base> type;
    // ...
}

type stores the classtype of classes which inherit from base

There's another class which stores base objects in a container, it also has a get method which returns objects stored in that container:

class container {
    private Vector<base> v;
    //...
    public <something here> get(int i){
        base b=v.get(i);
        return b.type.cast(b);
    }

How do I implement such a function(container.get), which casts the object to it's correct type before returning it? It would be nice to be able to program in a cascading style:

JSONObject o;
//...
o.get("nestedObject").get("array").get(3);

Solution

  • You may be misunderstanding what a "cast" is in Java. Except for casts that involve primitive types (such as casting an int to a float), casts don't change objects at all.

    One of your instance variables is

    private Vector<Base> v;
    

    (Please use the Java convention and start class names with upper-case letters. I've changed base to Base to set a good example.)

    The objects that you put in this vector can be of type Base or any subclass of Base. The type of the object is fixed when the object is constructed. Suppose Base has three subclasses, Child1, Child2, and Child3. Those objects can be construted with new Child1(...), new Child2(...), new Child3(...). As soon as you say new Child1(...), you have an object whose type is Child1, and you can't do anything to change that. But since a Child1 is a Base, you can use your Child1 anywhere you can use a Base, such as putting it into your vector v.

    Casting just causes the compiler to look at a variable differently. Suppose you say

    Base x = v.get(i);
    

    x (if not null) will be a Base or some object of a subclass. The type of x will be whatever you used to construct the object. Now if you say

    Child1 y = (Child1)x;
    

    If x is a Child1, y will be a reference to the same object as x. But the compiler will know that y is a Child1, and you can access new methods that are defined in a Child1. (If x isn't a Child1, you get an exception.) But it's important to note that a cast does not create a new object and it does not change the type of anything. It just tells the compiler to look at a variable or expression in a different way.

    Given this, you cannot gain anything by trying to write a method that returns a "variable" type. You may as well just write

    public Base get(int i){
        return v.get(i);
    }
    

    The object that it returns could be a Base, Child1, Child2, etc.; it will be the same type as the object was when you put it into the vector. When you call get, if you are expecting the result to be a Child1, you can say

    Child1 child = (Child1)container.get(n);
    

    which will throw an exception if the object isn't a child1. Or you can use instanceof to check yourself:

    Base child = container.get(n);
    if (child instanceof Child1) {
        ...
    }
    

    If this isn't good enough, you'll need to explain more clearly what you want to accomplish and why this won't work.

    MORE: After reading your edit, that says you want to do something like this:

    JSONObject o;
    //...
    o.get("nestedObject").get("array").get(3);
    

    You don't need any casting to accomplish this. What you need is an abstract JSONValue base type that represents any kind of value that can be returned from a JSON parser. The subclasses would be things like "object", "array", and whatever scalar types you need (integer, string, etc.). get would return JSONValue. In actuality, it will return an object of one of the other types, but all the compiler needs to know is that it returns JSONValue.

    Now a JSONValue will need two get methods, one that takes a String and one that takes an int or Integer. These will be polymorphic methods. Say v is a JSONValue and you say v.get("array"): the method it actually calls may be different based on the actual type of v. So if v is your "object" type, it will call the method you've defined for the object type; if v is an integer type, it will call the method defined for the integer type, and so on. What you want is for the get method that takes a String to look up the field for the JSON "object" type, and throw an exception for everything else. Similarly, for the get method that takes an integer parameter, the method for the JSON "array" type will look up a list or something, and the method for every other subclass of JSONValue will throw an exception. No "casting" is needed for this. Casting is a compile-time concept. At all points, the compiler will know only that it's working with a JSONValue. But at run time, the object's actual type is used to determine which method to call, so the compiler doesn't need to know anything more.

    This is a basic concept that all Java programmers need to know (also Javascript, Python, and pretty much every other language these days, although polymorphism is handled differently in interpreted languages like Javascript and Python). The tutorial https://docs.oracle.com/javase/tutorial/java/IandI/index.html covers this and related concepts. There may be better Java tutorials on "polymorphism" out there also.