Java8-Lambda 表达式

浅谈Java8新特性Lambda&&Stream

Lambda表达式

  • 参考文献
    • http://www.importnew.com/16436.html
    • https://www.liaoxuefeng.com/article/001411306573093ce6ebcdd67624db98acedb2a905c8ea4000
    • http://blog.csdn.net/ioriogami/article/details/12782141/
    • http://zh.lucida.me/blog/java-8-lambdas-insideout-language-features/
  • 什么是Lambda表达式? Lambda表达式是java8引入的一个新特性,Lambda表达式虽然在java8中是新特性,但是在python或者是c#中早已存在,想要了解Lambda表达式你可能需要先去了解什么是函数式编程,今天我们就不扯远了,本文主要介绍Lambda一些基础概念和如何使用的一些例子
  • 函数式编程:
  • 为什么要学Lambda?对我个人而言,我是抱着新颖的东西很酷的心态去学习,当然在了解了Lambda和流APi之后,我希望Lambda可以是我的代码更加干净更加简洁,这里需要注意两点,Lambda是java8引入的新特性,请注意你的编码环境是否是java8,如果你想在生产环境中使用Lambda,你还需要注意生存环境是否是jdk8,以免造成不必要的麻烦
  • Lambda的作用是什么? Lambda其实就是一个匿名方法,在java8之前,我们我i饿了实现带一个方法的接口,往往需要定义一个匿名类并复写接口方法,代码很臃肿(例如下面会实现的Runnable)
  • λ表达式可以被当做是一个Object(注意措辞)。λ表达式的类型,叫做“目标类型(target type)”。λ表达式的目标类型是“函数接口(functional interface)”,这是Java8新引入的概念。它的定义是:一个接口,如果只有一个显式声明的抽象方法,那么它就是一个函数接口。一般用@FunctionalInterface标注出来(也可以不标)
  • Lambda的具体作用
    • 简化单方法接口的写法,例如Runnable
    • 配合流API使用,处理集合,详情见下放Stream章节
    • 事件监听处理(安卓常用)

初识Lambda

  • lambda本质上是一个匿名方法
  • Lambda 由三部分组成 : 参数列表,箭头(->),以及一个表达式或语句块
//java8之前写法
public int add(int x, int y){
  return x + y;
}
//lambda
(int x , int y) -> x + y;
(x,y) -> x + y; //返回两数只和,java编译器可以推断x和y的类型
(x,y) -> {return x+y;} //显示指明返回值
  • Lambda也可以没有参数,也没有返回值
() -> {System.out.println("Hello Lambda!");}
  • 有返回参数,如果只有一个,也可以java自动推断类型
c -> {return c.size();}
  • 你也可以用lambda表达式为函数接口赋值

Lambda基础示例(jdk 官方示例)

//历史写法
Runnable r1 = new Runnable(){

  @Overrride
  public void run(){
    System.out.printLn("Hello world one!")
  }

  //Lambda 写法
  Runnable r2 = () -> System.out.printLn("hello world two!")
}

//注: () 代表无参输入 -> 可以看作是分隔符,注意lambda不允许方法实现里声明外部同名变量,编译将会异常

//注意我们可以吧接口赋值给Object类型
//例如 Object obj = r2;
//但是绝对不可以 Object obj = () -> {System.out.printLn("hello world two!");}
//会报错ERROR! Object is not a functional interface!
//必须显示转换成一个函数接口
// Object o = (Runnable) () -> { System.out.println("hi"); };

Lambda表达式进行事件处理

  • 如果你用过Swing API编程或者安卓,你就会记得怎样写事件监听代码。这又是一个旧版本简单匿名类的经典用例,但现在可以不这样了。你可以用lambda表达式写出更好的事件监听代码
// Java 8之前:
JButton show =  new JButton("Show");
show.addActionListener(new ActionListener() {
    @Override
    public void actionPerformed(ActionEvent e) {
    System.out.println("Event handling without lambda expression is boring");
    }
});

// Java 8方式:
show.addActionListener((e) -> {
    System.out.println("Light, Camera, Action !! Lambda expressions Rocks");
});

Lambda对集合进行处理

Lambda配合Stream流对集合进行处理恐怕是我们对Lambda使用最多的地方,由于篇幅比较大,所以单独开一张来讲,小伙伴继续阅读吧,你会发现集合原来可以这么玩,java对集合的处理 可以说是从 jdk1.4之前 -> 泛型引入 -> 流API引入 三个时代.

