April 17, 2014
Hot Topics:
RSS RSS feed Download our iPhone app

Java Needs to Get a Pair (and a Triple...)

  • April 1, 2009
  • By Dick Wall
  • Send Email »
  • More Articles »

Options for multiple return parameters in Java are somewhat limited. A method may only return one object, array or primitive, and unlike many other languages it does not offer an easy facility to use out parameters in method calls. In effect your options are to return an Array of Objects, a Collection, create a class just for the return parameters, or finally to pass in objects which you intend to alter. All of these have their drawbacks:

Using an Array of Objects

If you are lucky enough to have a homogeneous set of return parameters, then an array of Objects is a fine option with the exception that you have to remember which parameter is which when you unpack them. If, on the other hand, you are returning multiple different types of parameters, you will need to use an Array of a superclass of all of the objects—most likely Object itself. You will then need to cast each parameter as you unpack it. You have lost type safety and raised the chance of getting the order of the return parameters wrong as well.

Using a Collection

Similar to using an Array, you could create a collection to return. The main reason to use an Array over a Collection is the amount of code necessary to create a collection, which is much higher than using array initializers:

return new Object[] {string1, num2, object3}

is a lot shorter than

List<Object> retVal = new ArrayList<Object>();
retVal.add(string1);
retVal.add(num2);
retVal.add(object3);
return retVal;

There are no real advantages to using a collection over an array unless you decide to use a map in order to organize return values by a name or other key.

When Java was first created, its simplicity was a reversal of the increasing complexity (and, let's be fair, flexibility) of C++. Pointers and memory management were simplified, including elimination of parameter indirection, const, function pointers and other powerful but often confusing features. In C++ you can pass parameters by value (like in Java) or by reference, allowing the reference to be reassigned in the method and providing you with out parameters, or a way of returning more values from a method than the one return value allowed by the syntax. The signatures that can end up with char** and & dereferences throughout the code might be unsightly to some, but they are quite useful.

Using JavaBeans

C++ also supports structs, which allow lightweight structured data packaging. Of course, Java classes can do double duty as structs easily enough, but often the conventions (like JavaBeans) end up making the source larger with a lot of boilerplate.

Another problem with using classes and the JavaBeans conventions is that the objects are inherently mutable in nature. Given that these objects may end up being shared between the caller of the method and the class of the method called, this can lead to shared mutable state, which can be bad news in a multi-threaded system.

In Java, what you are left with is method parameters being passed by value and cannot be outparams, while methods can only return one argument. This certainly works, look at any significant codebase and you will see plenty of examples, but it's not particularly efficient in developer time.

Improving the Java Beans Way

So what can be done? Well, the Java classes option is really the only typesafe solution to be had, and by improving style, these classes can be a better substitute for structs, and with a few advantages of their own.

Let's take a return class with two arguments - say a name and a date of birth:

public class PersonNameDOB {
    private String name;
    private Date dob;

    public Date getDob() {
        return dob;
    }

    public void setDob(Date dob) {
        this.dob = dob;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

Obviously this is a manufactured example, and the chances are you probably have a Person class already defined that you could maybe return instead. I would also bet that you have examples of your own where you want to return two different objects from a method but don't have a class already defined for them, or maybe you end up returning a class with far more information than necessary in order to just get a couple of items out of it. Depending on the circumstance that might be even worse. For example, what if the person calling your method starts to use or even modify values in that returned object when you had no intention of letting that happen?

The above is more code than is needed anyway. This is meant to be a lightweight way of returning some values from a method, so let's make some simple changes:

public class PersonNameDOB {
    public final String name;
    public final Date dob;

    public PersonNameDOB(String name, Date dob) {
        this.name = name;
        this.dob = dob;
    }
}

The result is shorter, and more fit for the task. Values are being returned, so there is no need for setters, let's just set up the values when the return object is created. They don't need to change, and since they are in a constructor, they can be made final. Now that they are final, there is no risk to making the class attributes themselves public, since they can't be side affected, so now you can get rid of the getters as well as the setters. The result is shorter and easier to use:

PersonNameDOB personNameDOB = SSNLookup.lookupBySSN("123-45-6789");
System.out.println(personNameDOB.name);
System.out.println(personNameDOB.dob);

And the lookupBySSN method:

public PersonNameDOB lookupBySSN(String ssn) {
    ... Find the person record in the DB, etc. ...

    return new PersonNameDOB(person.getName(), person.getDOB());
}

If this seems totally obvious then great, just bear with me as I take things a bit further.

I like this approach to lightweight return objects. It is typesafe, so there is no need to cast objects out of arrays after return. Even better, the final modifier on the attributes means that these return objects cannot be abused - they are simply for transfer of data.

Taking that safety a step further, I recommend that you take copies of objects or use immutable objects where possible since doing otherwise is to risk unexpected modification of the values in your donor object by a calling method. In our example, String is immutable, but date should be copied:

public PersonNameDOB lookupBySSN(String ssn) {
    ... Find the person record in the DB, etc. ...

    return new PersonNameDOB(person.getName(), new Date(person.getDOB().getTime()));
}

This will prevent a caller doing the following:

PersonNameDOB personNameDOB = SSNLookup.lookupBySSN("123-45-6789");
personNameDOB.dob.setTime(0);

from side affecting the original dob value which is a huge risk otherwise. Immutable values rock, and if you can't use those, then be sure to take copies and return those copies in your results instead.





Page 1 of 3



Comment and Contribute

 


(Maximum characters: 1200). You have characters left.

 

 


Sitemap | Contact Us

Rocket Fuel