1.基本类型和引用类型在内存中的保存
Java中数据类型分为两大类,基本类型和对象类型。相应的,变量也有两种类型:基本类型和引用类型。
基本类型的变量保存原始值,即它代表的值就是数值本身;
而引用类型的变量保存引用值,"引用值"指向内存空间的地址,代表了某个对象的引用,而不是对象本身,
对象本身存放在这个引用值所表示的地址的位置。
基本类型包括:byte,short,int,long,char,float,double,Boolean,returnAddress,
引用类型包括:类类型,接口类型和数组。
相应的,变量也有两种类型:基本类型和引用类型。
2.引用传递和值传递
这里要用实际参数和形式参数的概念来帮助理解,
值传递:
方法调用时,实际参数把它的值传递给对应的形式参数,函数接收的是原始值的一个copy,此时内存中存在两个相等的基本类型,即实际参数和形式参数,后面方法中的操作都是对形参这个值的修改,不影响实际参数的值。
引用传递:
也称为传地址。方法调用时,实际参数的引用(地址,而不是参数的值)被传递给方法中相对应的形式参数,函数接收的是原始值的内存地址;
在方法执行中,形参和实参内容相同,指向同一块内存地址,方法执行中对引用的操作将会影响到实际对象。
3. 例子展示
public class trans_args1 {
public static void main(String[] args){
trans_args1 trans=new trans_args1();
//String类似基本类型,值传递,不会改变实际参数的值
String str="Hello";
trans.change(str);
System.out.println(str);
//StringBuffer和StringBuilder等是引用传递
StringBuffer sb=new StringBuffer("Hello");
trans.change(sb);
System.out.println(sb.toString());
}
public void change(String str){
str=str+"world";
}
public void change(StringBuffer sb){
sb.append(" World");
}
}
输出结果:
Hello
Hello World
从结果来看,sb的值被改变了,那么是不是可以说:对象作为参数传递时,是把对象的引用传递过去,如果引用在方法内被改变了,那么原对象也跟着改变。从上面例子的输出结果来看,这样解释是合理。
考虑下面的代码:
public class trans_args1 {
public static void main(String[] args){
trans_args1 trans=new trans_args1();
//StringBuffer和StringBuilder等是引用传递
StringBuffer sb1=new StringBuffer("Hello");
trans.change(sb1);
System.out.println(sb1.toString());
}
public void change(StringBuffer sb2){
sb2 = new StringBuffer("Hello ");
sb2.append(" World");
}
}
在执行 sb2 = new StringBuffer(); 之前,sb1 和 sb2 指向内存中的同一个地址。 而执行完sb2 = new StringBuffer() 之后,sb2 将指向内存中的新的地址。 两者不再指向同一地址,所以,main 中的 sb1 的值肯定是没有改变的。
结尾
今天之所以查阅博客写了这几个例子,是因为今天碰到的一个坑。
import java.util.*;
public class Test1 {
public static void main(String[] args){
int[] src = new int[]{1,2,3,4,5,6,7,8,9,10};
change(src);
for(int item : src) {
System.out.print(item + " ");
}
}
public static void change(int[] src) { // 改变数组的第一个值为100
src[0] = 100;
}
}
100 2 3 4 5 6 7 8 9 10
这个输出结果应该是显然的。因为数组在传递参数时也是引用传递。
然而,今天碰到下面代码的使用情况:
public class Test1 {
public static void main(String[] args){
List<Integer> list = new ArrayList<>();
int[] src = new int[]{1,2,3,4,5,6,7,8,9,10};
grow(src);
System.out.println(src.length);
}
public static void grow(int[] nums) {
nums= Arrays.copyOf(nums, nums.length*2); // 将数组 nums 扩容两倍
}
}
上述代码的输出: 10, 说明 main 中的 src 数组并没有扩容成功!
在一个题目中,有用到以下代码,扩容之后进行一系列操作,一直报错 越界,11。
之后查阅博客,了解了参数传递的一些细节之后,才恍然大悟。
查看 Arrays.copyOf() 源码:
看到这个 new,原来在扩容的时候,new 了一个新的对象,使得传递过来的参数 (地址) 和 扩容之后的地址不一致!!。
所以,main 中的 src 数组并没有实际扩容!!
其次,如果你用的是 IDEA 的话,也可以得到一定的提示;
上面的提示显示 str 对象定义了但是没有使用,说明 change() 函数体内的 str 和 main 中的 str 并没有指向同一个对象。