应对不断变化得需求
初始牛刀:删选绿苹果
public static List<Apple> filetGreenApples (List<Apple> inventory){ //累积苹果得列表 ArrayList<Apple> objects = new ArrayList<>(); for (Apple apple : inventory) { if(apple.getColor().equals("green")){//仅仅选出绿苹果 objects.add(apple); } } return objects; }
但是农民伯伯改变注意了,他还想要删选红苹果。应该怎么做?
简单得解决办法就是复制这个方法,把名字改为filterRedApples,然后更改if条件来匹配红苹果。然而农民伯伯想要删选更多得颜色,这种方法就应付不了了。一个良好的原则是在编写类似得代码后,尝试将其抽象化。
再展身手:将颜色作为参数
一种做法就是给方法添加一个参数,把颜色作为参数,这样就要可以灵活得变化了。
ArrayList<Apple> objects = new ArrayList<>(); for (Apple apple : inventory) { if(apple.getColor().equals(color)){ objects.add(apple); } } return objects;
现在只需要向下面一样调用方法,农民伯伯就饿很满意了:
filetApplesByColor(inventory,"green"); filetApplesByColor(inventory,"red");
那这个时候农名伯伯又要能区分苹果得重量,重得苹果大于150g 这时候怎么办呢?
public static List<Apple> filetApplesByWeight (List<Apple> inventory,Long weight){ //累积苹果得列表 ArrayList<Apple> objects = new ArrayList<>(); for (Apple apple : inventory) { if(apple.getWeight()>weight){ objects.add(apple); } } return objects; }
上面代码解决方案是可以的,但是复制了大部分代码来实现遍历库存,并对每个苹果应用筛选条件。打破了DRY的软件工程原则。如果想改变筛选遍历方式来提升性能,那就要修改所有方法的实现,而不是只改一个。
第三次尝试:对能想到的属性做筛选
一种就是最笨拙的方式:把所有属性结合起来
public static List<Apple> filetApples (List<Apple> inventory,Long weight,String color){ //累积苹果得列表 ArrayList<Apple> objects = new ArrayList<>(); for (Apple apple : inventory) { if(apple.getWeight()>weight && apple.getColor().equals(color)){ objects.add(apple); } } return objects; }
这个解决方案还是没有能够很好的应对变化的需求。
行为参数化
需要一种比添加很多参数更好的解决办法来应对变化的需求,比如:对选择标准建模,考虑的是苹果,需要根据Apple的某些属性(颜色是绿色吗?,重量大于150g吗?)来返回一个Boolean值。我们把它称为谓词(就是一个返回Boolean的函数)。
首先定义一个接口:
public interface ApplePredicate { boolean test (Apple apple); }
现在就可以根据ApplePredicate的不多个实现来代表不同的选择标准了
public class ApplePredicateColor implements ApplePredicate { @Override public boolean test(Apple apple) { return apple.getColor().equals("green"); } }
public class ApplePredicateWeight implements ApplePredicate { @Override public boolean test(Apple apple) { return apple.getWeight()>150; } }
可以把这些标准看作filter方法的不同行为,定义一族算法,把他们封装起来(称为策略),然后再运行时选择一个算法。算法族就是 ApplePredicate,不同的策略就是ApplePredicateColor和ApplePredicateWeight。
但是,该怎么利用Apple Predicate的不同实现呢? 需要filterApples方法接受Apple Predicate对象,对Apple做条件测试,这就是行为参数化:让方法接受多种行为作为参数,并在内部使用,来完成不同的行为。
第四次尝试:根据抽象条件筛选
利用ApplePredicate改过之后,filter方法是这样的:
public static List<Apple> filetApples (List<Apple> inventory,ApplePredicate applePredicate){ //累积苹果得列表 ArrayList<Apple> objects = new ArrayList<>(); for (Apple apple : inventory) { if(applePredicate.test(apple)){ //谓词对象封装了测试苹果的条件 objects.add(apple); } } return objects; }
庆祝一下,这样的话代码就灵活多了,读起来用起来也很方便!现在只需要创建不同的Apple Predicate对象,并将它们传递给filter Apples方法。比如,如果需要找出所有重量超过150g的红苹果,只需要创建一个类来实现ApplePredicate就行了。代码足够灵活,可以应对任何涉及苹果属性的需求变更了:
public class AppleRedAndHeavyPredicate implements ApplePredicate { @Override public boolean test(Apple apple) { return apple.getWeight()>150 && apple.getColor().equals("red"); } } public static List<Apple> filterApples (List<Apple> inventory, AppleRedAndHeavyPredicate appleRedAndHeavyPredicate){ }
filterApples方法的行为取决于你通过ApplePredicate对象传递的代码。也就是说把 filterApples方法的行为参数化了。
但是,由于该filterApples方法只能接受对象,所以必须把代码包裹在ApplePredicate对象里。通过一个实现了test方法的对象来传递布尔表达式的。
使用匿名内部类
下面的代码显示了如何通过创建一个匿名内部类实现ApplePredicate的对象,重写筛选的例子;
filterApples(inventory, new ApplePredicate() { @Override public boolean test(Apple apple) { return apple.getWeight()>150; } });
使用匿名内部类的缺点:往往很笨重,占用了很多空间。用起来让人费解
使用Lambda表达式
List<Apple> apples = filterApples(inventory, (Apple apple) -> apple.getColor().equals("red"));
代码看上去干净很多,看起来更像问题陈述本身了,已经解决了啰嗦的问题。
将List类型抽象化
目前,filterApples方法还是只适用于Apple。可以将List类型抽象化,从而超越你眼前要处理的问题。
public interface Predicate<T> { boolean test (T t); } public static <T>List<T> filter(List<T>list , Predicate<T> p){ ArrayList<T> objects = new ArrayList<>(); for (T t : list) { if(p.test(t)){ objects.add(t); } } return objects; }
现在可以把filter方法用在香蕉、橘子、Integer或String的列表上了。下面有一个例子。
小结
Lambda表达式
定义;简洁的表示可传递匿名函数的一种方式,他没有名称,但是有参数列表、返回类型、可能还有一个 可以抛出的异常列表。
组成:参数列表 箭头 Lambda主体
函数式接口
predicate:
@FunctionalInterface public interface Predicate<T> { /** * Evaluates this predicate on the given argument. * * @param t the input argument * @return {@code true} if the input argument matches the predicate, * otherwise {@code false} */ boolean test(T t);
此接口定义了一个名叫test的抽象方法。接受泛型T对象,并且返回一个boolean。如果需要表示涉及类型T的不然表达式时可以使用。
String p ="周杰伦轮"; Predicate<String> predicate = name-> name.length()>3; boolean test = predicate.test(p); System.out.println(test);
组合使用Predicate
Predicate.and()
两个条件都要满足;
List<String> names = Arrays.asList("Adam", "Alexander", "John", "Tom"); Predicate<String> predicate1 = str -> str.startsWith("A"); Predicate<String> predicate2 = str -> str.length() < 5; List<String> result = names.stream() .filter(predicate1.and(predicate2)) .collect(Collectors.toList());
Predicate.or()
满足一个就可以
List<String> names = Arrays.asList("Adam", "Alexander", "John", "Tom"); Predicate<String> predicate3 = str -> str.startsWith("J"); Predicate<String> predicate4 = str -> str.length() < 4; List<String> result2 = names.stream() .filter(predicate3.or(predicate4)) .collect(Collectors.toList());
Predicate.negate()
将此条件取反
Predicate predicate2 = str -> str.length() < 4;
相当于
Predicate predicate2 = str -> str.length() >= 4;
Predicate<String> predicate1 = str -> str.startsWith("J"); Predicate<String> predicate2 = str -> str.length() < 4; List<String> result = names.stream() .filter(predicate1.or(predicate2.negate())) .collect(Collectors.toList());
内联的方式组合使用Predicates
List<String> result = names.stream() .filter(((Predicate<String>)name -> name.startsWith("A")) .and(name -> name.length()<5)) .collect(Collectors.toList());
组合Predicates集合
List<Predicate<String>> allPredicates = new ArrayList<Predicate<String>>(); allPredicates.add(str -> str.startsWith("A")); allPredicates.add(str -> str.contains("d")); allPredicates.add(str -> str.length() > 4); List<String> result = names.stream() .filter(allPredicates.stream().reduce(x->true, Predicate::and)) .collect(Collectors.toList());
Consumer
@FunctionalInterface public interface Consumer<T> { /** * Performs this operation on the given argument. * * @param t the input argument */ void accept(T t);
此接口定义了一个accept抽象方法,接受泛型T的对象,没有返回值。
比如下面代码;
Arrays.asList(1,2,3,4,5).forEach(( i)->{ System.out.println(i); }); }
Function
@FunctionalInterface public interface Function<T, R> { /** * Applies this function to the given argument. * * @param t the function argument * @return the function result */ R apply(T t);
此接口定义了一个apply的方法,接受一个泛型T的对象,并且返回一个泛型R的对象。如果你要定义Lambda,将输入对象的信息映射到代码中,就可以使用这个接口。
函数类型特化
Java类型要么是引用类型,要么是原始类型。但是泛型只能绑定到引用类型。
装箱:将原始类型转换为对应的引用类型
拆箱: 将引用类型转换为对应的原始类型
Java有一个自动装箱机制:装箱拆箱操作自动完成。
但是性能方面付出代价。装箱后的值本质上就是把原始类型包裹起来,并保存在堆里。因此,装箱后的值需要更多的内存,并需要额外的内存搜索来获取被包裹的原始值。
总结
方法引用
方法引用可以重复使用现有的方法定义,并像Lambda一样传递他们,方法引用可以被看作仅仅调用特定方法的Lambda的一种快捷写法。
基本思想:如果一个Lambda代表的只是“直接调用这个方法”,那最好还是用名称来调用它,而不是去描述如何调用它。
构建方法引用
方法引用主要有三类
- 指向静态方法的方法引用(比如 Integer的parseInt方法,可以写作 Integer::parseInt)
- 指向任意类型实例方法的方法引用(比如 String的length方法,写作String::length)
- 指向现有对象的实例方法的方法引用(比如有一个局部变量 expensiveTransaction用于存放Transaction类型的对象,它支持实例方法getValue,那么就可以写成expensiveTransaction::getValue)
构造函数引用
对于一个现有构造函数,可以利用名称和关键字new来创建他的一个引用: ClassName::new 。 功能与指向静态方法的引用类似。
比如:一个构造器函数没有参数,适合Supplier的签名()->Apple。
流
什么是流
1、流是JavaAPI的新成员,允许以声明性方式处理数据集合(通过查询语句来表达,而不是临时编写一个实现),可以把他们看成便利数据集的高级迭代器。
2、流可以透明的并行处理,不需要写任何多线程代码。
下图所示:使用流来筛选菜单,找出三个高热量菜肴的名字
- filter----接受Lambda,从流中排除某些元素。
- map----接受Lambda,将元素转换成其他形式活提取信息。
- limit-----截断流,使其元素不超过给定数量。
- collect----将流转换为其他形式。
流和集合
区别:
1、集合与流之间的差异在于什么时候进行计算。集合是一个内存中的数据结构,他包含数据结构中目前所有的值---集合中的每个元素都得先算出来才能添加到集合中。(可以向集合里加或者删除东西,但是不管什么时候,集合中每个元素都是放在内存里的,元素都得先算出来才能成为集合的一部分)
2、流是在概念上固定的数据结构(不能添加或删除元素),其元素使按需计算的。流就像一个延迟创建的集合:只有在消费者要求的时候才会计算值。
3、使用Collection接口需要用户去迭代(foreach),成为外部迭代。而Stream库使用内部迭代---他帮你把迭代做了,把得到的流值存在某个地方,只需要给出一个函数说要干什么就行了。
流和迭代器类似 只能遍历一次
流操作
可以连接起来的流成为中间操作(filter、map、limit等等)
关闭流的操作成为终端操作(collect)
中间操作
中间操作会返回另一个流,让多个操作可以连接起来形成一个查询。除非流水线上触发一个终端操作,否则中间操作不会执行任何处理。
终端操作
终端操作会从流的流水线生成结果
使用流
筛选和切片
用谓词筛选
方法引用检查菜肴是否适合素食者
menu.stream().filter(Dish::isVegetarian).collect(tolist());
筛选各异的元素
筛选列表中所有的偶数,并确保没有重复
List<Integer> numbers = Arrays.asList(1, 2, 3, 1, 33, 2, 3, 3, 4); numbers.stream().filter(i->i%2==0).distinct().forEach(System.out::println);
截断流
选出热量超过300卡路里的头三道菜
menu.stream().filter(d->d.getCalories()>300).limit(3).collect(toList());
跳过元素
跳过超过300卡路里的头两道菜,并返回剩下的
menu.stream().filter(d->d.getCalories()>300).skip(2).collect(toList());
映射
对流中每一个元素应用函数
流支持map方法,接受一个函数作为参数。这个函数会被应用到每个元素上,并将其映射成一个新的元素(创建一个新版本,而不是去修改)。
menu.stream().map(Dish:;getName).collect(toList());
流的扁平化
给定单词列表["HELLO",],需要返回["H","E","L","L","O"]
如果用map的话:
words.stream().mao(word->word.split("").distinct.collect(toList));
这个方法的问题在于,传递给map方法的Lambda为每个单词返回一个String[]。因此,map返回的流实际上是Stream类型的。真正需要的是Stream来表示一个字符流。
List<String[]> collect = a.stream().map(b -> b.split("")).distinct().collect(Collectors.toList()); List<Stream<String>> streams = a.stream().map(b -> b.split("")).map(Arrays::stream). distinct().collect(Collectors.toList()); List<String> strings = a.stream().map(b -> b.split("")).flatMap(Arrays::stream). distinct().collect(Collectors.toList());
使用flashMap方法的效果是,各个数组并不是分别映射成一个流,而是映射成流的内容。所有使用map(Arrays::stream)时生成的单个流都被合并起来,即扁平化成为一个流。
flatMap方法把一个流中的每个值都换成另外一个流,然后把所有的流连接成一个流。
查找和匹配
检查谓词是否至少匹配一个元素
anyMatch
查谓词是否匹配所有元素
allMatch
boolean ishealthy= menu.stream().allmatch(d->d.getCalories()<1000);
noneMatch
查找元素
findAny
返回当前流中的任意元素。可以与其他操作结合使用。
Optional<Dish> dish= menu.stream().filter(Dish::isVegetarian).findAny().collect(tolist());
查找第一个元素
findFirst
- 给定一个数字列表,找出第一个平法能被3整除的数。
Optional<Integer> collect1 = test.stream().map(n -> n * n).filter(n -> n % 3 == 0).findFirst();
规约
元素求和
- reduce
Integer reduce = numbers.stream().reduce(0, (a, b) -> a + b);
原理:首先,0作为Lambda(a)的第一个参数,从流中获得4作为第二个参数(b)。0+4=4,他成了新的累计值。然后在用累积值和流中下一个元素5调用Lambda,产生新的累积值9.接下来,在用累积值和下一个元素3调用Lambda,得到12。最后用12和流中的最后一个元素9调用Lambda,得到最终结果21。
- reduce还有一个重载的变体,不接受初始值,但是会返回一个Optional对象
Optional<Integer> reduce1 = numbers.stream().reduce((a, b) -> a + b);
最大值和最小值
Optional<Integer> reduce2 = numbers.stream().reduce(Integer::max);
测试:怎样用map和reduce方法数一数流中共有多少个菜?
思路:可以把流中每个元素都映射成数字 1 ,然后用reduce求和。相当于按顺序 数流中的元素的个数
int count= menu.stream().map(d->1).reduce(0,(a,b)->a+b);