前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