Java 里“对象变量”到底是什么?
以及
函数里改的到底是“对象本身”还是“参数这个局部变量”。
我用最接地气的方式给你捋。
1. Java 里的“对象变量”是什么意思
在 Java 里,变量大致分两类:
第一类:基本类型变量
比如:
int a = 10;
double b = 3.14;
char c = 'A';
boolean flag = true;
这些变量里放的就是“值本身”。
第二类:引用类型变量(你刚说的“对象变量”基本就是这个意思)
比如:
String s = "hello";
User u = new User();
List<Integer> list = new ArrayList<>();
这里的:
-
s -
u -
list
都不是直接装“对象本体”,
而是装着一个引用,你可以粗略理解成“对象的地址/门牌号/指向对象的箭头”。
所以“对象变量”其实更准确地说是:
引用类型的变量
也就是:这个变量里存的不是对象内容本身,而是对象的引用。
2. Java 为什么说“值传递,但对象变量里存的是引用”
这个说法第一次看确实很绕。
比如:
public static void test(int x) {
x = 20;
}调用:
int a = 10;
test(a);
System.out.println(a); // 10
这是最普通的值传递:
-
a的值是10 -
调用函数时,把
10复制一份给x -
你改
x,不会影响a
再看对象:
public static void test(User u) {
u.age = 20;
}调用:
User user = new User();
user.age = 10;
test(user);
System.out.println(user.age); // 20
这里很多人就会以为“Java 是引用传递”。
其实不是。
真正发生的是:
-
user这个变量里存的是一个“引用” -
调用
test(user)时,传过去的是这个引用的副本 -
虽然是副本,但它和原来的
user仍然指向同一个对象 -
所以
u.age = 20本质上是在改那个共同指向的对象
所以 Java 依然是值传递,只不过传递的这个值,恰好是“引用”。
3. 你说的这句,理解得差不多了
你问:
指的是这个函数形参是对象吗,而不是基本数据类型?
对,基本可以这么理解。
更准确点说:
-
如果形参是
int、double这种,传的是基本类型值 -
如果形参是
User、String、List这种,传的是引用值
比如:
public static void f1(int x) {}
public static void f2(User u) {}
public static void f3(List<Integer> list) {}
这里:
-
x是基本类型参数 -
u和list是引用类型参数
4. 判断会不会影响实参,要看“改的是什么”
这是最重要的。
你说:
然后看在函数里面的修改会不会影响到真实的实参对象?
对,但你要分成两种“修改”。
情况 A:修改对象内部状态
例如:
class User {
int age;
}
public static void change(User u) {
u.age = 99;
}
调用:
User user = new User();
user.age = 18;
change(user);
System.out.println(user.age); // 99
为什么会影响?
因为:
-
u和user指向的是同一个User对象 -
u.age = 99是在修改这个对象本身
所以外面能看到变化。
情况 B:让形参指向一个新对象
例如:
public static void change(User u) {
u = new User();
u.age = 99;
}调用:
User user = new User();
user.age = 18;
change(user);
System.out.println(user.age); // 18
为什么这次外面没变?
因为:
-
一开始
u和user指向同一个对象 -
但
u = new User()之后,u改去指向新对象了 -
外面的
user还指向原来那个对象
所以外面不受影响。
5. 一句话总结 Java
你可以先记这个:
Java 参数永远是值传递。
如果传的是基本类型,复制的是值。
如果传的是对象类型,复制的是引用。
改对象内容会影响外面,改形参自己的指向不会影响外面。
6. Python 和这个非常像
你说:
Python你说的是基本数据类型的修改是不会影响到实参的,但是如果传的是可变类型的对象就会原地修改实参对象?
对,这个理解基本是对的。
但我帮你再修正得更精确一点。
Python 里,不要硬分“基本数据类型”
Python 更常见的分法是:
不可变类型:
int、float、str、tuple、bool可变类型:
list、dict、set
不可变类型:函数里改,通常不影响外面
例如:
def f(x):
x = x + 1
a = 10
f(a)
print(a) # 10
为什么?
因为整数不可变,x = x + 1 不是修改原来的 10,
而是让 x 重新绑定到新对象 11。
外面的 a 还是原来的 10。
可变类型:如果原地改,会影响外面
例如:
def f(lst):
lst.append(4)
a = [1, 2, 3]
f(a)
print(a) # [1, 2, 3, 4]
为什么会影响?
因为:
-
lst和a指向同一个列表对象 -
append是原地修改列表内容
所以外面能看到变化。
但如果只是重新绑定,也不会影响外面
例如:
def f(lst):
lst = [100, 200]
a = [1, 2, 3]
f(a)
print(a) # [1, 2, 3]
因为这里不是改原来的列表,
而是让 lst 这个局部变量指向了一个新列表。
和 Java 的这个现象非常像。
7. 你现在可以这样对比 Java 和 Python
Java
-
基本类型:改形参,不影响实参
-
引用类型:
-
改对象内容,影响实参对象
-
改形参指向,不影响实参
-
Python
-
不可变对象:通常不会影响外面
-
可变对象:
-
原地修改对象内容,会影响外面
-
重新绑定参数,不影响外面
-
8. 给你一个并排对比,你就更清楚了
Java
class User {
int age;
}
public class Main {
public static void changeAge(User u) {
u.age = 30;
}
public static void changeRef(User u) {
u = new User();
u.age = 50;
}
public static void main(String[] args) {
User user = new User();
user.age = 18;
changeAge(user);
System.out.println(user.age); // 30
changeRef(user);
System.out.println(user.age); // 30
}
}
Python
def change_content(lst):
lst.append(4)
def change_ref(lst):
lst = [100, 200]
a = [1, 2, 3]
change_content(a)
print(a) # [1, 2, 3, 4]
change_ref(a)
print(a) # [1, 2, 3, 4]
9. 最后我帮你把你的这句话改成标准理解
你原来的理解可以整理成这样:
Java
Java 是值传递。
如果参数是基本类型,传的是值本身。
如果参数是对象类型,传的是对象引用的副本。
所以函数里修改对象内容,可能影响实参对应的对象;
但让形参重新指向新对象,不会影响实参。
Python
Python 函数参数传递的是对象引用。
对于不可变对象,操作通常会产生新对象,所以不会影响外面的变量;
对于可变对象,如果在函数里原地修改对象内容,就会影响外面的对象;
如果只是让形参重新绑定到新对象,则不会影响外面的变量。
1042

被折叠的 条评论
为什么被折叠?



