《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"); } });
这实际上是一个代码即数据的例子--我们给按钮传递了一个代表某种行为的对象
设计匿名内部类的目的,就是为了方便将代码作为数据传递。
匿名内部类的问题:
- 不够简单,上述就有4行样板代码。(打印以外的4行)
- 难读,不想传入对象,只想传入代码。
在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
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表达式的类型。