博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Java8新特性:函数式编程
阅读量:6155 次
发布时间:2019-06-21

本文共 6020 字,大约阅读时间需要 20 分钟。

hot3.png

Lambda表达式

Lambda表达式形式

Java 中表达式一共有五中基本形式 , 具体如下:

public class LambdaExpression {        public static void express() {        // 1----------------------------------------------        Runnable runnable = () -> System.out.println("Hello World");        // 2----------------------------------------------        ActionListener oneArgument = event -> System.out.println("button clicked");        // 3----------------------------------------------        Runnable multiStatement = () -> {            System.out.print("Hello");            System.out.println(" World");        };        // 4----------------------------------------------        BinaryOperator
add = (x , y) -> x + y; new BinaryOperator
() { @Override public Long apply(Long aLong, Long aLong2) { return null; } }; // 5---------------------------------------------- BinaryOperator
addOper = (Long x , Long y) -> x + y; }}
  1. 如1所示Lambda表达式中不含参数,使用空括号()标识没有参数。该Lambda 表达式实现了Runnable接口,该接口也只有一个run方法 , 没有参数,且返回类型为void类型。
  2. 如2所示的lambda表达式包含且只有一个参数,可以省略参数的括号,lambda表达式主题不仅可以是一个表达式,也可以是一段代码块,使用({})大括号将代码块括起来如3所示
  3. 如4所示,这是就有必要思考怎样去阅读该Lambda表达式。这行代码并不是将两数字相加,而是创建了一个函数,用来计算两个数字相加的结果,变量add的类型是BinaryOperator类型。

记住一点很重要,Lambda表达式都可以扩写为原始的匿名类形式,所以当你觉得这个Lambda表达式很负责且不易理解的时候,不妨把它扩写为 匿名类 形式来看

闭包

也许之前遇到过这样的问题,当匿名内部类需要所在方法里的变量的时候,必须把变量声明为 final , 如下所示:

final int a = 1;        new Runnable(){            @Override            public void run() {                System.out.println(a);            }        };

java8 放松了这一限制,可以不必再把变量声明为 final,但其实该变量实际上仍然为final的。索然无需将变量声明为final , 但在lambda表达式中,也无法用作非终态变量, 如果坚持用作非终态变量(即改变变量的值),编译器就会报错。

函数式接口

上面例子中提到了 ActionListener 接口 , 我们看下它的代码:

public interface ActionListener extends EventListener {    /**     * Invoked when an action occurs.     */    public void actionPerformed(ActionEvent e);}

ActionListener 只有一个抽象方法:actionPerformed,被用来表示行为:接受一个参数,返回空。记住,由于 actionPerformed 定义在一个接口里,因此 abstract 关键字不是必需的。该接口也继承自一个不具有任何方法的父接口:EventListener。

我们把这种接口就叫做函数接口。 JDK 8 中提供了一组常用的核心函数接口:

接口 参数 返回类型 描述
Predicate<T> T boolean 用于判别一个对象。比如求一个人是否为男性
Consumer<T> T void 用于接收一个对象进行处理但没有返回,比如接收一个人并打印他的名字
Function<T, R> T R 转换一个对象为不同类型的对象
Supplier<T> None T 提供一个对象
UnaryOperator<T> T T 接收对象并返回同类型的对象
BinaryOperator<T> (T, T) T 接收两个同类型的对象,并返回一个原类型对象

其中 ConsumerSupplier 对应,是一个消费者与一个提供者。 Predicate 用于判断对象是否符合某个条件,经常被用来过滤对象。 Function是将一个对象转换为另一个对象,比如说要装箱或拆箱某个对象。 UnaryOperator 接收和返回同类型对象,一般用于对对象属性进行修改。 BinaryOperator 可以理解为合并对象

集合处理

Stream

Java8 中引入流的概念, 这里的流和我们之前使用的IO流并不一样。 所有继承自Collection的接口都可以转换为Stream 。 假设我们有一个List中存放 Person对象,对象中有 名称 name , 年龄 age 两个字段, 现在求列表中年龄大于20的人数。 不用Stream 我们会这么写:

int count = 0;		for (Person person : persons) {			if(person.getAge() > 20) {				count ++;			}		}

如果用stream的话,则会简单很多:

long count2 = persons.stream()				.filter(person -> person.getAge() > 20)				.count();

实际filter中参数转换为了如下函数进行的调用:

Predicate predicate = new Predicate
() { @Override public boolean test(Person o) { return o.getAge() > 20 ; } }; long count1 = persons.stream() .filter(predicate) .count();

这是stream的很简单的一个用法,链式调用方法算是一个主流, 这样也更利于理解。

Stream的常用操作