Stream(流)

  • Stream java.util.stream,流,可以更方便简洁的操作集合,常见函数(map,reduce,filter,foreach)
  • 参考文献:
    • https://www.ibm.com/developerworks/cn/java/j-lo-java8streamapi/
    • http://blog.csdn.net/u010425776/article/details/52344425
    • http://blog.csdn.net/happyheng/article/details/52832313

为什么需要流

  • Stream 作为 Java 8 的一大亮点,它与 java.io 包里的 InputStream 和 OutputStream 是完全不同的概念。它也不同于 StAX 对 XML 解析的 Stream,也不是 Amazon Kinesis 对大数据实时处理的 Stream。Java 8 中的 Stream 是对集合(Collection)对象功能的增强,它专注于对集合对象进行各种非常便利、高效的聚合操作(aggregate operation),或者大批量数据操作 (bulk data operation)。Stream API 借助于同样新出现的 Lambda 表达式,极大的提高编程效率和程序可读性。同时它提供串行和并行两种模式进行汇聚操作,并发模式能够充分利用多核处理器的优势,使用 fork/join 并行方式来拆分任务和加速处理过程。通常编写并行代码很难而且容易出错, 但使用 Stream API 无需编写一行多线程的代码,就可以很方便地写出高性能的并发程序。所以说,Java 8 中首次出现的 java.util.stream 是一个函数式语言+多核时代综合影响的产物。

什么是流

  • Stream 不是集合元素,它不是数据结构并不保存数据,它是有关算法和计算的,它更像一个高级版本的 Iterator。原始版本的 Iterator,用户只能显式地一个一个遍历元素并对其执行某些操作;高级版本的 Stream,用户只要给出需要对其包含的元素执行什么操作,比如 “过滤掉长度大于 10 的字符串”、“获取每个字符串的首字母”等,Stream 会隐式地在内部进行遍历,做出相应的数据转换。
  • Stream 就如同一个迭代器(Iterator),单向,不可往复,数据只能遍历一次,遍历过一次后即用尽了,就好比流水从面前流过,一去不复返。
  • 而和迭代器又不同的是,Stream 可以并行化操作,迭代器只能命令式地、串行化操作。顾名思义,当使用串行方式去遍历时,每个 item 读完后再读下一个 item。而使用并行去遍历时,数据会被分成多个段,其中每一个都在不同的线程中处理,然后将结果一起输出。Stream 的并行操作依赖于 Java7 中引入的 Fork/Join 框架(JSR166y)来拆分任务和加速处理过程。
  • 总结:流的几大特性
    • 流不是数据结构,而是针对数据结构的一种算法
    • 流是单向的,数据只能遍历一次
    • 流是并行化的,所以不会出险ArrayList等非现成安全的数据结构出现的并发修改异常的问题

流的构成

  • 如果我们需要使用一个流,需要经过三个步骤(获取一个数据源(例如数组,集合等) -> 数据转换 -> 执行操作获取想要的结果,每次转换原有Stream对象不改变,返回一个新的Stream对象,可以有多次转换 )

  • 流的生成方式:

    • 集合和数组
      • Collection.stream()
      • Collection.parallelStream()
      • Arrays.Stream(T array) or Stream.of()
    • BufferedReader
      • java.io.BufferedReader.lines()
    • 静态工厂
      • java.util.steam.IntStream.range()
      • java.nio.file.Files.walk()
    • 自己创建
      • java.util.Spliterator
    • 其他
      • Random.ints()
      • BitSet.stream()
      • Pattern.splitAsStream(java.lang.CharSequence)
      • JarFile.stream()
  • 示例

    • 集合-通过stream()方法获取流对象

      List<Person> list = new ArrayList<>();
      Stream<Person> stream = list.stream();
      
    • 数组-通过Arrays类提供的静态函数stream()获取数组的流对象

      String[] names = {"chaimm","peter","john"};
      Stream<String> stream = Arrays.stream(names);
      
    • 值-将几个值变成流对象

      Stream<String> stream = Stream.of("chaimm","peter","john");
      

