Java8专题二《通过行为参数化传递代码》

Java8提出了三个新概念:流处理、行为参数化、并行处理。

目录

什么是行为参数化?
行为参数化的好处是什么?
应对不断变化的需求的案例?

1. 什么是行为参数化?

行为是一个动词,指的是举止动作;参数是个名称,是一个变量;将一个“举止动作”作为参数,传递给方法。

那么行为参数化就是一个方法接受多个不同的行为作为参数,并在内部使用它们,完成不同的行为能力。

2. 行为参数化的好处是什么?

行为参数化可以让代码更好的应用不断变化的需求,减少未来的工作量,减少冗余的代码。

所谓传递代码,就是将新行为作为参数传递给方法。在Java8之前需要为接口声明许多只用一次的实体类,实现起来很啰嗦。在Java8之前可以用匿名类来减少这种啰嗦的代码。

3. 应对不断变化的需求的案例

编写能够应对变化的需求的代码并不容易,让我们来看一个例子,我们会逐渐改进这个例子。以展示一些让代码更灵活的最佳做法。
Alice是一个农场的农夫,你需要帮他实现一个从列表中筛选绿苹果的功能,听起来很简单吧。

3.1 小试牛刀,第一个解决方案可能是下面这样的

 public static List<Apple> filterGreenApples(List<Apple> inventory) {
        List<Apple> result = new ArrayList<Apple>();
        for (Apple apple : inventory) {
            if ("green".equals(apple.getColor()) {
                result.add(apple);
            }
        } return result;
    }

现在Alice改变注意了,他还想要筛选红苹果,你该怎么做呢?简单的解决当大就是复制这个方法,把名字改成filterRedApples,然后更改if条件匹配红苹果。然而Alice想要筛选多种苹果:浅绿色、暗红色、黄色…这种做法就应付不了了。一个良好的原则是在编写类似的代码后,尝试将其抽象化。

3.2 再展身手,把颜色作为参数

 public static List<Apple> filterApplesByColor(List<Apple> inventory,
                                                  String color) {
        List<Apple> result = new ArrayList<Apple>();
        for (Apple apple : inventory) {
            if (apple.getColor().equals(color)) {
                result.add(apple);
            }
        }
        return result;
    }

Alice又跑来跟你说,要是能区分轻苹果和重苹果就太好了,重苹果一般是150g。作为软件工程师,你早就想到Alice可能还会更改重量,于是你是这样解决的:

 public static List<Apple> filterApplesByWeight(List<Apple> inventory,
                                                   int weight) {
        List<Apple> result = new ArrayList<Apple>();
        For(Apple apple:inventory){
            if (apple.getWeight() > weight) {
                result.add(apple);
            }
        }
        return result;
    }

解决方案不错,但是请注意,你复制了大部分的代码来实现遍历库存,并对每一个苹果应用筛选条件。这有点令人失望,应为它打破了DRY(Don’t Repeat Yourself,不要重复你自己)的软件原则。

3.3 第三次尝试:对你想到的每一个属性做筛选

 public static List<Apple> filterApples(List<Apple> inventory, String color,
                                           int weight, boolean flag) {
        List<Apple> result = new ArrayList<Apple>();
        for (Apple apple : inventory) {
            if ((flag && apple.getColor().equals(color)) ||
                    (!flag && apple.getWeight() > weight)) {
                result.add(apple);
            }
        }
        return result;
    }
 
 
//你可以这么调用:
List<Apple> greenApples = filterApples(inventory, "green", 0, true); 
List<Apple> heavyApples = filterApples(inventory, "", 150, false); 

这种做法好吗?目前只是颜色和重量两个属性的判断,如果需要过滤掉生产地、形状... 属性呢?你会有好多个重复的filter方法,或一个巨大的非常复杂的方法。对与组合属性的扩展性好像不太友好。

行为参数化

让我们后退一步来看看更高层次的抽象。一种可能的解决方案是对你的选择标准建模:你考虑的是苹果,需要根据Apple的某些属性(比如绿色、重量)来返回一个boolean值。我们称它为谓词(一个返回boolean值的函数),让我们定义一个接口为选择标准建模。

public interface ApplePredicate {
        boolean test(Apple apple);
    }

现在你可以用ApplePredicate的多个实现代表不同的选择标准了。

仅仅选出重的苹果

public class AppleHeavyWeightPredicate implements ApplePredicate {
        public boolean test(Apple apple) {
            return apple.getWeight() > 150;
        }
    }

仅仅选出绿苹果

    public class AppleGreenColorPredicate implements ApplePredicate {
        public boolean test(Apple apple) {
            return "green".equals(apple.getColor());
        }
    }

你可以把这些看作是filter方法的不同行为。这些行为就像是不同的策略,ApplePredicate就像是一个算法蔟。
但是,该怎么利用ApplePredicate的不同实现呢?你需要让filterApples方法接受ApplePredicate对象,对Apple做条件筛选。这就是行为参数化:让方法接受多种行为做参数,并在内部使用,来完成不同的行为。

3.4 第四次尝试:根据抽象条件筛选

 public static List<Apple> filterApples(List<Apple> inventory,
                                           ApplePredicate p) {
        List<Apple> result = new ArrayList<>();
        for (Apple apple : inventory) {
            if (p.test(apple)) {
                result.add(apple);
            }
        }
        return result;
    }

现在你把filterApples方法迭代集合的逻辑与你要应用到集合中每个元素的行为区分开了。
现在Alice让你找出所有重量超过150g的红苹果,你只需要创建一个类来实现ApplePredicate就行了。

public class AppleRedAndHeavyPredicate implements ApplePredicate {
        public boolean test(Apple apple) {
            return "red".equals(apple.getColor())
                    && apple.getWeight() > 150;
        }
    }
 
    List<Apple> redAndHeavyApples = filterApples(inventory, new AppleRedAndHeavyPredicate());

你已经做了一件很酷的事:filterApples方法的行为取决你通过ApplePredicate对象传递的代码,换句话说,你把filterApples方法的行为参数化了!
请注意,在上一个例子中,唯一重要的代码是test方法的实现,由于filterApples方法只能接受对象,所以你必须把代码包裹ApplePredicate对象里。你的做法就类似与在内联“传递代码”。你会在下一节中看到,通过使用Lambda,可以直接将表达式"red".equals(apple.getColor())
&&apple.getWeight() > 150传递给filterApples方法,省去定义多个ApplePredicate的实现类,从而去掉不必要的代码。

Java8之前是如何做的?使用匿名类

3.5 第五次尝试:使用匿名类

List<Apple> redApples = filterApples(inventory, new ApplePredicate() {
public boolean test(Apple apple) {
return "red".equals(apple.getColor());
}
});
Java8是如何做的?使用Lambda表达式

3.6 第六次尝试:使用Lambda表达式

   List<Apple> result = filterApples(inventory, (Apple apple) -> "red".equals(apple.getColor()));

匿名类与Lambda表达式的比较

匿名类:

它看起来很笨重,因为它占用了很多空间。
让我们觉得它用起来很让人费解。好的代码应该是一目了然的。虽然匿名类在一定程度上改善了为一个接口声明好几个实体类的啰嗦问题,但它仍不能令人满意。
Lambda表达式:

不得不承认这代码看上去比先前干净很多。

3.7 第七次尝试:将List类型抽象化

 public interface Predicate<T> {
        boolean test(T t);
    }
 
    public static <T> List<T> filter(List<T> list, Predicate<T> p) {
        List<T> result = new ArrayList<>();
        for (T e : list) {
            if (p.test(e)) {
                result.add(e);
            }
        }
        return result;
    }

现在你可以把filter方法用在香蕉、橘子Integer或String的列表上了。
eg.

 List<Apple> redApples =
            filter(inventory, (Apple apple) -> "red".equals(apple.getColor()));
    List<Integer> evenNumbers =
            filter(numbers, (Integer i) -> i % 2 == 0);

酷不酷?你现在在灵活性和简洁性之间找到了最佳的平衡点,这在Java8之前是不能做到的!

3.8 真实的例子

用Comparator来排序
Alice想要根据苹果的重量对库存进行排序(ps,Java8中,List自带了一个sort方法)我们用Collections.sort

// java.util.Comparator 
public interface Comparator<T> { 
 public int compare(T o1, T o2); 
} 
 
inventory.sort(new Comparator<Apple>() {
        public int compare(Apple a1, Apple a2){
            return a1.getWeight().compareTo(a2.getWeight());
        }
    });

用Lambda表达式的话,看起来是这样的:

inventory.sort( 
 (Apple a1, Apple a2) -> a1.getWeight().compareTo(a2.getWeight())); 

用 Runnable 来执行代码块

// java.lang.Runnable 
public interface Runnable{ 
 public void run(); 
} 
 
Thread t = new Thread(new Runnable() { 
 public void run(){ 
 System.out.println("Hello world"); 
 } 
}); 

用Lambda表达式的话,看起来是这样的:

Thread t = new Thread(() -> System.out.println("Hello world")); 

看完这个案例,你对行为参数化有所了解了吗?😊

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,504评论 6 496
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,434评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,089评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,378评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,472评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,506评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,519评论 3 413
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,292评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,738评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,022评论 2 329
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,194评论 1 342
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,873评论 5 338
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,536评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,162评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,413评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,075评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,080评论 2 352

推荐阅读更多精彩内容