1、介绍
本片文章会从一下几个知识点进行介绍:
- 函数式接口 @FunctionalInterface
- Lambda 表达式
- 函数引用 Function Reference
- Stream
看了几篇关于 java8 入门的例子,其中引入了许多令人期待已久的特性(虽然我没有过这样的体会),但不管你的代码是普通青年款还是文艺青年款,你都可以从被 java8 的重新组织的代码看到她的简洁之处,不得不让我对这些新入贵圈(java圈)的小鲜肉做一些记录,以便我能很好的利用他们。
最大的一个特点莫非就是引入了“函数式”编程这一个概念(都是剽窃的别的语言来的),还有那么多新奇的词语,比如“语法糖”“闭包”“显式|隐式函数”等这里我就不再一个一个去体会了,这里属于初次接触,先理解会用为主,细节的深入待后期慢慢琢磨。
2、函数式接口 -- @Functionnalinterface
在翠花正式上代码示例之前,我们现来对这个函数式接口有个认识:
- 接口必须只有一个抽象方法;
- 接口可以有其他的默认(default)或者静态(static)方法;
- 接口默认继承了 Object 类,所以接口中不能覆盖了 Object 中的方法;
说明:该注解不是必须的,如果一个接口满足“函数式接口”特性,那么不加这个注解也没有影响,加上该注解能够更好的让编译器检查,如果不符合规范将会报编译错误。
比如在 jdk8 中就对Comparator、Callable、Runnable等接口加上了该注解,下面两段代码的效果是一样的:
1 public static void runThreadByLambda() { 2 Runnable runnable = () -> System.out.println("这个是用拉姆达实现的线程"); 3 new Thread(runnable).start(); 4 } 5 6 //--------------------------------------------------------------------------------- 7 public static void runThreadByInnerClass() { 8 Runnable runnable = new Runnable() { 9 @Override 10 public void run() { 11 System.out.println("这个是用内部类实现的线程"); 12 } 13 }; 14 new Thread(runnable).start(); 15 }
3、Lambda 表达式
先用自己的语言来个 Lambda 下个定义:"一段带有输入参数的可执行语句块”。精确与否那是别人的事情,反正我是这么理解的(目前阶段是这样的)。
1 (Type1 param1, Type2 param2, ..., TypeN paramN) -> { 2 statment1; 3 statment2; 4 //............. 5 return statmentM; 6 }
对应定义我们自然要给个例子
1 List<String> names = new ArrayList<>(); 2 names.add("TaoBao"); 3 names.add("ZhiFuBao"); 4 List<String> lowercaseNames = names.stream().map((String name) -> {return name.toLowerCase();}).collect(Collectors.toList());
这不就是将一个集合全部元素变为小写吗?和我以前用的循环代码繁简度不想上下,有什么好奇怪的!这位看管不要着急嘛,下面我来个他搓个背您再看看如何。
1 List<String> lowercaseNames = names.stream().map(name -> name.toLowerCase()).collect(Collectors.toList()); 2 3 //如果加入方法应用就是这样 4 List<String> lowercaseNames = names.stream().map(String::toLowerCase()).collect(Collectors.toList());
简化规则如下:
- 绝大部分情况下,编译器都可以从上下文推算出参数类型,因此可以省略
- 当参数只有一个,可以省略小括号
- 当表达式只包含一条语句时,可以省略大括号、returned和语句结尾的分号
在 Lambda 眼中,外部的变量也是可以访问的,只是这个变量默认必须是 final ,即便你自己没有加上,编译器也会自动帮你加上,所以变大时内部访问外部变量必须是不可变的(只是引用不可变);还有一个东西就是 this ,this 在 lambda 指代的不是表达式生产的那个 SAM(Simple Abstract Method)对象,而是申明她的外部对象。
4、方法引用(Method reference)和构造器引用(Construct reference)
- objectName::instanceMethod
- ClassName::staticMethod
例子:System.out::println 等同于 x -> System.out.println(x)
Math::max 等同于 (x,y) -> Math.max(x,y)
前两种方法类似,等同于把 Lambda 表达式的参数当成实例方法或者静态方法的参数来调用,下面再来看看第三种情况 - ClassName::instanceMethod
例子:String.toLowerCase 等同于 x -> x.toLowerCase()
等同于把 Lambda 表达式的第一个参数当成 实例方法的目标对象,其余参数当成该方法的参数来调用。 - ClassName::new
例子:BigDecimal::new 等同于 x -> new BigDecimal(x)
等同于把 Lambda 的参数当成构造器的参数来调用。
5、Stream
Stream 堪称 Lambda 的好基友,他们两个配合起来使用才能快快乐乐。
-
Stream 是元素的集合,这点让 Stream 看起来有些类似 iterator
- 可以支持顺序和并行的对原 Stream 进行汇聚的操作
//Lists是Guava中的一个工具类 List<Integer> nums = Lists.newArrayList(1,null,3,4,null,6);
5.1 创建 Stream
// of() 有两个,变长参数和单一参数 Stream<Integer> integerStream = Stream.of(1, 2, 3, 5); Stream<String> stringStream = Stream.of("taobao"); // 匿名类的写法 Stream.generate(new Supplier<Double>() {@Overridepublic Double get() {return Math.random();} }); // Lambda 写法 Stream.generate(() -> Math.random); // 函数引用写法 Stream.generate(Math::random); // generate() 生产一个无限长度的Stream // iterate() 也是生产了一个无限长度的 Stream ,其是重复调用用户给定的种子值类获取元素,seed f(seed) f(f(seed)) ...... Stream.iterate(1, item -> item + 1).limit(10).forEach(System.out::println);
除了上面 Stream 的静态工厂创建方法之外,就是下面的 Collection 接口的默认方法。
1 public interface Collection<E> extends Iterable<E> { 2 //其他方法省略 3 default Stream<E> stream() { 4 return StreamSupport.stream(spliterator(), false); 5 } 6 }
5.2 转换 Stream
顾名思义,就是通过一定的法则将原来的 Stream 转换成一个新的 Stream,下面列举一些常用的转换方法来加深理解,一图胜千言
从以上图中就可以看出来几个常用的 转换函数的功能,下面我们将大声的喊出我们的口号“在一起!”
1 List<Integer> nums = Lists.newArrayList(1,1,null,2,3,4,null,5,6,7,8,9,10); 2 System.out.println(“sum is:”+nums.stream().filter(num -> num != null). 3 distinct().mapToInt(num -> num * 2). 4 peek(System.out::println).skip(2).limit(4).sum());
5.3 汇聚(Reduce) Stream
汇聚,也称为折叠,接受一个元素序列为输入,反复使用某个合并操作,把序列中的元素合并成一个汇总的结果,下面会分两部分来介绍汇聚行为。
5.3.1 可变汇聚
把输入元素累计到一个可变的容器中,比如 Collection 或者 StringBuilder。
5.3.2 其他汇聚
除了可变汇聚之外的其余汇聚,一般不是通过反复修改某个可变对象,而是通过把前一次的汇聚结果当成下一次的入参,反复如此,比如reduce、count、allMatch