流的操作类型

  • Intermediate(中间操作) : 一个流可以跟随多个intermediate操作,其主要目的是打开流,做出某种成都的数据映射/过滤,然后返回一个新的流,交给下一个操作使用.这个类操作都是惰性化(lazy),也就是说仅仅调用到这个类方法,并没有真正开始流的遍历(可使用的操作:map (mapToInt,flatMap 等),filter,distinct,sorted,peek,limit,skip,parallel,sequential,unordered)
  • Terminal(终端操作) : 一个流只能有一个terminal操作,当这个操作执行后,流就被使用"光",无法再被操作,所以这必定是流的最后一个操作.Terminal操作的执行,才会真正开始流的遍历,并且会生成一个结果.(forEach,forEachOrdered,toArray,reduce,collect,min,max,count,anyMatch,allMatch,noneMatch,findFirst,findAny,iterator)
  • short-circuiting(anyMatch,allMatch,noneMatch,findFirst,findAny,limit)
    • 对于一个 intermediate 操作,如果它接受的是一个无限大(infinite/unbounded)的 Stream,但返回一个有限的新 Stream.
    • 对于一个 terminal 操作,如果它接受的是一个无限大的 Stream,但能在有限的时间计算出结果.
    • 当操作一个无限大的 Stream,而又希望在有限时间内完成操作,则在管道内拥有一个 short-circuiting 操作是必要非充分条件
  • 流的使用步骤
    • 准备一个数据源
    • 执行中间操作,中间操作可以有多个
    • 执行终端操作,结束流,得到执行结果

流的类型

  • 需要注意的是,对于基本数值型,目前有三种对应的包装类型Stream:IntStream,LongStream,DoubleStream.当然我们也可以用 Stream,Stream >,Stream,但是 boxing 和 unboxing 会很耗时,所以特别为这三种基本数值型提供了对应的 Stream.

流的操作

流转换为其他数据结构
// 1. Array
String[] strArray1 = stream.toArray(String[]::new);
// 2. Collection
List<String> list1 = stream.collect(Collectors.toList());
List<String> list2 = stream.collect(Collectors.toCollection(ArrayList::new));
Set set1 = stream.collect(Collectors.toSet());
Stack stack1 = stream.collect(Collectors.toCollection(Stack::new));
// 3. String
String str = stream.collect(Collectors.joining()).toString();

map/flatMap(遍历操作)
  • 使用map操作可以遍历集合中的每个对象,并对其进行操作,map之后,用collect(Collectors.toList())会得到操作后的集合
转换大写
  • wordList集合中的所有单词都转换为大写,并保存在output集合中
List<String> output = wordList.stream().map(String::toUpeerCase).collect(Collectors.toList());
//注:双冒号是简化的lamdba表达式 双冒号左边表示类,右边表示类中的方法,也就是执行左边类中的某个方法,例如HashMap::new 实例化一个HashMap对象 等价于 () -> new HashMap<>();
平方数
List<Integer> nums = Arrays.asList(1,2,3,4);
List<Integer> squareNums = nums.stream().map.(n -> n * n).collect(Collectors.toList());
// 1,2,3,4 -> squareNums 集合 1,4,9,16
一对多
  • 上面都是一对一映射,每个输入的元素,都按照规则转换为另一个元素,还有一些场景是复杂层级结构的,一对多映射,这时候就需要使用flatMap
Stream<List<Integer>> inputStream = Stream.of(
Array.asList(1),
Array.asList(2,3),
Array.asList(4,5,6),
);
Stream<Integer> outputStream = inputStream.flatMap((childList) -> childList.stream());
//flatMap把inputStream中的层级结构扁平化,将所有的元素抽出放在一起,最终outputStream 流对象中已经没有List,都是一个个的元素
filter(过滤)
  • filter对原始Stream进行某项测试,通过测试的元素被留下来生成一个新的Stream,顾名思义-过滤;filter函数会接受一个Lamdba表达式作为参数,该变大时返回boolean类型,在执行过程中,Stream将元素逐一输送给filter,筛选出所有结果为true的元素
过滤奇数
Integer[] sixNums = {1,2,3,4,5,6};
Integer[] evens = Stream.of(sixNums).filter(n -> n%2 == 0).toArray(Integer[]::new);
得到不为空的字符串
List<String> filterLists = new ArrayList<>();
filter.add("");
filter.add("a");
filter.add("b");
List<String> afterFilterLists = filterLists.stream().filter(s -> !s.isEmpty()).collect(Collectors.toList());
distinct(去除重复)
List<Person> result =list.stream().distinct().collect(toList());
limit(截取)
  • 截取流的前N个元素,和mysql中Limit类似
