基本理论
为准备面试,把这部分基础知识梳理了一下,纯古法手搓,无AI。
基本类型和引用类型
- 基本类型:byte、short、int、long、float、double、boolean、char
- 引用类型:对象(包括:数组、包装类)
创建变量时发生了什么
基本类型:
int a = 100;
jvm在栈上创建了一个int类型的区域,并保存当前的值。
引用类型:
Student stu = new Student();
此时jvm
- 在栈上创建一个Student类型的引用stu
- 在堆上创建一个新的Student对象
- 把stu引用指向新的Student对象的实际地址
实参和形参
- 声明一个函数时,函数中的参数称为形参,形参在不调用的时候不知道它的值
- 调用一个函数时,传入的值称为实参,也就是调用时实际的值
- 值传递和引用传递的区别在于:修改形参会不会影响实参
值传递和引用传递
值传递:修改形参不会影响实参。
public static void main(String[] args) {
int a = 100;
modify(a);
System.out.println(a); // a 没有被影响,输出100
}
public static void modify(int b) {
b = 999999; // 只改变了当前方法内的形参,不影响方法外的实参
}
通常理解的引用传递(并非真正的引用传递,Java中只有值传递):修改形参会影响实参
public static void main(String[] args) {
Student stu = new Student();
stu.name = "张三";
modify(stu);
System.out.println(stu.name); // 当前参数指向的实际对象被修改,输出李四
}
public static void modify(Student b) {
b.name = "李四"; // 由于Student是引用类型,实际的对象也被修改
}
为什么说Java只有值传递?
明明看到形参修改了对象的属性,为什么说引用类型也是值传递呢?
引用类型分为两部分:栈上的引用变量、堆上的实际对象
刚刚的例子验证了堆上的实际对象可以被修改,那么,当传递引用类型的参数时,参数的指向能否修改呢?
public static void main(String[] args) {
Student stu = new Student();
stu.name = "张三";
modify(stu);
System.out.println(stu.name); // 当前参数指向的实际对象没有被修改,输出张三
}
public static void modify(Student b) {
b = new Student(); // 形参改变了引用对象,由于引用值是值传递,并没有改变实参的引用对象
b.name = "李四";
}
当函数接到参数时,如果参数是引用类型,我们只能接到引用的值,并创建新的变量保存了引用的值。虽然可以对引用指向的实际对象做修改,但不能修改原始的引用。
这就是为什么说“Java只有值传递”:对于基本类型,拿到的变量值的副本;对于引用类型,拿到的对象引用的副本。
C++ 的引用传递允许直接操作变量本身(类似于别名),而 Java 为了安全性和内存管理的简化,屏蔽了指针操作,统一使用“值传递引用地址”的方式。
特殊类型的传值实验
String:
public static void main(String[] args) {
String str = new String();
str = "张三";
modify(str);
System.out.println(str); // 当前参数指向的实际对象没有被修改,输出张三
}
public static void modify(String b) {
b = "李四"; // 字符串的直接赋值等于修改引用,String本身是不可变对象
}
String是不可变对象,修改、拼接、拆分等操作本质上都是创建新对象+更新引用。
包装类
public static void main(String[] args) {
Integer num = Integer.valueOf(100);
modify(num);
System.out.println(num); // 当前参数指向的实际对象没有被修改,输出100
}
public static void modify(Integer b) {
b = 9999; // 包装类的直接赋值等于修改引用,包装类也是不可变对象
}
包装类也是不可变对象,同上
数组
public static void main(String[] args) {
int[] num = new int[10];
num[0] = 100;
modify(num);
System.out.println(num[0]); // 当前参数指向的实际对象被修改,输出9999
}
public static void modify(int[] b) {
b[0] = 9999; // 数组是对象,修改元素相当于修改实际引用对象的属性
}
数组也是对象,修改元素的操作可以理解成修改对象属性的语法糖,所以函数外的实参也是会被修改的。
由于“Java只有值传递”,如果对数组重新赋值呢?
public static void main(String[] args) {
int[] num = new int[10];
num[0] = 100;
modify(num);
System.out.println(num[0]); // 当前参数指向的实际对象没有被修改,输出100
}
public static void modify(int[] b) {
b = new int[10]; // 此处发生了引用变化,由于是值传递,未修改实参的引用
b[0] = 9999; // 实际修改的数组已经不是当时传过来的数组了
}
显然,重新赋值的行为改变了形参的地址,但未改变实参的地址。
如果不想改变原始值怎么办?
拷贝
- 拷贝是指将当前对象克隆一份,并传递克隆对象的引用地址。
- 浅拷贝:基本类型直接复制,引用类型复制引用,不创建新的引用对象
- 深拷贝:基本类型直接赋值,引用类型递归拷贝,创建新的引用对象
受限于篇幅,拷贝的内容先不写了。面试中需要注意:对于包含引用类型成员的类,浅拷贝会导致新旧对象共享同一个成员对象,修改其中一个对象的成员,依然会影响另一个对象。
总结
- 值传递和引用传递的区别:修改形参是否影响实参
- Java中只有值传递
- 对于基本类型,传递的是数值的副本
- 对于引用类型,传递的是引用(内存地址)的副本
扩展
如果你受够了重复写增删改查、配权限、搭流程……试试 JNPF。基于 SpringBoot + Vue.js,Java/.Net 双核驱动,全源码交付。拖拽生成后台、表单、报表,支持国产化环境,代码随时导出二次开发。不是“低代码玩具”,是真正给研发人员提效的引擎。点击免费体验,把精力留给更难的挑战。