来聊聊java8的新特性
先看一个例子,以案例带出来java的特性。
一:Lambda表达式与方法引用
1.1Lambda表达式
先聊一个不断变化的需求:
第一次需求:筛选出绿苹果
public static List<Apple> filterGreenApples(List<Apple> apples){ List<Apple> result=new ArraryList<>();//累积苹果的列表 for(Apple apple:apples){ if("green".equals(apple.getColor()){ result.add(apple); } } return result; }
第二次需求:筛选出多种颜色
重构为颜色作为参数
public static List<Apple> filterGreenApples(List<Apple> apples,String color){ List<Apple> result=new ArraryList<>();//累积苹果的列表 for(Apple apple:apples){ if(color.equals(apple.getColor()){ result.add(apple); } } return result; }
第三次需求:既能筛选颜色,也能筛重量
public static List<Apple> filterGreenApples(List<Apple> apples,String color,int weight,boolean flag){ List<Apple> result=new ArraryList<>();//累积苹果的列表 for(Apple apple:apples){ if((flag && color.equals(apple.getColor()) ||(!flag && apple.getWeight()>weight)){ result.add(apple); } } return result; }
第四次策略模式重构
public interface ApplePredicate{//策略接口 boolean test(Apple apple); }
public class AppleGreenColorPredicate implements ApplePredicate{//第一种策略 public boolean test(Apple apple){ return "green".equals(apple.getColor()); } }
public class AppleRedAndHeavyPredicate implements ApplePredicate{//第二种策略 public boolean test(Apple apple){ return "red".equals(apple.getColor())&&apple.getWeight()>150; } }
public static List<Apple> filterApples(List<Apple> apples,ApplePredicate p){ List<Apple> result=new ArrayList<>(); for(Apple apple:apples){ if(p.test(apple)){ result.add(apple); } } return result; }
使用策略的代码
List<Apple> redAndHeavyApples=filterApples(apples,new AppleRedAndHeavyPredicate());
这里有个重要的知识点是第二个参数,传递策略,也就是行为参数化。
第五次需求:匿名类重构
List<Apple> redApples=filterApples(apples,new ApplePredicate(){ public boolean test(Apple apple){ return "red",equals(apple.getColor()); } });
第六次:使用java8Lambda表达式
List<Apple> result=filterApples(apples,(Apple apple)->"red".equals(apple.getColor()));
我们可以把Lambda表达式理解为简洁地表示可传递的匿名函数的一种方式:它没有名称,但有参数列表、函数主体、返回类型。它可以作为参数传递给方法或存储在变量中。
(Apple a1,Apple a2)->a1.getWeight().compareTo(a2.getWeight());
Lambda表达式有参数、箭头和主体组成,(Apple a1,Apple a2)为参数,->为箭头,a1.getWeight().compareTo(a2.getWeight());是函数主体
用Lambda表达式之前的写法是:
Comparator<Apple> byWeight=new Comparator<Apple>(){ public int compare(Apple a1,Apple a2){ return a1.getWeight().compareTo(a2.getWeight()); } };
Lambda表达式让代码更简洁,并且更高效,降低多线程编程,后边会介绍流。
哪些是有效的Lambda表达式呢?
(String s)->s.length();//第一个是一个String类型参数并返回一个int。Lambda没有return语句,因为已经隐含了return
(Apple a)->a.getWeight()>150//也属于第一种类型,返回一个boolean类型
(int x,int y)->{//第二种类型,多个参数没有返回值。
System.out.println("Result:");
System.out.println(x+y);//注意Lambda表达式可以包含多行语句,这里是两行
}
测验Lambda语法:
(1)()->{}正确,没有参数并返回void
(2) ()->"JDWL" 正确,没有参数,并返回String做表达式
(3) ()->{return "JDWL"} 正确,没有参数,返回显示返回String类型数据
(4)(Integer i)->return "JDWL"+i;错误,return是一个表达式语句,因此需要使用花括号(Integer i)->{return "JDWL"+i;}
(5)(String s)->{"JDWL";}错误,"JDWL"是一个表达式,不是一个语句。
1.2方法引用
方法引用让你可以重复使用现有的方法定义,并像Lambda一样传递它们。在一些情况下,比使用Lambda表达式,更易读。
先前Lambda表达式
apples.sort((Apple a1,Apple a2)->a1.getWeight().compareTo(a2.getWeight()));
使用方法引用之后
apples.sort(comparing(Apple::getWeight());
方法引用可以被看作仅仅调用特定方法的Lambda的一种快捷写法。他是让你用已有的方法实现来创建Lambda表达式。
目标引用放在分隔符::前,方法的名称放在后面。例如,Apple::getWeight就是引用了Apple类中定义的方法getWeight。请记住这里不需要括号,因为你没有实际调用这个方法。方法引用就是Lambda表达式(Apple a)->a.getWeight()的快捷写法。
Supplier<Apple> c1=Apple::new;
Apple a1=c1.get();
等价于:Supplier<Apple> c1=()->new Apple();
Apple a1=c1.get();
看一个方法引用的实例:
按照重量排序的最原始写法:
public class AppleComparator implements Comparator<Apple>{ public int compare(Apple a1,Apple a2){ return a1.getWeight().compareTo(a2.getWeight()); } } apples.sort(new AppleComparator());
使用使用Lambda表达式:
apples.sort((Apple a1,Apple a2)->a1.getWeight().compareTo(a2.getWeight());
使用方法引用:
apples.sort(comparing(Apple::getWeight));
二:流
流是java8的新的api,它允许你以声明性方式处理数据集合(通过查询 语句来表达,而不是临时编写一个实现)。
如果把for循环看成外部迭代器,那么流可以看做内部迭代器。并且流还可以并行
java8之前想要对卡路里小于400的菜肴排序并获取菜肴名称的写法:
1 List<Dish> lowCaloricDishes=new ArrayList<>(); 2 for(Dish d:menus){//选择卡路里低于400卡的菜肴 3 if(d.getCalories()<400){ 4 lowCaloricDishes.add(d); 5 } 6 } 7 Collections.sort(lowCaloricDishes,new Comparator<Dish>(){//使用匿名类对菜肴排序 8 public int compare(Dish d1,Dish d2){ 9 return Integer.compare(d1.getCalories(),d2.getCalories()); 10 } 11 }); 12 List<String> lowCaloricDishesName=new ArrayList<>(); 13 for(Dish d:lowCaloricDishes){//获取菜名 14 lowCaloricDishesName.add(d.getName()); 15 }
使用java8流之后
1 List<String> lowCaloricDishesName=menus.stream().filter(d->d.getCalories()<400).sorted(comparing(Dish::getCalories)).map(Dish::getName).collect(toList());
如果使用多线程做排序只用把stream()换成parallelStream(),还可以取出前3名
List<String> lowCaloricDishesName=menus.parallelStream().filter(d->d.getCalories()<400).sorted(comparing(Dish::getCalories)).map(Dish::getName).limit(3).collect(toList());
15行代码压缩到1行,曾经有人因为面试官让写一个java8筛选、排序、获取名称的题没做出来,薪资就止步20k以下了。
java8流的好处:
声明性:说明想要完成什么,而不是说明如何实现一个操作。更简洁,更易读
可复合:更灵活的组合
可并行:性能更好
流操作分为两种:
一种是filter、sorted、map、limit、distinct中间操作,连成一条流水线
另外是终端操作,包含collect、count、forEach,会触发流水线执行并关闭它。