views:

166

answers:

6

I feel like a novice for asking this question -- but why is it that when I pass the Set below into my method and point it to a new HashSet, it still comes out as the EmptySet? Is it because local variables are allocated on the stack, and so my new is blown away when I exit the method? How could I achieve the functional equivalent?

import java.util.HashSet;
import java.util.Set;

public class TestMethods {

    public static void main(final String[] args) {

        final Set<Integer> foo = java.util.Collections.emptySet();
        test(foo);

    }

    public static void test(Set<Integer> mySet) {

        mySet = new HashSet<Integer>();

    }

}
+2  A: 

Java passes references by value, think of mySet as just a copy of the foo reference. In void test(Set<Integer> mySet) , the mySet variable is just a local variable within that function, so setting it to something else doesn't affect the caller in main.

mySet does reference(or "point to" if you like) the same Set as the foo variable does in main though.

If you want to alter the reference in main, you could do e.g.:

foo = test(); //foo can't be final now though
 public static Set<Integer>  test() {
   return new HashSet<Integer>();
}
nos
+4  A: 

... Is it because local variables are allocated on the stack, and so my new is blown away when I exit the method?

No. It is because of the argument passing semantics of Java.

Java arguments are passed "by value", but in the case of an object or array type, the value you are passing is the object/array reference. When you create and assign a new set object to mySet, you are simply setting the local variable / parameter. Since Java uses pass by value, this has no effect on the foo variable in the main method.

When you enter the test method, you have two copies of the reference to the HashSet instance created in the main method; one in foo and one in mySet. Your code then replaces the reference in mySet with a reference to a newly created HashSet, but this new reference doesn't get passed back to the caller. (You could change your code to pass it back ... for example as the result of the test method. But you have to do this explicitly.)

OK - however -- if I were to do add or some other operation within my method call, that allocation would be preserved. Why is that?

That is because when you call an instance method using the reference in foo or mySet, that method is executed on the object (HashSet) that the reference refers to. Assuming that the two references point to the same object, your "allocation will be preserved". Or more precisely, you can observe the effects of operations on one reference to an object via operations on other references to the same object.

Just remember that Java method calls copy references to object, not the objects themselves.

By the way you won't be able to add elements to a set returned by Collections.emptySet(). That set object is immutable. Calling (for example) add on it will throw an exception.

Stephen C
Fine, however -- if I were to do add or some other operation within my method call, that allocation would be preserved. Why is that?
Amir Afghani
Brilliant answer Stephen, thank you.
Amir Afghani
A: 

Not sure I understand the question. In the test method, you are instantiating a new set and assigning it to the local mySet variable. mySet then no longer will reference the same set as foo does back in Main.

When you return from the method, foo still references the original emptySet() and the HashSet created in the method will be marked for garbage collection.

Gray
A: 
import java.util.HashSet;
import java.util.Set;

public class TestMethods {

    public static void main(final String[] args) {

        final Set<Integer> foo = java.util.Collections.emptySet();
        test(foo);

    }

    public static void test(Set<Integer> mySet) {
        // here mySet points to the same object as foo in main
        mySet = new HashSet<Integer>();
        // mySet now points to a new object created by your HashSet constructor call,
        // any subsequent operations on mySet are no longer associated with foo, because
        // they are no longer referencing the same object
    }
}

How could I achieve the functional equivalent?

I am not sure if I understand this question, are you looking for a return?

    public static Set<Integer> test(Set<Integer> mySet) {
        for(Integer i : mySet){
            // do something??
        }
        mySet = new HashSet<Integer>();
        return mySet;
    }

Now, if you assign foo to what test returns, you have the "functional equivalent"?

Cambium
+1  A: 

Your 'foo' referred to an empty set going into the test() call, the test call did not modify that object, and so it's still an empty set on return from there.

Within the test() method, 'mySet' is just a local reference, which refers to the original set (foo) on entry, and when you did the assignment of a new HashSet to that reference, you lost the reference to the original set. But these effects are all entirely local to the test() method, because java simply gave test() a duplicate of the reference to the original set.

Now, within test(), since you have a reference to the original object, you can modify that object. For instance, you could add elements to that set. But you can't change the reference in the calling function, you can only change what it refers to. So you can't replace the one collection with a different one, and if you wanted a HashSet in the first place, you'd have to new the HashSet in main().

JustJeff
A: 

you should read this book:

"A Programmer's Guide to Java SCJP Certification: A Comprehensive Primer (3rd Edition)"

abc
Thanks for the pointer.
Amir Afghani