24

I've just discovered a huge hole in my Java knowledge. I knew that Java passes parameters by value. I thought I understand that and whenever I needed to edit the field object of a class, I create a field. For example,

private int mNumber;

void main(){
   mNumber = 1;
}

void otherMethod(){
   mNumber = 3;
}

This would change the field object, everyone's happy.

I was debugging and had to review the classes inside Android SDK and I noticed this piece of code

public static String dumpCursorToString(Cursor cursor) {
    StringBuilder sb = new StringBuilder();
    dumpCursor(cursor, sb);
    return sb.toString();
}

public static void dumpCursor(Cursor cursor, StringBuilder sb) {
    sb.append(">>>>> Dumping cursor " + cursor + "\n");
    if (cursor != null) {
        int startPos = cursor.getPosition();
        cursor.moveToPosition(-1);
        while (cursor.moveToNext()) {
            dumpCurrentRow(cursor, sb);
        }
        cursor.moveToPosition(startPos);
    }
    sb.append("<<<<<\n");
}

The are changing the local variable sb by passing it as a parameter to another method.

In my case, sb would be a field and I would change it from another method.

How is this possible? Is a reference (pointer) passed as a parameter so we are ALWAYS changing the source value (just like we pass it by reference in other languages)? Was my approach with field unnecessary all this time?

So my approach could be easily changed to

void main(){
   int number = 1;
   otherMethod(number)
}

void otherMethod(int number){
   number = 3;
}

Don't be angry at me if this sound like a beginner question. It may actually be, but I have just discovered a huge hole in my Java knowledge.

