形参和实参
形式参数, 是在方法定义阶段, 是定义某个函数时使用的参数, 用于接收实参传入. 例 f(x,y) 中 x 和 y 是形参.
实际参数, 是在方法调用阶段, 是主调函数调用有参函数时, 实际传递的内容. 例 f(3,7) 中 3 和 7 是实参.
值传递和引用传递
值传递和引用传递不是简单地通过传递内容区分的. 如果是值, 就是值传递; 如果是引用, 就是引用传递. 这一理解是不正确的.
值传递, 是指在调用函数时将实际参数复制一份传递给函数形参. 此时, 在函数中对形参做修改, 不影响实际参数.
引用传递, 是指在调用函数时将实际参数的地址直接传递给函数形参. 此时, 在函数中对参数做修改, 将影响实际参数.
根本区别在于值传递会创建副本, 因此函数中无法改变原始对象; 引用传递不创建副本, 函数中可以改变原始对象.
通过一个经典案例讲解 Java 值传递
- public class ParamPassing {
- private static int intStatic = 222;
- private static String stringStatic = "old string";
- private static StringBuilder stringBuilderStatic = new StringBuilder("old stringBuilder");
- public static void main(String[] args) {
- // 方法调用 1
- method(intStatic);
- System.out.println(intStatic);
- // 方法调用 2
- method();
- System.out.println(intStatic);
- // 方法调用 3
- method(stringStatic);
- System.out.println(stringStatic);
- // 方法调用 4
- method(stringBuilderStatic, stringBuilderStatic);
- System.out.println(stringBuilderStatic);
- }
- // 方法 1
- public static void method(int intStatic) {
- intStatic = 777;
- }
- // 方法 2
- public static void method() {
- intStatic = 888;
- }
- // 方法 3
- public static void method(String stringStatic) {
- stringStatic = "new string";
- }
- // 方法 4
- public static void method(StringBuilder stringBuilderStatic1, StringBuilder stringBuilderStatic2) {
- stringBuilderStatic1.append(".method.first-");
- stringBuilderStatic2.append(".method.second-");
- // 引用重新赋值
- stringBuilderStatic1 = new StringBuilder("new stringBuilder");
- stringBuilderStatic1.append("new method's append");
- }
- }
运行结果:
- 222
- 888
- old string
- old stringBuilder.method.first-.method.second-
方法 1, 参数是局部变量, 拷贝的变量值是 777, 会存入虚拟机栈中的局部变量表的第一个位置. 在方法内部, 根据作用于就近原则, 使用局部变量的参数, 操作与实参无关. 而方法 2, 先把本地赋值的 888 压入虚拟机栈中的操作栈, 然后给静态遍历 intStatic 赋值.
方法 3,String 是 immutable 对象, 类中没有提供任何方法用来修改对象."old string" 仍然由实参持有, 在方法 3 中, 会重新 new 一个 String 对象, 并把引用赋给形参.
方法 4, 直接使用参数引用, 可以修改对象; 当对引用重新赋值后, 不再影响实参.
当 stringBuilderStatic 引用作为实参传递给形参 stringBuilderStatic1 时, 此时形参是 stringBuilderStatic 的一个副本, 两个引用共同指向 StringBuilder 对象所在的堆内存地址, 此时对形参的任何修改都会改变对象属性. 当创建新对象并赋值给 stringBuilderStatic1 后, 该引用指向了新的内存地址, 对其修改不会改变原对象的属性.
字节码解释
- 0: aload_0 // 引用类型入栈
- 1: ldc #14 // 将常量值从常量池推到栈顶
- // String .method.first-
- 3: invokevirtual #15 // 调用实例方法
- // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
- 6: pop // 栈顶元素出栈
- 14: new #17 // 创建实例
- // class java/lang/StringBuilder
- 17: dup // 赋值栈顶数值, 压入栈顶
- 18: ldc #18
- // String new stringBuilder
- 20: invokespecial #19 // 实例初始化
- // Method java/lang/StringBuilder."<init>":(Ljava/lang/String;)V
- 23: astore_0 // 将栈中 ref 引用存到局部变量表
- 24: aload_0 // 加载局部变量到操作数栈
- 25: ldc #20 // 加载常量到操作数栈
- // String new method's append
- 27: invokevirtual #15 // 调实例方法
- // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
- 30: pop // 操作数栈顶元素出栈
- 31: return // 方法返回指令
来源: https://www.cnblogs.com/lizhongping/p/11172925.html