Java, Pass-By-Value, Reference Variables

Java pass by reference

Java always passes arguments by value NOT by reference.


Let me explain this through an example:

public class Main
{
public static void main(String[] args)
{
Foo f = new Foo("f");
changeReference(f); // It won't change the reference!
modifyReference(f); // It will modify the object that the reference variable "f" refers to!
}
public static void changeReference(Foo a)
{
Foo b = new Foo("b");
a = b;
}
public static void modifyReference(Foo c)
{
c.setAttribute("c");
}
}

I will explain this in steps:

  1. Declaring a reference named f of type Foo and assign it to a new object of type Foo with an attribute "f".

    Foo f = new Foo("f");

    Sample Image

  2. From the method side, a reference of type Foo with a name a is declared and it's initially assigned to null.

    public static void changeReference(Foo a)

    Sample Image

  3. As you call the method changeReference, the reference a will be assigned to the object which is passed as an argument.

    changeReference(f);

    Sample Image

  4. Declaring a reference named b of type Foo and assign it to a new object of type Foo with an attribute "b".

    Foo b = new Foo("b");

    Sample Image

  5. a = b is re-assigning the reference a NOT f to the object whose its attribute is "b".

    Sample Image


  6. As you call modifyReference(Foo c) method, a reference c is created and assigned to the object with attribute "f".

    Sample Image

  7. c.setAttribute("c"); will change the attribute of the object that reference c points to it, and it's same object that reference f points to it.

    Sample Image

I hope you understand now how passing objects as arguments works in Java :)

Can I pass parameters by reference in Java?

Java is confusing because everything is passed by value. However for a parameter of reference type (i.e. not a parameter of primitive type) it is the reference itself which is passed by value, hence it appears to be pass-by-reference (and people often claim that it is). This is not the case, as shown by the following:

Object o = "Hello";
mutate(o)
System.out.println(o);

private void mutate(Object o) { o = "Goodbye"; } //NOT THE SAME o!

Will print Hello to the console. The options if you wanted the above code to print Goodbye are to use an explicit reference as follows:

AtomicReference<Object> ref = new AtomicReference<Object>("Hello");
mutate(ref);
System.out.println(ref.get()); //Goodbye!

private void mutate(AtomicReference<Object> ref) { ref.set("Goodbye"); }

Java Pass By Value and reference

You're incrementing x twice, but on different dogs. Look at this code:

public static void foo(Dog d) {
++d.x;
d = new Dog("Taz");
++d.x;
}

Initially, d refers to the dog with a name of Tom, with x=100. That isn't a copy of the original object - it's a reference to the same object. Java passes the reference by value, which isn't the same as either passing an object by value or passing an object by reference. It's important to understand that the value of aDog in main isn't a Dog object - it's a reference to a Dog object. The value of that reference is passed, by value, to your foo method... so the initial value of d is the same as the value of aDog. Further changes to the d variable itself (rather than the object its value refers to) do not change the aDog variable.

So, looking at the rest of foo:

  • After the ++d.x, d refers to the dog with a name of Tom, with x=101.
  • After d = new Dog("Taz"), d refers to a dog with a name of Taz, with x=100.
  • After the ++d.x, d refers to the dog with a name of Taz, with x=101.

The calling code only ever knows about the dog with a name of Tom, so it prints out Tom 101.

Java is NEVER pass-by-reference, right?...right?

As Rytmis said, Java passes references by value. What this means is that you can legitimately call mutating methods on the parameters of a method, but you cannot reassign them and expect the value to propagate.

Example:

private void goodChangeDog(Dog dog) {
dog.setColor(Color.BLACK); // works as expected!
}
private void badChangeDog(Dog dog) {
dog = new StBernard(); // compiles, but has no effect outside the method
}

Edit: What this means in this case is that although voiceSetList might change as a result of this method (it could have a new element added to it), the changes to vsName will not be visible outside of the method. To prevent confusion, I often mark my method parameters final, which keeps them from being reassigned (accidentally or not) inside the method. This would keep the second example from compiling at all.

Java and C++ pass by value and pass by reference

C++ does not have the same semantics as Java for values and references. At first, every type has the potential to be either passed by copy or by reference or by address (you can however prevent types to be passed by copy by hiding the copy constructor).

The passing type the most closely related to Java's "by reference" passing is by pointer. Here are an example of the three:

void foo(std::string bar); // by copy
void foo(std::string& bar); // by reference
void foo(std::string* bar); // by address

As a side note, passing by copy is always more expensive than passing by reference or pointer for types that are larger than the size of a pointer. For this reason, you may also often prefer to pass by const reference, which will allow you to read an object without having to copy it.

void foo(const std::string& bar); // by const reference

However, this is all tricky and you need to be aware of Java subtleties to decide correctly what you want in C++. In Java, you're not actually passing objects by references: you are passing object references by copy. That is, if you assign a new object to an argument, the object of the parent scope won't change. In C++, this more closely matches passing objects by address than by reference. Here's an example of how this is important:

// Java using "object references":
public static void foo(String bar)
{
bar = "hello world";
}

public static void main(String[] argv)
{
String bar = "goodbye world";
foo(bar);
System.out.println(bar); // prints "goodbye world"
}

// C++ using "references":
void foo(std::string& bar)
{
bar = "hello world";
}

int main()
{
std::string bar = "goodbye world";
foo(bar);
std::cout << bar << std::endl; // prints "hello world"
}

// C++ using pointers:
void foo(std::string* bar)
{
bar = new std::string("goodbye world");
delete bar; // you don't want to leak
}

int main()
{
std::string bar = "goodbye world";
foo(&bar);
std::cout << bar << std::endl; // prints "hello world"
}

In other words, when you use references in C++, you're really dealing with the same variable you passed. Any change you do to it, even assignations, are reflected to the parent scope. (This is in part due to how C++ deals with the assignation operator.) Using pointers, you get a behavior more closely related to the one you have with Java references at the cost of possibly having to get object addresses through the unary & operator (see foo(&bar) in my example), needing to use the -> operator to access members, and some additional complexity for using operator overloads.

Another notable difference is that since the usage of by-reference arguments is syntactically closely related to the usage of by-copy arguments, functions should be able to assume that the objects you pass by reference are valid. Even though it's usually possible to pass a reference to NULL, this is very highly discouraged as the only way to do it is to dereference NULL, which has an undefined behavior. Therefore, if you need to be able to pass NULL as a parameter, you'll prefer to pass arguments by address rather than by reference.

Most of the time, you'll want to pass by reference instead of by address when you want to modify an argument from a function because it is more "C++ friendly" (unless you need the NULL value), even though it's not exactly like what Java does.



Related Topics



Leave a reply



Submit