water men

今天,被一位先生问到了一个Java的问题,下面的代码应该输出什么:

Class Demo{
	public static void main(String[] args){
		String s = "0";
		change(s);
		System.out.println(s);
	}
	
	static void change(String s){
		s = "123";
	}
}

说实话,当这位先生问我问题的时候,我还是比较紧张的,毕竟他曾是某牛逼公司的牛逼架构师。
而且,往往人们遇到自认为简单的问题,往往就觉得这个问题不简单,举个例子就是:
当被问到一加一等于几的时候,人们往往会想:这个问题肯定不像我想象的这么简单,说不定有个坑正等着我去跳。
所以对于上面这道题,我脑子里面经历了下面几个过程:

1.原始想法

s是一个对象,而且不是基础类型数据,所以参数因该传递的是引用,那么在函数中改变了引用中某个属性的值(比如),所以main函数中s的值肯定也就改变了。

2.转折

但是,不会这么简单吧,他为什么要问我一个类似于一加一等于几的问题?很大可能是打印“0”,但这与我的推断结果并不相符。

3.转折

难道这道题就打印的是“123”?万一这道题就是考验人的心理素质呢?

所以:

我就坚定不移的回答了“123”。当这位先生问我两次“你确定?”之后,我依然认为他是在对我进行心理上的考验。
结果证明:我错大发了!
当这位先生把结果给我展现的时候,我觉得他一定在想:哥们儿,你当时面试怎么过的呀,进这公司不是走后门儿的吧?!(哈哈。。。)
所以我当时就更紧张了,同时:我的大脑进行飞速的运转,思考着各种各样会导致这种输出的原因。

一.第一步进阶

现在我需要研究一下Java的函数调用到底是传值还是传引用?
之前我一直认为:除了Java的基础数据类型(例如int,long等等),其余的数据类型(例如String,自定义对象等)都是引用传递。结果是:
通过查阅各种资料获知,Java中只存在值传递,值传递的时候会创建副本。比如下面这个例子:

class Demo{
	public static void main(String[] args){
		User u = new User();
		u.name = "123";
		changeName(u);
		System.out.println(u.name);
	}
	void changeName(User u){
		u = new User();
		u.name = "0";
	}
}
class User{
	public Strin name;
}

结果肯定是打印123,而不是打印0.现在引用就相当于房卡,对象就相当于房间。这个例子中传递的就是房卡,在changeName方法中,对房卡进行了修改,本来该房卡是打开1001这间房的,但是现在这个房卡已经执行1002号房间了。
那么main方法中的房卡是不是也就应该指向1002号房间了呢?答案是否定的,因为main方法调用changeName方法的时候,只不过是将房卡复制了一份,交给changeName(注意这里不是讲1001号房间复制一份交给changeName)。
所以这里传递的是引用的值,引用同样是一种数据结构。like that:


因为Java中没有指针,对内存的操作也很难下手,所以通过C语言相似的例子似乎更能够说明问题,如下所示:

#include<stdio.h>
#include<malloc.h>
#include<string.h>
void getSpace(char* str)
{
	str = (char*)malloc(sizeof(char)*10);
}
int main(int argc,char** argv)
{
	char* str;
	getSpace(str);
	strcpy(str,"nihao");
	printf("%s\n",str);
}

这个小程序就是main函数中声明一个字符串指针,然后通过将该指针以参数的形式传入到函数中进行空间分配。
空间分配完成后将一个字符串copy到该空间。并且打印该空间中的内容。
该程序会在运行期间异常终止。这里的函数形参是一个指针,就相似于Java中的引用。
虽然我们在getSpace函数中开辟了一块空间,并且让该指针指向开辟的新的空间,但是main函数中的指针是没有变的,main函数传递的只是指针值的一个副本。同样是一个copy后的房卡,main函数中的房卡仍然是指向原来的房间。


二.第二步进阶

回到最初的例子:

Class Demo{
	public static void main(String[] args){
		String s = "0";
		change(s);
		System.out.println(s);
	}
	static void change(String s){
		s = "123";
	}
}

通过第一步进阶得到的结论:main函数中保留的是String s的引用,传递到change函数中的是s的引用副本,但他们都指向堆中的同一片区域:“123”。
所以如果我们在change函数中改变了s所指向的堆中“123”的值,那么按道理来说,main函数中s所指向的值也就会发生改变。

但是问题来了,String并没有提供改变自身value的方法,所以s = “123” 就相当于 s = new String("123");!!!!!!!!!!!!!!!!!!!!!!!!,所以s所指向的堆中“123”这片区域没有发生改变。
这里发生改变的同样是让房卡副本指向了1002号房间,main函数中的房卡仍然指向1001号房间。
所以对于之前提到的那位先生问我的问题,应该输出0,而非123.

三.第三步进阶

第二步进阶的说明是错误的!俗话说:温故而知新,当我再一次查看第二步进阶的时候我想到了以前在C语言中遇到的一个问题,如下所示:

#include<stdio.h>
int main(int argc,char** argv)
{
	char* str="hello";
	str[1] = 'z';
	printf("%s\n",str);
}

这个程序很简单,就是先让一个指针指向一个字符串“hello”,再将“hello”中的‘e'替换为‘z’。但是在运行时,该程序会异常退出,原因在于:
对于指针str我们并没有分配空间,而是让其直接指向了常量池(方法区)中的“Hello”字符串,常量池中的内容是不能够被改变的,就相当于4=5这样的赋值操作是不被允许的。
通过下面这个例子更加可以说明问题:

#include<stdio.h>
int main(int argc,char** argv)
{
	char* str1="hello";
	char* str2="hello";
}

C调试的时候相对于Java的好处就在于:可以很容易的看到变量所在的内存地址。如下图所示:


嗦噶,上面这些话都是为了引入下面的主题:对于Java来说是不是也是这样呢?String s = “123” 与 String s = new String("123")是否有区别呢? 答案是肯定的!
我们通过下面两个程序示例可以看到效果:
示例代码一:

String a = "123";
String b = "123";
a==b; //true
a.equals(b);//true

示例代码二:

String a = new String("123");
String b = new String("123");
a==b; //false
a.equals(b);//true

如下图所示:




所以对于第二步进阶中说明的main中的s与change中的s指向堆中同一片内容的说法是错误的,他们应该是指向方法区中的同一片内容。


后记:
我在这里非常诚恳的感谢这位先生对我的教导。自从学Java以来,整天都是各种框架满天飞,各种新技术不断地尝试,却忽略了根本。
多看,多想,多写!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值