打印的结果是什么?
public class TransferValue {
public void changevalue(String str) {
str = "xxx";
}
public static void main(String[] args) {
TransferValue test = new TransferValue();
String str = "abc";
test.changevalue(str);
System.out.println(str);
}
}
运行结果是:abc
我承认第一次做有点掉以轻心了。java中每个方法对应着一个栈帧,一个栈帧有一个专属的局部变量表和操作数栈,栈的结构是后进先出。
执行main方法时,先开一个栈帧,
调用changevalue(String str)时,再开一个栈帧,main方法先进,后出,示意图。
"abc"是字符串,存放在常量池中。main方法把abc的引用(abc的内存地址值)交给了changevalue()方法.
两个栈都有一个局部变量叫str,这两个str并不是同一个变量,他们分别存放在不同的局部变量表中,只是他们指向的空间是一样的。(不同方法里的局部变量的名称是可以相同的,因为不在一个作用域里)。
还不理解的话就把changevalue方法的形参改成string,再自己分析分析
public class TransferValue {
public void changevalue(String string) {
string = "xxx";
}
public static void main(String[] args) {
TransferValue test = new TransferValue();
String str = "abc";
test.changevalue(str);
System.out.println(str);
}
}
main方法只是把"abc"的内存地址传过去了,常量池中相同字符串只保存一份,大家都共用常量池里的"abc"。这两个str分别在两个不同的方法中,作用域是不一样的。
表面上看changevalue()把str的内容改掉了,其实是没有!String比较特殊,被final修饰,是不可变类型,字符串一旦创建好了无法修改。
str = “xxx”;本质是重新new了一个字符串"xxx",再把指针重新指向新的字符串"xxx"。原来的字符串”abc"依然在常量池中没用被修改。
idea安装jclasslib,直接上字节码,
看看changevalue()方法做了什么
- 加载常量池中的"xxx",压入操作数栈中
- 存进该栈局部变量中
- 退出方法,栈销毁
自娱自乐,啥也没做!对main方法一点影响都没有。
所以最终打印的结果是:abc
test.changevalue(str);这一步表面上看是main方法把str对象传给changevalue()做修改,实际并不是。由于String字符串特殊不可变,实际上传的是该字符串"abc"在常量池中的内存地址。
再看这题
public class Person {
private Integer id;
private String personName;
public Person() {
}
public Person(String personName) {
this.personName = personName;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getPersonName() {
return personName;
}
public void setPersonName(String personName) {
this.personName = personName;
}
}
public class TransferValue {
/* public void changeValue2(Person person){
person.setPersonName("xxx");
System.out.println(person);
}*/
//为了避免混淆,这里就写成Person p,跟main方法的person特意做了区分
public void changeValue3(Person p) {
p.setPersonName("xxx");
System.out.println(p);
}
public static void main(String[] args) {
TransferValue transferValue = new TransferValue();
Person person = new Person("abc");
System.out.println("personName---" + person.getPersonName());
}
}
运行结果
personName—xxx
为什么改掉了呢?很简单–因为我们自定义的Person类可以修改。引用类型传递的本质是传递内存地址
对象是放在堆中的,p和person都同时指向堆内存中Person对象的实际地址,内存中仅有一份。
p.setPersonName(“xxx”);就直接把person对象的名字改掉了,改的是同一个对象,而person对象可以直接被修改。
String被final修饰,创建后不可以被修改,这就是区别。
第三题
public class TransferValue {
public void changeValue4(int age) {
age=40;
}
public static void main(String[] args) {
TransferValue transferValue = new TransferValue();
int age=20;
transferValue.changeValue4(age);
System.out.println("age---"+age);
}
}
运行结果:
age—20
基本数据类型的传值是值传递,把值拷贝了一份再传递。也就是把20这个值传递过去。
还不懂的话,就看成这样
public class TransferValue {
public void changeValue4(int a) {
System.out.println("a的值是"+a); //20
a=40;
System.out.println("a的值是"+a); //40
}
public static void main(String[] args) {
TransferValue transferValue = new TransferValue();
int age=20;
transferValue.changeValue4(20);
System.out.println("age---"+age);
}
}
运行结果:
a的值是20
a的值是40
age—20
形参的名字是可以随便修改的。一般情况还是改成一样方便查看。
a=40,跟age=20,没半毛钱关系,作用域都不一样。
结论:基本数据类型传值传的是变量值的拷贝,引用类型传的是对象的内存地址值。String类型比较特殊不可改变。