《Java8函数式编程》学习笔记 - 第一章,第二章

第一章 简介

1.1 为什么需要再次修改Java

开发类库的程序员使用Java时,发现抽象级别还不够,
处理大型数据集合,Java还欠缺高效的并行操作。
使用Java8可以编写复杂的集合处理算法, 只需要简单修改一个方法,就能让代码在多核CPU上高效运行。

为了编写这类处理批量数据的并行类库,
需要在语言层面上修改现有的Java:增加Lambda表达式。

  • 面向对象编程是对数据进行抽象,
  • 函数式编程是对行为进行抽象。

函数式编程并不是性能优先的代码,但带来的好处是比较明显。
代码更容易阅读,更多地表达了业务逻辑的意图,而不是实现机制。
特别是在写回调函数事件处理程序时,不必再纠缠于内部类的冗繁和可读性

1.2 什么是函数式编程

使用不可变值函数函数对一个值进行处理,映射成另一个值。

1.3 示例

创建了,Artist类,Track类,Album类

第二章 Lambda表达式

Java8的最大变化就是引入了Lambda表达式,一种紧凑的,传递行为的方式。

2.1 第一个Lambda表达式

//例2-1 使用匿名内部类将Action和按钮点击进行关联  
button.addActionListener(new ActionListener() {
    public void actionPerformed(ActionEvent event) {
        System.out.println("button clicked");
    }
});

这实际上是一个代码即数据的例子--我们给按钮传递了一个代表某种行为的对象
设计匿名内部类的目的,就是为了方便将代码作为数据传递。

匿名内部类的问题:

  1. 不够简单,上述就有4行样板代码。(打印以外的4行)
  2. 难读,不想传入对象,只想传入代码。

在java8中可以改写成下面的lambda表达式

//例2-2 使用Lambda表达式将行为和按钮单击进行关联
button.addActionListener(event->
        System.out.println("button clicked"););

和传入一个实现某接口的对象不同,我们传入了一段代码块,一个没有名字的函数

  • event是参数名,和上面匿名内部类是同一个参数
  • “->”,将参数和lambda表达式的主体分开,
  • 主体,是用户点击按钮时会运行的一些代码。

和使用匿名内部类的另一处不同在于声明event 参数的方式。
内部类需要显式声明,而lambda表达式通过类型推断,无需显式声明。

尽管与之前相比,Lambda表达式中的参数需要的样板代很少,
但是Java8仍然是一种静态类型语言

2.2如何辨别Lambda表达式

Runnable noArguments = () -> System.out.println("Hello World");

该Lambda表达式实现了Runnable接口。 Runnable接口也只有一个run方法,没有参数,且返回类型为void。

ActionListener oneArgument = event -> System.out.println("button clicked");

该Lambda表达式实现了ActionListener接口 ActionListener接口只有一个actionPerformed方法,而actionPerformed方法只有ActionEvent一个参数。 因此这里只需要"event"一个参数,并且通过系统的类型推断,无需显式声明。

Runnable multiStatement = () -> {
    System.out.print("Hello");
    System.out.println(" World");
};

该Lambda表达式同样实现了Runnable接口。
Lambda表达式的主体不仅可以是一个表达式,也可以是一个闭包。

BinaryOperator<Long> add = (x, y) -> x + y;

这行代码并不是将两个数字相加,而是创建了一个函数,用来计算两个数字相加的结果。

目标类型是指Lambda 表达式所在上下文环境的类型。
比如,将Lambda 表达式赋值给一个局部变量,或传递给一个方法作为参数,
局部变量或方法参数的类型就是Lambda 表达式的目标类型。

2.3 引用值,而不是变量

//例2-5 匿名内部类中使用final 局部变量
final String name = getUserName(); 
button.addActionListener(new ActionListener() {
    public void actionPerformed(ActionEvent event) {
        System.out.println("hi " + name);
    }
});

如上例所示,匿名内部类中用到变量name时,需要将name声明为final。
Lambda表达式虽然可以引用非final变量,但该变量在既成事实上必须是final。
既成事实上的final是指只能给该变量赋值一次。
换句话说,Lambda表达式引用的是值,而不是变量

例如

String name = getUserName();
button.addActionListener(event -> System.out.println("hi " + name));

给变量多次赋值,然后在Lambda表达式中引用它,编译器就会报错。

String name = getUserName();
name = formatUserName(name);
button.addActionListener(event -> System.out.println("hi " + name));

显示出错信息:local variables referenced from a Lambda expression must be final or effectively final
总结:Lambda表达式都是静态类型

接口单一方法的命名并不重要,只要方法签名和Lambda表达式的类型匹配即可。

2.4 函数接口

函数接口是只有一个抽象方法的接口,用作Lambda表达式的类型

public interface ActionListener extends EventListner {
    public void actionPerformed(ActionEvent event);
}

Java1.8提供了不少重要的函数接口 Consumer,Supplier,Function<T,R>,Predicate

2.5 类型推断

Java 7 中的菱形操作符

Map<String, Integer> oldWordCounts = new HashMap<String, Integer>();
Map<String, Integer> diamondWordCounts = new HashMap<>();

Java 7中我们可以省略构造函数的泛型类型。
Java 8中更进一步,可以省略Lambda表达式中的所有参数类型。

Java 8 中对类型推断系统的改善值得一提。上面的例子将new HashMap<>()传给useHashmap方法, 即使编译器拥有足够的信息,也无法在Java 7 中通过编译。

正:BinaryOperator<Long> addLongs = (x, y) -> x + y;
误:BinaryOperator add = (x, y) -> x + y; 编译器给出的报错信息如下:
Operator '& #x002B;' cannot be applied to java.lang.Object, java.lang.Object.
其实就是浏览器认不出x,y的类型,x,y被默认为Object,而Object对象是没有+操作符的,因此报错了。

要点

  • Lambda 表达式是一个匿名方法,将行为像数据一样进行传递。
  • Lambda表达式的常见结构:BinaryOperator add = (x, y) → x + y。
  • 函数接口指仅具有单个抽象方法的接口,用来表示 Lambda表达式的类型。