views:

320

answers:

5

Hey everyone,

I am relatively new to Java and while trying out some code came across something that surprised me. Hopefully some one can shed some light on this. Here's some code snippets relevant to my question. Those who've read the practice of programming might find this familiar.

This function is defined for the List datatype, and applies the passed "fn" argument (which is nothing but a function wrapped in an object) to all members of the list.

    public void apply(MapFunctionI fn, Object fnArg) {
    Node curr = head;
    for (; curr != null; curr = curr.getNext()){
        fn.function(curr.getItem(), fnArg);
    }
}

Next, I try to use this function to count the number of elements in a list using a class that implements MapFunctionI (an interface that requires a method called 'function')

class CounterFunction implements MapFunctionI {
public void function(Object node, Object arg){
    ((MyInteger)arg).increment();
}
}

Here's how I call this.

    static void TestApply(LinkedList l){
    System.out.println("Number of elements in List -> ");
    MyInteger n = new MyInteger(0);
    l.apply(new CounterFunction(),n);
    System.out.println(n.value());
}

And here's the MyInteger type.

class MyInteger {
private int n;

MyInteger(int i){
    n = i;
}

public void increment(){
    n++;
}

public int value(){
    return n;
}

Now, if you're wondering why I am using my own Integer type, that's what my question is related to. I tried using the Java Integer type for this, but I could not get it to work, the answer printed was always O, the value of the "Integer" did not persist across multiple invocations. Here's how I was doing it,

arg = ((Integer)arg).intValue() + 1;

What explains this behavior?

And I am sure there is a more succinct way of posing this question :-)

+2  A: 

The reason it didn't work when you used the Integer type is because you were updating the local copy of the arg pointer inside function(). In Java, all non-primitives are "reference types", and all function arguments are passed by value.

Edit - in case that last statement is confusing, imagine if you were working with a dialect of C++ that only allowed you to pass references by value (ie you could only pass copies of pointers), and you wanted to implement the swap function. Not possible, right? Updating the local pointers would have no effect on the pointers that were passed to the function.

danben
Yep, Java is pass-by-value, but it does pass references by value.
Kaleb Brasee
Yes, and that is exactly what I said in my answer.
danben
+3  A: 

The first problem is that type Integer is immutable; once created with a certain integer value, that value cannot be changed. So you can't directly change an Integer (I'm not saying you didn't know this or tried to).

The next problem is that what gets passed to the function is a reference to that Integer. You can, inside the function, change that reference to point to a different-valued Integer, but the changed reference from inside the function is never passed back out to the calling function, so its value isn't relevant there.

The reason why your home-built class works is that you are now changing the internal contents of your object and not just a reference to it. The very same object exists inside and outside your function, so when you change it inside, the change is indeed seen outside.

Carl Smotricz
+2  A: 

First off, Java's Integer class is immutable; it only ever represents a single integer value. When you call intValue() on it, that's simply returning the current (immutable) value of the integer.

When you assign the (unboxed and reboxed) incremented value to arg, arg is just a local reference to what got passed in (just like a pointer value inside a function in C, say). Setting to something and then returning from the function loses the new value you just gave it.

If you were in C/C++, this is where you'd use a pointer-to-a-pointer. But Java doesn't have an equivalent construct, so you're on your own in dealing with it-- a wrapper object like you've used is a reasonable solution to your problem.

quixoto
A: 

The problem with the last code portion is that the arg variable just holds a reference to an Integer class. When you set arg to something, its value gets replaces in the local context with a new object, and does not update the object that already exists. You have to specify the full path to the reference you're updating.

amphetamachine
+1  A: 

This happens because in Java parameter passing is BY VALUE. NOTE that when you pass an object to a function you pass BY VALUE its reference... Or, said in another way, you pass its address by value.

When you write:

arg = .... // a new Integer Object

you change the value of the arg reference locally in the function (to point to the new object), while the reference saved in the fnArg variable in the List.apply function is still the same (and so pointing to the same Integer(0)).

If you know C++ it's the same difference you have in a function with a int* or an int*& as a parameter: in Java it's like having int* (for objects).

Andrea Zilio