List<Person> result = list.stream().limit(3).collect(toList());
skip(跳过)
  • limit是选取前n个元素,skip则是扔掉n个元素
List<String> forEachLists = new ArrayList<>();
forEachLists.add("a");
forEachLists.add("b");
forEachLists.add("c");
forEachLists.add("d");
forEachLists.add("e");
forEachLists.add("f");
List<String> limitLists = forEachLists.stream().skip(2).limit(3).collect(Collectors.toList());
//skip会扔掉a b 然后limit选取前三个 c d e

findFirst

forEach与peek
  • forEach方法接受一个Lambda表达式,然后在Stream的每一个元素上执行该表达式
//java8之前
for(Person p : roster){
  if(p.getGender() == Person.Sex.MALE){
    System.out.println(p.getName());
  }
}

//java 8
roster.stream().filter(p -> p.getGender() == Person.Sex.MALE).forEach(p -> System.out.println(p.getName()))
  • 对一个人员集合遍历,找出男性并打印姓名,可以看出来forEach是为Lambda而设计的,保持了最紧凑的风格而且lambda表达式本身是可以重用的,非常方便.当需要为多核系统优化时,可以parallelStream.forEach(),只是此时原有元素的次序没法保证,并行的情况下将改变串行时操作的行为,此时forEach本身的实现不需要调整,而Java8以前的for循环code可能需要就爱如额外的多线程逻辑
  • 但一般认为,forEach和常规for循环的诧异不涉及性能,他们仅仅是函数式风格与传统Java风格的差别.
  • 另外一点需要注意,forEach是terminal操作,因此它执行后,Stream的元素就被"消费"掉了,你无法对一个Stream进行两次terminal运算
  • peek对每个元素执行操作并返回一个新的Stream
Stream.of("one", "two", "three", "four")
 .filter(e -> e.length() > 3)
 .peek(e -> System.out.println("Filtered value: " + e))
 .map(String::toUpperCase)
 .peek(e -> System.out.println("Mapped value: " + e))
 .collect(Collectors.toList());
sort(排序)

对Stream的排序通过sorted进行,它比数组的排序更强指出在于你可以首先对Stream进行各类map,filter,limit,skip甚至distinct来减少元素数量后,再排序,这能帮助程序明显缩短执行时间.

List<Integer> sortLists = new ArrayList<>();
sortLists.add(1);
sortLists.add(4);
sortLists.add(6);
sortLists.add(3);
sortLists.add(2);
List<Integer> afterSortLists = sortLists.stream().sorted((ln1,ln2) -> ln1-ln2).collect(Collectors.toList());
List<Person> persons = new ArrayList();
 for (int i = 1; i <= 5; i++) {
 Person person = new Person(i, "name" + i);
 persons.add(person);
 }
List<Person> personList2 = persons.stream().limit(2).sorted((p1, p2) -> p1.getName().compareTo(p2.getName())).collect(Collectors.toList());
System.out.println(personList2);
max(获取最大)
  • min和max的功能也可以通过对Stream元素排序,再findFirst来实现,但前者的性能会更好.同时他们作为特殊的reduce方法被独立出来也是因为求最大最小值是很常见的操作
List<String> maxLists = new ArrayList<>();
maxLists.add("a");
maxLists.add("b");
maxLists.add("c");
maxLists.add("d");
maxLists.add("e");
maxLists.add("f");
maxLists.add("hahaha");
int maxLength = maxLists.stream().mapToInt(s->s.length()).max().getAsInt();
System.out.println("字符串长度最长的长度为"+maxLength);
  • 通过流遍历,获取一个int类型的流,里面装的是字符的长度,然后从这个流中取出最大值,再获取值
BufferedReader br = new BufferedReader(new FileReader("c:\\123.txt"));
int longest = br.lines().mapToInt(String :: length).max().getAsInt();
br.close();
System.out.printLn(Longset);
  • 找出文件中最长的一行,并输出长度
reduce

这个方法的主要作用是把Stream元素组合起来,它提供了一个起始值,然后哪找运算规则,和前面的Stream的第一个,第二个,第三个元素组合,从这个意义上说,字符串拼接,数值的sum,min,max,average都是特殊的reduce