Stream的方法分为两类, 一类叫惰性求值,一类叫及早求值。 判断一个操作是惰性求值还是及早求值很简单:只需看它的返回值 , 如果返回值是Stream 就是惰性求值。 如果调用惰性求值方法,Stream会记录下这个惰性求值方法的过程 , 并没有去计算, 等到及早求值方法后,就会联通一系列惰性求值方法顺序进行计算 , 返回结果。 通用形式为: Stream.惰性求值.惰性求值....惰性求值.及早求值。 整个过程和建造者模式有共通之处, 建造者模式使用一系列的操作配置属性和配置, 最后调用build方法创建对象。

collect(Collectors.toList())

collect(Collectors.toList()) 方法由 Stream 里的值生成一个列表,是一个及早求值操作。可以理解为 Stream 向 Collection 的转换。 注意这边的Collectors.toList() 其实采用了静态导入,看起来十分简洁。

List
collect = Stream.of("a", "b", "c").collect(Collectors.toList());

map

如果一个函数可以将一种类型的值转换成另外一种类型, map操作就可以使用该函数, 将一个流中的值转换为一个新的流

List
upperCollect = Stream.of("a", "b", "hello").map(item -> item.toUpperCase()).collect(Collectors.toList());

map方法就是接受一个 Function 的匿名函数进行转换:

Function
function = new Function
() { @Override public String apply(String o) { return o.toUpperCase(); } }; List
funcCollect = Stream.of("a", "b", "hello").map(function).collect(Collectors.toList());

filter

遍历集合数据并过滤其中的元素 , 可尝试使用Stream 中提供的新方法 filter

long count2 = persons.stream()				.filter(person -> person.getAge() > 20)				.count();

filter方法就是接收一个Predicate 的匿名函数类,判断对象是否符合条件 , 符合条件才保留下来。

Predicate predicate = new Predicate
() { @Override public boolean test(Person o) { return o.getAge() > 20 ; } }; long count1 = persons.stream() .filter(predicate) .count();

flatMap

flatMap可用Stream替换 , 然后将多个Stream链接成一个Stream。

List
collect = Stream.of(Arrays.asList(1, 2), Arrays.asList(3, 4)) .flatMap(item -> item.stream()).collect(Collectors.toList());

flatMap常用的操作就是合并多个 Collection。

max 和 min

Stream 上常用的操作之一是求最大值和最小值, Stream API中的max 和 min操作足以解决这一问题。

List
list = Arrays.asList(2,4,6,3,9,4); Integer maxInt = list.stream().max(Integer::compareTo).get(); Integer minInt = list.stream().min(Integer::compareTo).get(); System.out.println(maxInt); System.out.println(minInt);

这里有两点需要注意: 1.max 和 min 方法返回的是一个 Optional 对象 , (类似Google Guava里的 Optional对象一样) 。 Optional 对象封装的就是实际的值,可能为空 , 所以保险起见 , 可以先用 isPresent() 方法判断一下, Optional的引入就是为了解决方法返回 null 的问题。 2.Integer::compareTo 也是属于 Java8 引入的新特性 , 叫作 方法引入 (Method References) ,在这边就是 (int1,int2) -> int1.compareTo(int2) 的简写 。可以自己查阅,不做赘述。

reduce

reduce 操作可以实现从一组值中生成一个值。 在上述例子中用到的 count , min 和 max 方法 , 因为常用而被纳入标准库中 。 事实上 这些方法都是reduce 操作。 如下是一个累加的过程:

long l = Stream.of(1, 2, 3, 4 ).reduce(0, (acc, element) -> acc + element).longValue();

注意reduce的第一个参数 , 这是一个初始值 , 0 + 1 + 2+ 3 + 4 = 10; 实际第二个参数是BinaryOperator<T>的匿名内部类 如果累乘为如下形式

BinaryOperator
operator = new BinaryOperator
() { @Override public Integer apply(Integer integer, Integer integer2) { return integer * integer2; } }; long l1 = Stream.of(1, 2, 3, 4).reduce(1, operator).longValue();

结果为1 * 1 * 2 * 3 * 4 = 24;

转载于:https://my.oschina.net/LucasZhu/blog/1922919

你可能感兴趣的文章
SparseArray
查看>>
第二章
查看>>
android背景选择器selector用法汇总
查看>>
[转]Paul Adams:为社交设计
查看>>
showdialog弹出窗口刷新问题
查看>>
java
查看>>
Vue.js连接后台数据jsp页面  ̄▽ ̄
查看>>
关于程序的单元测试
查看>>
mysql内存优化
查看>>
都市求生日记第一篇
查看>>
Java集合---HashMap源码剖析
查看>>
SQL优化技巧
查看>>
thead 固定,tbody 超出滚动(附带改变滚动条样式)
查看>>
Dijkstra算法
查看>>
css 动画 和 响应式布局和兼容性
查看>>
csrf 跨站请求伪造相关以及django的中间件
查看>>
MySQL数据类型--与MySQL零距离接触2-11MySQL自动编号
查看>>
生日小助手源码运行的步骤
查看>>
Configuration python CGI in XAMPP in win-7
查看>>
bzoj 5006(洛谷 4547) [THUWC2017]Bipartite 随机二分图——期望DP
查看>>