- public void Foo(String s) {
- s = new String("Hello");
- }
在函数外面new一个String:String s1 = new String("World"),然后执行 Foo(s1),然后输出s1的值是怎样的,我们都知道,当然还是“World”。但是既然传入的是引用,而不是值,那为什么执行完 Foo(s1) 之后,s1 的值没有变呢?我们再看下面这个C++的例子,从中寻找答案。
- #include <iostream>
- using namespace std;
- class CObj {
- public:
- CObj() {x = 0;}
- CObj(int x) {this->x = x;}
- public:
- int x;
- };
- void Foo1(CObj * pObj) {
- pObj = new CObj(200);
- }
- void Foo2(CObj * &pObj) {
- pObj = new CObj(200);
- }
- int main(int argc, char * argv[]) {
- CObj * pObj1 = new CObj(100);
- cout << pObj1->x << endl;
- Foo1(pObj1);
- cout << pObj1->x << endl;
- Foo2(pObj1);
- cout << pObj1->x << endl;
- return 0;
- }
运行结果如下:
- 100
- 100
- 200
这里的 Foo1() 函数就类似上面 Java 代码中的 Foo() 函数。Java中所谓的引用,可以理解成堆上生成的真正对象的地址,这里用 “C++中的指针”、“地址”、或是“引用”来表示都可以,总之,它本身不是对象的“肉身”,只是一个能找到“肉身”的标识。请注意,这样的一个标识,也有它的值,它的值就是它指向的对象肉身的地址。
C++代码中的 Foo1() 函数,传入的 pObj1 是一个标识的值,即 pObj1 虽然本身是一个指针,但是Foo1(pOjb1) 中,pObj1 是以“值传递”的方式传入的,说白了,就是真正传给了 Foo1() 函数的,是 pObj1 的一个副本,就相当于下面这样:
- void Foo1(CObj * pObj) {
- CObj * pTmp = pObj;
- pTmp = new CObj(200);
- }
- public void Foo(String s) {
- s = new String("Hello");
- }
- public void Foo(String s) {
- String tmp = s;
- tmp = new String("Hello");
- }
相比之下,C++代码中的 Foo2() 函数,就能改变传入的 pObj 对象本身,因为传入的指针本身被重置了!
- void Foo2(CObj * &pObj) {
- pObj = new CObj(200);
- }
上面各种方式的比较:
C++的传入引用貌似方便,但是常常要看到函数实现的内部,才知道传入的参数究竟在函数中是只读使用的,还是有写操作的。相比之下,Java基本上就是通过函数返回值传出结果,比C++要清晰的多。因此,在用Java时,尽量不要像C++的方式靠,而应尽量利用函数的返回值。比如,如果要同时返回两个String则可以返回一个List<String>;又比如,如果要将字符串“Jack_London_1234”中的字符串"London"和数字1234识别并返回,可以返回一个List<Object>,第一个元素是String "London",第二个元素是Integer 1234,然后分别做类型转换,转换成String类型和Integer类型。
PS
这篇帖子指出,JVM的引用,实际上就是标准的“对象指针”,根据不同的平台,表现为一个32位或64位的整型值,该整型值就是“对象指针”指向的对象在内存的地址。
PPS
如果String类提供一个函数,比如void assign(String input),该函数将input的内容赋给当前String对象(注意是堆上的对象实体),那么像下面这样的函数也是可以通过s参数返回“hello”的。之所以String类没有提供这样的函数,可能就是编写者不希望使用者像C++那样去用参数吧。
- public void Foo3(String s) {
- s.assign("hello");
- }