前2天听了熊老师的一个Session,关于List Process的,差点忘了总结,现记录如下:
从一个Story开始,现需求如下:(注:List中的每个元素都是都是整数)
(1)、给定一个List,把List中的每个元素+1,返回新的List;
(2)、给定一个List,把List中的每个元素*2,返回新的List;
(3)、给定一个List,取出其中的偶数,返回新的List;
以TDD的思维开发,先写Testcase
public class ListProcessTest {
@Test
public void test_every_element_in_list_add_one() throws Exception {
List<Integer> inputList = Arrays.asList(1, 2, 3, 4);
assertThat(ListProcess.addOne(inputList), is(Arrays.asList(2, 3, 4, 5)));
}
@Test
public void test_every_element_in_list_multiply_two() throws Exception {
List<Integer> inputList = Arrays.asList(1, 2, 3, 4);
assertThat(ListProcess.multiplyTwo(inputList), is(Arrays.asList(2, 4, 6, 8)));
}
@Test
public void test_get_even_from_input_list() throws Exception {
List<Integer> inputList = Arrays.asList(1, 2, 3, 4);
assertThat(ListProcess.getEvenList(inputList), is(Arrays.asList(2, 4)));
}
}
第一次大家对addOne,multiplyTwo,getEvenList实现如下
public class ListProcess {
public static List<Integer> addOne(List<Integer> inputList) {
List<Integer> result = new ArrayList<Integer>();
for (int input : inputList) {
result.add(input + 1);
}
return result;
}
public static List<Integer> multiplyTwo(List<Integer> inputList) {
List<Integer> result = new ArrayList<Integer>();
for (int input : inputList) {
result.add(input * 2);
}
return result;
}
public static List<Integer> getEvenList(List<Integer> inputList) {
List<Integer> result = new ArrayList<Integer>();
for (int input : inputList) {
if (input % 2 == 0) {
result.add(input);
}
}
return result;
}
}
功能完成,测试通过之后,马上就回发现这段代码的Bad Smell,一共才5,6行的函数,居然和其他函数有4句代码是重复的,然后熊老师就开始给我们讲这个Session的主要目的。
对于List的处理,我们通常可以分为3类
第一类:对List中的每个元素,做同样的处理,得到一个新的集合,即遍历列表,针对每个elment做一元函数Func(x)的映射,因为一一对应,因此可简称为Map操作;
第二类:判断List中的的每个元素是否满足某个条件,对满足条件的元素进行相应的逻辑处理得到新的List。比方说filter,select,reduce等等操作。
第三类:求取集合中所有元素的总值,比方说求和操作,这类操作对于每个元素来说都是一个2元操作,func(init_value,x),即把当前element的值加初始值上。可以简称为Accumulation操作。
[img]http://dl.iteye.com/upload/attachment/0065/9520/cabfe7b1-2ceb-3795-bbb9-e801a7362246.png[/img]
在Google的collections包中就根据这样的思想提供了相应的工具类,改造过后的代码
public class ListProcess {
public static List<Integer> addOne(List<Integer> inputList) {
return Lists.transform(inputList, new Function<Integer, Integer>() {
@Override
public Integer apply(@Nullable Integer integer) {
return ++integer;
}
});
}
public static List<Integer> multiplyTwo(List<Integer> inputList) {
return Lists.transform(inputList, new Function<Integer, Integer>() {
@Override
public Integer apply(@Nullable Integer integer) {
return integer * 2;
}
});
}
public static List<Integer> getEvenList(List<Integer> inputList) {
//注:不知为何,Google Collections没有把Filter也做在Lists里面,而是放到Collections2里面,返回一个Collection<E>,我们不得不自己转成List
List<Integer> result = new ArrayList<Integer>();
result.addAll(
Collections2.filter(inputList, new Predicate<Integer>() {
@Override
public boolean apply(@Nullable Integer integer) {
return integer % 2 == 0;
}
}));
return result;
}
}
这样改完之后有什么好处呢?
第一:代码更直观,我们关注的只是function,predicate中的内容,也就是我们的核心业务逻辑。
第二:可以分布式处理,因为循环的时候CPU需要记录当前处理的元素下标之类的信号量,整个循环的处理过程会绑定在一个CPU上处理,就只能发挥一个CPU的计算能力,多核时代多浪费。如果使用Google的这种方式,就可以把一个很大的List拆分成一个个小的任务,然后并发执行之后再合并回来,也就是Map-Reduce的思想。
最后,熊老师推荐了一本经典书籍<Structure and Interpretation of Computer Programs>(计算机程序的构造和解释),说是看完之后,编程基本功会大大提高。刚加入清单,还没开始读,等读完之后再来写篇读后感。
补:关于为什么不把Filter的功能做到Lists类的原因参见:http://code.google.com/p/guava-libraries/wiki/IdeaGraveyard