这个用法最初是在工作中读浩哥代码看到的,由于当时时间紧张,粗略看了一下以为就是个生成 List 的方法,于我而言其最多使用的地方在 Entity、Dto、From 间的互转,以至于在今后所有的涉及到转换的地方我都开始倾向于使用此方法,但是突然有一次就掉坑了…
先看看一段正常的代码1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63public class{
public static void main(String[] args){
List userList = Lists.newArrayList(new User("Alex", 12, "Play"), new User("August", 18, "Play"), new User("Ada", 11, "Play"));
List userFormList = new ArrayList(userList.size());
userFormList = Lists.transform(userList, new Function(){
public UserForm apply(User input){
UserForm form = new UserForm();
form.setName(input.getName());
form.setAge(input.getAge());
form.setHobby(input.getHobby());
return form;
}
});
for (UserForm form : userFormList){
System.out.println(form);
}
}
}
@Getter
@Setter
class User{
String name;
Integer age;
String hobby;
public User(){
}
public User(String name, Integer age, String hobby){
super();
this.name = name;
this.age = age;
this.hobby = hobby;
}
public String toString(){
return "User [name=" + name + ", age=" + age + ", hobby=" + hobby + "]";
}
}
@Getter
@Setter
class UserForm{
String name;
Integer age;
String hobby;
Integer id;
public String toString(){
return "UserForm [name=" + name + ", age=" + age + ", hobby=" + hobby + ", Id=" + id +"]";
}
}
输出结果:1
2
3UserForm [name=Alex, age=12, hobby=Play, Id=null]
UserForm [name=August, age=18, hobby=Play, Id=null]
UserForm [name=Ada, age=11, hobby=Play, Id=null]
这段代码作用就是快速通过 List 生成 List,没什么毛病对吧,接下来我们想在生成的 List 中修改 hobby 和 Id,看看会发生什么:1
2
3
4
5
6
7
8
9Random random = new Random();
for (UserForm form : userFormList){
form.setHobby("Study, It makes me happy~");
form.setId(String.valueOf(random.nextInt(20)));
System.out.println(form);
}
for (UserForm form : userFormList){
System.out.println(form);
}
输出结果:1
2
3
4
5
6
7UserForm [name=Alex, age=12, hobby=Study, It makes me happy~, Id=15]
UserForm [name=August, age=18, hobby=Study, It makes me happy~, Id=17]
UserForm [name=Ada, age=11, hobby=Study, It makes me happy~, Id=2]
UserForm [name=Alex, age=12, hobby=Play, Id=null]
UserForm [name=August, age=18, hobby=Play, Id=null]
UserForm [name=Ada, age=11, hobby=Play, Id=null]
咦! 第一次迭代的时候输出结果和我们预期一样,但是第二次的结果怎么显示成这个样子??
这么神奇的吗??
Debug 分析问题
带着疑惑,我们 Debug 走一下流程
首先在执行 Lists.transform() 的时候会 先判断是否是 RandomAccess 类型,结合 RandomAccess 展开树我们可以发现,ArrayList 实现了 RandomAccess 接口
紧接着会创建 TransformingRandomAccessList 类,并赋值 fromList 和 function
创建完成之后,function 是没有立即执行的,此时代码直接跳转到了 迭代 userFormList 的地方
到此时开始执行 TransformingRandomAccessList 中的 listIterator() 方法开始迭代生成数据,这时候每次都会执行 transform() 方法,传入 F(From) 来源 User 类型数据,生成 T(Traget) 目标 UserForm 类型数据
说说我的理解:我们的 userFormList 在执行 Lists.transform 后是没有立刻被赋值的,它只是被转换成了 TransformingRandomAccessList 类型,此类型 重写了迭代器,每次当我们迭代 userFormList 时候,它才会调用 function 进行赋值。
带着上面的理解我们来重新看一次这段代码:1
2
3
4
5
6
7
8
9Random random = new Random();
for (UserForm form : userFormList){
form.setHobby("Study, It makes me happy~");
form.setId(String.valueOf(random.nextInt(20)));
System.out.println(form);
}
for (UserForm form : userFormList){
System.out.println(form);
}
userFormList 在第一次迭代的时候会执行 Function 中的 UserForm form = new UserForm(); 此时会创建新的 UserForm,然后进行 set 不同的值,在我们第二次进行 迭代的时候,此时又会执行 Function 中的 UserForm form = new UserForm(); 这时候会再次创建 UserForm,当你 Debug 的时候会发现两次创建的 UserForm 地址是不一样的,这样就能解释为什么我们 set 的时候不起作用啦~
带着上面得出的结论来看看源码中的官方文档:
可以说官方文档已经安排得明明白白的,它提到 Lists.transform 返回的每一个元素是 function 执行后的结果,其返回的 list 就是一个转换后的 “视图”,修改 原有的 list 会影响到现有的 list。并且 function 方法是不可逆的,生成的 list 不支持 add、addAll、set 方法,这个 function 是类似一种懒加载的机制,只有在需要的时候被调用,如果你想让返回的 List 不受此限制的话,需要自己动手 copy…
最后它还提了一句,此方法返回的 list 在你进行序列化的时候可能出现一些奇怪的操作,所以不推荐序列化。
突破 set 无法成功
在了解这个方法之后,我发现这个方法限制还蛮多的,于是找到浩哥,问了一下此方法用意,原以为是有性能上的优化,结果发现只是为了代码简洁一点,性能对比还没有普通写法来得快…
之前我们发现需要 set 的时候不能再次迭代 List,所以只有写在 Function 中才能完成我们的迭代咯,于是我又模拟了一下最常见的情况,我们稍微修改一下现有的代码:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65@Getter
@Setter
class User{
String name;
Integer age;
String hobby;
Integer id;
public User(){
}
public User(String name, Integer age, String hobby, Integer id){
super();
this.name = name;
this.age = age;
this.hobby = hobby;
this.id = id;
}
public String toString(){
return "User [name=" + name + ", age=" + age + ", hobby=" + hobby + ", id=" + id + "]";
}
}
@Getter
@Setter
class UserForm{
String name;
Integer age;
String hobby;
String secret;
public String toString(){
return "UserForm [name=" + name + ", age=" + age + ", hobby=" + hobby + ", Id=" + secret + "]";
}
}
@Test
public void ttest(){
List userList = Lists.newArrayList(new User("Alex", 12, "Play", 1), new User("August", 18, "Play", 2), new User("Ada", 11, "Play", 3));
Map secretMap = new HashMap();
secretMap.put(1, "biubiubiu~");
secretMap.put(2, "miaomiaomiao~");
secretMap.put(3, "qiuqiuqiu~");
List userFormList = new ArrayList(userList.size());
userFormList = Lists.transform(userList, new Function(){
@Override
public UserForm apply(User user){
UserForm form = new UserForm();
form.setName(user.getName());
form.setAge(user.getAge());
form.setHobby(user.getHobby());
form.setSecret(secretMap.get(user.getId()));
return form;
}
});
}
注意这里: form.setSecret(secretMap.get(user.getId())); 这里编译的时候会报错:
Cannot refer to non-final local variable secretMap define in an enclosing scope.
提示 secretMap 不是 final 修饰的变量,难道还需要使用 final 类型的变量吗? (至于为什么需要 final 修饰的变量,这个问题后面有时间再研究咯~)
将上面的代码改一下就能生效啦:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29@Test
public void ttest(){
List userList = Lists.newArrayList(new User("Alex", 12, "Play", 1), new User("August", 18, "Play", 2), new User("Ada", 11, "Play", 3));
Map secretMap = new HashMap();
secretMap.put(1, "biubiubiu~");
secretMap.put(2, "miaomiaomiao~");
secretMap.put(3, "qiuqiuqiu~");
final Map finalSecretMap = secretMap;
List userFormList = new ArrayList(userList.size());
userFormList = Lists.transform(userList, new Function(){
@Override
public UserForm apply(User user){
UserForm form = new UserForm();
form.setName(user.getName());
form.setAge(user.getAge());
form.setHobby(user.getHobby());
form.setSecret(finalSecretMap.get(user.getId()));
return form;
}
});
for (UserForm form : userFormList){
System.out.println(form);
}
}
再看看输出结果:1
2
3UserForm [name=Alex, age=12, hobby=Play, Id=biubiubiu~]
UserForm [name=August, age=18, hobby=Play, Id=miaomiaomiao~]
UserForm [name=Ada, age=11, hobby=Play, Id=qiuqiuqiu~]
Nice~~~