//例如下面的reduce就相当于sum
Integer sum = integers.reduce(0,(a,b) -> a+b);
Integer sum = integers.reduce(0, Integer :: sum)
//也有没有起始值的情况,这时会把Stream的前面两个元素组合起来,返回的是Optional
  • reduce的用例
//字符串连接,concat = "ABCD"
String concat = Stream.of("A","B","C","D").reduce("",String::concat);
//求最小值,minValue = -3.0
double minValue = Stream.of(-1.5,1.0,-3.0,-2.0).reduce(Double.MAX_VALUE,Double::min);
//求和,sumValue = 10,无起始值
sumValue = stream.of(1,2,3,4).reduce(Integer::sum).get();
//过滤,字符串连接,concat = "ace"
concat = Stream.of("a","b","c","D","e","F").filter(x -> x.compareTo("Z") > 0).reduce("",String :: concat)

  • 上面代码例如第一个示例reduce(),第一个参数(空白字符串)即为初始值,第二个参数(String::concat)为BinaryOperator(运算规则).这类有起始值的reduce()都返回具体的对象.而对于第四个示例没有起始值的reduce(),由于可能没有足够的元素,返回的是Optional,注意这个区别
Match
  • Stream有三个match方法:
    • allMatch : Stream中全部元素符合传入的条件,返回true
    • anyMatch : Stream中只要有一个元素符合传入的条件,返回true
    • noneMatch : Stream中没有一个元素符合传入的条件,返回true
  • 他们都不是要遍历所有元素才能返回结果,例如allMatch只要一个元素不满足条件,剩下的都会skip,返回值为false
List<Person> person = new ArrayList();
person.add(new Person(1,"name" + 1,10));
person.add(new Person(2,"name" + 2,21));
person.add(new Person(3,"name" + 3,34));
person.add(new Person(4,"name" + 4,6));
person.add(new Person(5,"name" + 5,55));
boolean isAllAdult = person.stream().allMatch(p -> p.getAge() >18);
System.out.println("All are adult? " + isAllAdult);
boolean isAllAdult = person.stream().anyMatch(p -> p.getAge() <12);
System.out.println("All child? " + isAllAdult);
//输出结果
//All are adult? false
//Any child? true
  • 判断集合中没有有为‘c’的元素
List<String> matchList = new ArrayList<>();
matchList.add("a");
matchList.add("a");
matchList.add("c");
matchList.add("d");
boolean isExits = matchList.stream().anyMatch(s -> s.equals("c"));
  • 判断集合中是否全不为空
List<String> matchList = new ArrayList<>();
matchList.add("a");
matchList.add("");
matchList.add("a");
matchList.add("c");
matchList.add("d");
boolean isNotEmpty = matchList.stream().noneMatch(s -> s.isEmpty());

//返回false
进阶
生成 10 个随机整数
Random seed = new Random();
Supplier<Integer> random = seed::nextInt;
Stream.generate(random).limit(10).forEach(System.out::println);
//Another way
IntStream.generate(() -> (int) (System.nanoTime() % 100)).
limit(10).forEach(System.out::println);
生成一个等差数列
Stream.iterate(0, n -> n + 3).limit(10). forEach(x -> System.out.print(x + " "));

Optional

  • Optional是java8新加入的一个容器,这个容器只存一个或0个元素,它用于防止空指针异常(NullpointException)的出现.
  • API
    • isPresent():判断容器中是否有值
    • ifPresent(Consume lambda) : 容器如果不为空则执行括号中的Lambda表达式
    • T get() : 获取容器中的元素,如果容器为空则抛出NoSuchElement异常
    • T orElse(T other) : 获取容器中的元素,如果容器为空则返回括号中的默认值
findFirst
  • 获取流中第一个元素findFirst
Optional<Person> person = list.stream.findFirst();
数值计算
  • 每种数值流都提供了数值计算函数,如max、min、sum等
//找出最大的年龄
OptionalInt maxAge = list.stream().mapToInt(Person::getAge).max();
  • 由于数值流可能为空,并且给空的数值流计算最大值是没有意义的,因此max函数返回OptionalInt,它是Optional的一个子类,能够判断流是否为空,并对流为空的情况作相应的处理。
    此外,mapToInt、mapToDouble、mapToLong进行数值操作后的返回结果分别为:OptionalInt、OptionalDouble、OptionalLong
  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值