简体   繁体   中英

When you need pass by reference in Java to assign values to multiple parameters, how do you do it?

In order to refactor some methods into smaller ones, I need pass by reference, since I can only have one return type. I could create a different return or parameter type whenever I need to do this, but then I'd end up with a bunch of bloated, seemingly unnecessary classes.

What is the best alternative here, besides redesigning the program, which can't be done?

edit: some of it CAN be redesigned, and that's what I am doing, but, for example, at one point it is finding the minimum and second minimum values in a huge collection and then manipulating them. I wanted to split that into 2 methods - one finds, another manipulates - but it seems highly unlikely I'll be able to do this cleanly.

You are talking about "returning" multiple values by means of so-called out parameters, like you sometimes find in C, which are passed by reference?

You asked for an alternative to returning a single value class, but that is really the right thing to do.

But since you ask, yes, you're correct that you need a little more work to get pass-by-reference semantics in Java. Java is always pass-by-value (yes I mean that -- it is object references that are passed into methods, and those are passed by value). So to fake pass-by-reference, you could do something like pass an array of one Object, and change the reference it contains, if you wanted to pass back a new reference this way.

However I'd call this highly unconventional and would not recommend it. Any improvement you get by refactoring is destroyed by this.

I need pass by reference

To be strictly precise, Java does not have pass by reference. There's only one passing mechanism in Java, and that's pass by value.

In the case of objects, the thing that's being passed is not the object itself, which lives on the heap. It's the reference to the object that's passed by value.

It might sound like a nitpick, but it's an important one.

I don't see what this has to do with refactoring. If you need to create new types just to be returned, by all means do so. I'd bet that you really don't have an object model that reflects the problem at hand, which is why you have this refactoring problem.

Doesn't sound very object-oriented to me, especially after reading your edit.

You could do something like create a generic class that could handle out values from a function.

private static class OutValue<T> {
    public T value;

    static <X> OutValue<X> makeOutValue(X value) {
        OutValue<X> outValue = new OutValue<X>();
        outValue.value = value;
        return outValue;
    }
}

Here is an example of how the class could be used to get an integer from a function.

void getInteger(OutValue<Integer> x)
{
    x.value = 1;
}

OutValue<Integer> outValue = OutValue.makeOutValue(0);
getInteger(outValue);
System.out.println("value = " + outValue.value);

It is probably not the most elegant overall solution, but it will keep you from having to write a ton of classes if you do not want to do a more involved refactor.

bunch of bloated, seemingly unnecessary classes.

Hmm either the data belong together, which is why you have decided to return them from a single method, or they don't. If they do, isn't a new class is appropriate? As a last ditch you could return a Vector.

edit

or if you always had a finite number of return items you could use something like Pair<T1,T2> (or define some such generic tuple type)

Promote your variables to instance variables, that way they'll be visible to both methods (you are allowed to have state in an object after all). If they don't look like they should belong on your object then I don't think you'll be able to get around a redesign.

Since in a method, the arguments the references of objects are passed by value, couldn't you do something along the lines of:

import java.awt.Point;

public class Example {
    public static void main(String args[]) {
        Point pnt1 = new Point(0, 0);
        Point pnt2 = new Point(0, 0);
        System.out.println("X: " + pnt1.x + " Y: " + pnt1.y);
        System.out.println("X: " + pnt2.x + " Y: " + pnt2.y);
        System.out.println(" ");
        sillyMethod(pnt1, pnt2);
        System.out.println("X: " + pnt1.x + " Y: " + pnt1.y);
        System.out.println("X: " + pnt2.x + " Y: " + pnt2.y);
    }

    public static void sillyMethod(Point arg1, Point arg2) {
        arg1.x = 100;
        arg1.y = 100;
        arg2.x = 200;
        arg2.y = 200;
    }
}

This way, you are changing the values of the objects, without returning diverging types.

Without more detail, it's difficult to give specific advice. But possibilities include:

Put related functions into a single class and make the data member variables. Like to take your "find and minipulate two smallest":

class ManipulateLeast2
{
  List<int> list;
  int firstSmallest;
  int secondSmallest;

  public ManipulateLeast2(List<int> list)
  {
    this.list=list;
  }
  public void findTwoSmallest()
  {
     .. scan list and populate firstSmallest and secondSmallest ...
  }
  public void processTwoSmallest()
  {
     ... process firstSmallest and secondSmallest and do whatever ...
  }
}

If that's awkward, another possibility is to create a class to hold the values you need and return that. Like:

public class Smallest
{
  int first;
  int second;
  public Smallest(int first, int second)
  {
    this.first=first;
    this.second=second;
  }
}
...
public Smallest findSmallest(List list)
{
  ... find two smallest and put in first and second ...
  // Create the object and return it
  return new Smallest(first, second);
}

I think this is much superior to faking out a pass-by-reference. But if you really insist, it could be done by putting the value in a wrapper, like this:

public StringWrapper
{
  public String s;
}
...
// callee
void getTwoStrings(StringWrapper sw1, StringWrapper sw2)
{
  sw1.s="Hello";
  sw2.s="Goodbye";
} 
// caller
StringWrapper sw1=new StringWrapper();
StringWrapper sw2=new StringWrapper();
getTwoStrings(sw1, sw2);
System.out.println("First is "+sw1.s);
System.out.println("Second is "+sw2.s);

To make this clean you'd need a separate wrapper for each class. I suppose you could make an ObjectWrapper to make it generic and then cast things, but this is getting quite messy.

I think you're much better off to think of how your data relates and move it into logical classes.

Just because a method can only have one return type doesn't mean that that type can't contain more than one piece of information. The solution to the type of problem you seem to be having is to declare data types which hold multiple pieces of information.

Following the example in the question ("finding the minimum and second minimum values in a huge collection and then manipulating them"):

public class MinimumHolder {
    private int min;
    private int secondMin;
    public MinimumHolder(int min, int secondMin) {
        this.min = min;
        this.secondMin = secondMin;
    }

    //getters...
}

public MinimumHolder findTwoMinimums(Set<Integer> numbers) {
    // ...
    return new MinimumHolder(...);
}

public MinimumHolder manipulateData(MinimumHolder mins) {
    // do stuff with the data, this method could also be
    // declared as void if MinimumHolder was mutable
}

You could also pretty easily refactor MinimumHolder so that it could hold a variable list of "minimums" which were exposed with a getMinimum(int position) if you ever wanted to find more than two minimums.

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