deviDave
  • 2,933
  • 5
  • 26
  • 30
  • 3
    Possible duplicate: [Is Java “pass-by-reference” or “pass-by-value”?](http://stackoverflow.com/q/40480/484666) – scriptin Jun 06 '15 at 09:26
  • 1
    @scriptin I read this post first, before posting. But it does not explain the logic of the code I posted here. – deviDave Jun 06 '15 at 09:28
  • 4
    It does explain it: "Java is always pass-by-value. The difficult thing to understand is that Java passes objects as references and those references are passed by value." then the accepted answer goes on to explain how this works. –  Jun 07 '15 at 16:08

2 Answers2

71

The statement that "java is always pass-by-value" is technically correct, but it can be very misleading, because as you have just witnessed, when you pass an object to a function, the function can modify the contents of the object, so it appears that the object has been passed by reference, and not by value. So, what is happening?

Pointers. That's what's happening.

Pointers are everywhere

One of the major selling points of Java was that "it is a safe language because it is free of pointers".

This statement is true in the sense that you cannot do most of the unsafe things like pointer arithmetic that have historically been the cause of many a monstrous bug in languages like C and C++. However, this statement has also hindered the understanding of java for many a novice programmer.

You see, java is actually full of pointers; Novice programmers only begin to make sense out of the language once they realize that in java everything which is not a primitive is in fact a pointer; pointers are everywhere; you cannot declare an object statically, or on the stack, or as a element of an array as you can do in C++; the only way to declare and use an object is by pointer; even arrays of objects are in fact nothing but arrays of pointers to objects; everything that looks like an object in java is never really an object, it is always a pointer to an object.

Java makes a clear distinction between primitives and objects. Primitives, (int, boolean, float, etc.,) also known as value types, are always passed by value, and they behave precisely as you would expect according to the "java is always pass-by-value" maxim.

Objects, on the other hand, also known as reference types, are always accessed by reference, that is, via a pointer, so their behavior is a bit trickier. When you call a method passing an object to it, java does not really pass the object itself; it passes a pointer to it. This pointer is passed by value, so the maxim "java is always pass-by-value" still holds from a strictly technical perspective. If you set this pointer to null within the method, the pointer held by the caller is unaffected, so obviously, what you have inside the method is a copy of the pointer. But if you modify the object pointed by the pointer, the caller will always see these modifications, which means that both the caller and the callee are holding references to the exact same object.

So, the statement "java is always pass-by-value" is true from a strictly technical, but ultimately not very useful point of view, according to which objects are never really passed anywhere, only pointers to the objects are passed.

In reality, when you write code, when you think what the code does, it is very useful to think of objects as being passed to functions, and since java passes pointers (by value) under the hood, the objects themselves always appear to be passed by reference.

Mike Nakis
  • 32,003
  • 7
  • 76
  • 111
  • 3
    Yes, that's the whole I had. I actually new this, it's not new to me, but I completely forgot it. – deviDave Jun 06 '15 at 09:32
  • 11
    [Java is always pass-by-value.](http://javadude.com/articles/passbyvalue.htm) When you pass a variable of a non-primitive type, you still pass it by value, which just happens to be a pointer (to an object). – scriptin Jun 06 '15 at 09:38
  • 6
    @scriptin: I very much prefer to use the term "pointer" in this discussion, because using the term "reference" here is confusing, since it means something very different from what it means as part of the compound term "pass-by-reference". – Jörg W Mittag Jun 06 '15 at 09:41
  • @JörgWMittag but using the term "pointer" in the context of Java is more confusing, don't you think? – Thomas Junk Jun 06 '15 at 09:43
  • @ThomasJunk: See [Mike's edit](http://programmers.stackexchange.com/revisions/286013/2), he took the words right out of my mouth. – Jörg W Mittag Jun 06 '15 at 09:47
  • »You will only understand java once you realize that it is full of pointers; pointers are everywhere; you cannot actually declare an object statically, or in the stack, as you can do in C++; the only way to declare and use an object is by pointer; that thing which looks like an object is not really an object, it is always a pointer to the actual object.« In fact, there is no pointer nor any objects, but variables referring to objects, which are implemented as pointers in C. – Thomas Junk Jun 06 '15 at 09:47
  • @JörgWMittag but as he put it, it is IMHO not quite right ;) – Thomas Junk Jun 06 '15 at 09:48
  • @scriptin perhaps you will be less displeased by my answer after my last amendment. – Mike Nakis Jun 06 '15 at 10:01
  • @MikeNakis it sounds even more confusing to me now. *Pointers* to objects are passed *by value*. Objects themselves are not passed at all - neither by reference (in that case we could implement a `swap` function by passing two references), nor by value (in that case we'd have a full copy). – scriptin Jun 06 '15 at 10:41
  • 3
    @scriptin * sigh * – Mike Nakis Jun 06 '15 at 10:54
  • Apparently people are upvoting this because of the funny picture, because the actual text is misleading and wrong. Java does not support pass-by-reference. – JacquesB Jun 06 '15 at 11:59
  • 1
    @JacquesB don't worry, the answer started being upvoted before I added the picture. "Java does not support pass-by-reference" only in the most narrow-sighted, nitpicking, uninformative, and actually even blatantly ***anti-educational*** sense. No, java does not support any way of passing objects other than by reference, and it is these references that are passed by value. Simple. Makes sense. Works. End of story. – Mike Nakis Jun 06 '15 at 13:19
  • @MikeNakis: OK, so...what does the 'ref' modifier in C# do? – JacquesB Jun 06 '15 at 13:20
  • @JacquesB (without looking it up) it allows you to pass value types by reference, and it also allows you to pass an actual pointer to an object type (also called "reference type", *hint hint*) by reference. – Mike Nakis Jun 06 '15 at 13:22
  • 1
    @MikeNakis: Your intuitive understanding of 'ref' is incorrect. It does not have anything to do with the distinction between value and reference types. It means a reference to the local variable (not its value, but the local variable itself!) is passed into the parameter, which means the callee can modify the variable, eg. assign it a new value. This is what call-by-reference means, and it is not supported by Java. It is equivalent to the '&' modifier in C, where you pass the *address* of a variable rather than the value. – JacquesB Jun 06 '15 at 13:32
  • 3
    My "intuitive" understanding of 'ref' is fine, your interpretation of my understanding is incorrect. Yannis Rizos will probably show up any moment now to put an end to this lengthy debate in comments, so, if anyone has not had enough yet, [please consult this discussion](http://stackoverflow.com/q/40480/773113). The answer with the 1900+ upvotes says: "Java is always pass-by-value. The difficult thing to understand is that Java passes objects as references and those references are passed by value". (Which renders the "Java is always pass-by-value" statement pointless bickering.) – Mike Nakis Jun 06 '15 at 13:36
  • "When you pass an object to a method" It doesn't really make sense to say "pass an object", because "objects" are not values in Java. Syntactically, you can only pass expressions, and every expression has a compile-time type, and there are no "object types" in Java, so whatever you are passing, it's not "an object". It's something else. – user102008 Jun 09 '15 at 02:13
  • Upvote for the picture "Pointers everywhere!" – Yuantao Jan 02 '18 at 05:38
10

In short, you are confusing reassigning a local variable with modifying a shared object. sb.append() does not modify the variable sb, it modifies the object that the variable references. But number = 3 modifies the variable number by assigning it a new value. Objects are shared across method calls, but local variables are not. Therefore assigning a local variable a new value will not have any effect outside the method, but modifying an object will. A bit simplified: if you use a dot (.), you are modifying an object, not a local variable.


You confusion is probably due to confusing terminology. "Reference" means two different things in different context.

1) When talking about Java objects and variables, a reference is a "handle" to an object. Strictly speaking, a variable cannot hold an object, it holds a reference to an object. References can be copied, so two variables can get a reference to the same object. E.g.

StringBuilder a = new StringBuilder();
StringBuilder b = a; // a and b now references the same object
a.writeLine("Hello"); // modify the shared object
b.toString() // -> "Hello"

When an objects is passed into a method as a parameter, it is also the reference that is copied, so the called method can modify the same object. This is also called "Call by sharing".

void main(){
   String builder b = new StringBuilder();
   otherMethod(b);
   b.toString(); // --> "Hello" because the shared object is modified
}

void otherMethod(StringBuilder b){
   b.write("Hello"); // modify the shared object
}

But the otherMethod can only modify the shared object, it cannot modify any local variables in the calling method. If the called function assigns a new value to its own parameters, it doesn't affect the calling function.

void main(){
   String builder b = new StringBuilder();
   b.writeLine("Hello"); 
   otherMethod(b);
   b.toString(); // --> "Hello" because the shared object is NOT modified
}

void otherMethod(StringBuilder b){
   b = new StringBuilder(); // assign a new object to b
   b.write("Goodbye"); // modify the new object
}

So you have to be clear on the distinction between modifying a shared object (through modifying fields or calling method on the object) and assigning a new value to a local variable or parameter.

2) When talking evaluation strategy (how parameters are passed into function) in general computer language theory, call-by-reference and call-by-value means something quite different.

Call-by-value, which Java supports, means that values from arguments are copied into parameters when calling methods. The value can be a reference, which is why the called function will get a reference to the shared object. Confusingly Java is said to "pass references by value", which is quite distinct from call-by-reference.

If a language supports call-by-reference, a function can change the value of a local variable in the calling function. Java does not support this, which is shown by the example above. Call-by-reference means the called function gets a reference to the local variable used as argument in the call. This notion is completely foreign to Java, where we simply do not have the concept of a reference to a local variable. We only have references to objects, never to local variables.

To show how call-by-reference works, we can look at C# which do support call-by-reference with the ref modifier:

void main(){
   String builder b = new StringBuilder();
   b.writeLine("Hello"); 
   otherMethod(ref b);
   b.toString(); // --> "Goodbye" because otherMethod changed b to point to the new object
}

void otherMethod(ref StringBuilder b){
   b = new StringBuilder(); // assign a new object to b
   b.write("Goodbye"); // modify the new object
}

Here otherMethod() actually modifies the local variable b in main().

So what about primitives like integers, booleans etc.? These are immutable which means they cannot be modified like objects can. Since they cannot be modified, the question if they are shared or not is moot, since there is no observable difference whether they are shared or copied.


Note: I didn't invent the terminology, so don't blame the messenger. I much prefer the terms call-by-sharing or call-by-object which is more intuitive for Java, but the fact remains that the terms call-by-value and call-by-reference is established terminology, and the Java specification itself uses the term call-by-value.

JacquesB
  • 57,310
  • 21
  • 127
  • 176
  • 1
    "When an objects is passed into a method as a parameter," "Objects" are not values in Java (there are no "object types") and cannot be "passed". If you can explain that, then everything else follows. – user102008 Jun 09 '15 at 02:15