做了挺长时间开发之后就会陷入一个思维定式,往往喜欢复制某些功能逻辑现有的实现方式
比如我们查出来了一个列表放入List之后,有时候需要对列表进行其他属性的填充,一般会写成如下形式
List<User> users = ...;
fillItems(users);
private void fillItems(List<User> users) {
// 忽略其他逻辑判断 todo
for (User user : users) {
// 获取其他属性并填充进user对象
user.setXxx(xx);
}
}
于是乎就会忽视了为什么这段代码有用,并且想当然的认为这样形式的代码都是有效的,于是有了下面的代码
public List<XxxDomain> findByDeptId(Integer deptId) {
getDeptId(deptId);
YyyDomain yyyDomain = yyyService.find(deptId);
}
private void getDeptId(Integer deptId) {
Integer staffId = SecurityContextHelper.getCurrentUserId();
if (deptId == null || deptId <= 0) {
StaffDomain staffDomain = staffService.find(staffId);
if (staffDomain != null) {
deptId = staffDomain.getGid();
}
}
CheckUtil.checkBusiness(deptId == null || deptId <= 0, "未获取到合法的部门信息");
}
毫无疑问 认为getDeptId方法会填充上正确的deptId并返回
但是 并没有,在执行 yyyService.find(deptId)的时候 deptId依然是当前方法findByDeptId传入的参数deptId 值并未发生变化,问题出在哪里呢?
于是回想起,Java方法中参数传递的是什么,为什么fillItems会成功而getDeptId失败呢?于是做了如下测试
注意:本次测试环境为jdk1.8,有基本数据类型常量池和字符串常量池
先测试基本数据类型int
public static void main(String[] args) {
// 基本数据类型测试
int intNum = -1;
System.out.println(intNum);
updateInteger(intNum);
System.out.println(intNum);
}
public static void updateInteger(int num) {
num = 100;
}
输出结果为
-1
-1
对代码进行反汇编:
public static void main(java.lang.String[]);
Code:
0: iconst_m1
1: istore_1
2: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
5: iload_1
6: invokevirtual #3 // Method java/io/PrintStream.println:(I)V
9: iload_1
10: invokestatic #4 // Method updateInteger:(I)V
13: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
16: iload_1
17: invokevirtual #3 // Method java/io/PrintStream.println:(I)V
20: return
public static void updateInteger(int);
Code:
0: bipush 100
2: istore_0
3: return
发现main方法中code:9和10是加载了变量intNum进入updateInteger方法,updateInteger方法中只将100压入栈赋给了方法内局部变量(istore_0);与main方法中的intNum并无关联。
测试String类型
public static void main(String[] args) {
// String类型测试
String str = "A";
System.out.println(str);
updateString(str);
System.out.println(str);
}
private static void updateString(String str) {
str = "B";
}
结果
A
A
反汇编
public static void main(java.lang.String[]);
Code:
0: ldc #2 // String A
2: astore_1
3: getstatic #3 // Field java/lang/System.out:Ljava/io/PrintStream;
6: aload_1
7: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
10: aload_1
11: invokestatic #5 // Method updateString:(Ljava/lang/String;)V
14: getstatic #3 // Field java/lang/System.out:Ljava/io/PrintStream;
17: aload_1
18: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
21: return
public static void updateString(java.lang.String);
Code:
0: ldc #6 // String B
2: astore_0
3: return
与int类型类似
测试引用类型 修改自定义对象的属性
public static void main(String[] args) {
// 引用类型 对象属性修改测试
User user = new User("name", 27);
System.out.println(user.toString());
updateUser(user);
System.out.println(user.toString());
}
public static void updateUser(User user) {
user.setAge(100);
user.setName("newName");
}
static class User {
private String name;
private Integer age;
public User() {
}
public User(String name, Integer age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
结果
User{name='name', age=27}
User{name='newName', age=100}
反汇编
public static void main(java.lang.String[]);
Code:
0: new #2 // class com/study/base/MethodTest$User
3: dup
4: ldc #3 // String name
6: bipush 27
8: invokestatic #4 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
11: invokespecial #5 // Method com/study/base/MethodTest$User."<init>":(Ljava/lang/String;Ljava/lang/Integer;)V
14: astore_1
15: getstatic #6 // Field java/lang/System.out:Ljava/io/PrintStream;
18: aload_1
19: invokevirtual #7 // Method com/study/base/MethodTest$User.toString:()Ljava/lang/String;
22: invokevirtual #8 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
25: aload_1
26: invokestatic #9 // Method updateUser:(Lcom/study/base/MethodTest$User;)V
29: getstatic #6 // Field java/lang/System.out:Ljava/io/PrintStream;
32: aload_1
33: invokevirtual #7 // Method com/study/base/MethodTest$User.toString:()Ljava/lang/String;
36: invokevirtual #8 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
39: return
public static void updateUser(com.study.base.MethodTest$User);
Code:
0: aload_0
1: bipush 100
3: invokestatic #4 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
6: invokevirtual #10 // Method com/study/base/MethodTest$User.setAge:(Ljava/lang/Integer;)V
9: aload_0
10: ldc #11 // String newName
12: invokevirtual #12 // Method com/study/base/MethodTest$User.setName:(Ljava/lang/String;)V
15: return
测试引用类型 修改自定义对象为新对象
public static void main(String[] args) {
// 引用类型 对象重新赋值测试
User user = new User("name", 27);
System.out.println(user.toString());
updateUserNew(user);
System.out.println(user.toString());
}
private static void updateUserNew(User user) {
user = new User();
user.setAge(100);
user.setName("newName");
}
结果
User{name='name', age=27}
User{name='name', age=27}
反汇编
public static void main(java.lang.String[]);
Code:
0: new #2 // class com/study/base/MethodTest$User
3: dup
4: ldc #3 // String name
6: bipush 27
8: invokestatic #4 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
11: invokespecial #5 // Method com/study/base/MethodTest$User."<init>":(Ljava/lang/String;Ljava/lang/Integer;)V
14: astore_1
15: getstatic #6 // Field java/lang/System.out:Ljava/io/PrintStream;
18: aload_1
19: invokevirtual #7 // Method com/study/base/MethodTest$User.toString:()Ljava/lang/String;
22: invokevirtual #8 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
25: aload_1
26: invokestatic #9 // Method updateUserNew:(Lcom/study/base/MethodTest$User;)V
29: getstatic #6 // Field java/lang/System.out:Ljava/io/PrintStream;
32: aload_1
33: invokevirtual #7 // Method com/study/base/MethodTest$User.toString:()Ljava/lang/String;
36: invokevirtual #8 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
39: return
public static void updateUserNew(com.study.base.MethodTest$User);
Code:
0: new #2 // class com/study/base/MethodTest$User
3: dup
4: invokespecial #10 // Method com/study/base/MethodTest$User."<init>":()V
7: astore_0
8: aload_0
9: bipush 100
11: invokestatic #4 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
14: invokevirtual #11 // Method com/study/base/MethodTest$User.setAge:(Ljava/lang/Integer;)V
17: aload_0
18: ldc #12 // String newName
20: invokevirtual #13 // Method com/study/base/MethodTest$User.setName:(Ljava/lang/String;)V
23: return
上面两个测试结果不同,因为传入updateUser(New)中的user对象其实为user对象的副本,虽然是副本,但是副本和main中的user都指同一个地址,如果我们在方法中修改了对象的属性值,这时并未改变对象引用的地址;但是如果new了新的对象,方法内user就指向了新的对象地址,main方法中的user还是指向原地址。
总结:在Java方法中参数列表有两种类型的参数,基本类型和引用类型。
基本类型:值存放在局部变量表中,无论如何修改只会修改当前栈帧的值,方法执行结束对方法外不会做任何改变;此时需要改变外层的变量,必须返回主动赋值。
引用数据类型:指针存放在局部变量表中,调用方法的时候,副本引用压栈,赋值仅改变副本的引用。但是如果通过操作副本引用的值,修改了引用地址的对象,此时方法以外的引用此地址对象当然被修改。(两个引用,同一个地址,任何修改行为2个引用同时生效)。