Yeah, this is one of those things that while it may be more technically correct causes a lot of unnecessary confusion. I remember being confused by this as a C++ programmer learning Java when resources claimed that Java was always pass by value where the actual behaviour in almost all cases (due to Java going almost all-in on objects) is what a C++ programmer would expect from pass by reference.
I still see even relatively experienced programmers who don't understand this, particularly working with Unity where a lot of programmers came from C++ to C# and don't realise for example that a C# function that takes a List 'by value' and modifies it is actually modifying the same instance that was passed in by the caller.
No, Java really is pass by value. You can rely on this in Java:
String s = "hello";
foo(s);
assert s == "hello";
In C++, if the function takes a non-const reference, you can't.
Yes, Java always passes pointers to objects. But you can pass pointers by value. And passing a pointer by value is not the same as passing by reference!
I think the origin of the confusion around a function taking a list by value and the like is the implicitness of pointers in Java and its cousins. This Java method:
void foo(List<String> strings)
Is the equivalent of this C++ method:
void foo(shared_ptr<List<string>> strings)
True systems languages make the pointers explicit.
That's only because strings are immutable in Java. It's not true for reference types in general.
In C++ passing a pointer by value is effectively the same as passing a reference, the only real difference being that the syntax for accessing the underlying value is more implicit for a reference.
No, in Java, this is true for reference types in general. The method receiving the pointer can mutate the object, but it can't change which object the original variable points to.
This is also true when passing a pointer variable by value in C or C++. It is not true when passing a reference in C++ - the receiving method can change which object the original variable points to.
Ok, that's not really what your example showed though. You seem to be relying on string interning to have two different "hello" literals refer to the same underlying object and therefore be equal? Coming from other languages, and specifically C++, I tend to see `==` as value rather than reference equality so that wasn't immediately obvious to me.
The equivalent code in C++ has different semantics but a function that takes a non const reference in C++ cannot change what the reference refers to (references are immutable in that sense in C++, they can only ever refer to one object). What a non const reference allows in C++ is for the called function to change the value of the object referred to and since strings are not immutable in C++ that means that the value of string s could change, not the object identity.
With pointers to pointers, or references to pointers in C++ you can further change the object pointed to / referred to but not with references (there's no such thing as a reference to a reference in C++).
I still see even relatively experienced programmers who don't understand this, particularly working with Unity where a lot of programmers came from C++ to C# and don't realise for example that a C# function that takes a List 'by value' and modifies it is actually modifying the same instance that was passed in by the caller.