Java SE 8 for the Really Impatient读书笔记——Java 8 Lambda表达式
1. lambda表达式的语法
lambda表达式是一种没有名字的函数,它拥有函数体和参数。
lambda表达式的语法十分简单:参数->主体。通过->来分离参数和主体。
1.1 参数
lambda表达式可以有零个参数,一个参数或多个参数,参数可以指定类型,在编译器可以推导出参数类型的情况下,也可以省略参数类型。
两个参数的例子:
(String first, String second)-> Integer.compare(first.length(), second.length())
0个参数的例子:
() -> { for (int i = 0; i < 1000; i++) doWork(); }
关于省略参数类型,可以参考泛型省略类型来理解。从jdk7开始,泛型可以简化写成如下形式:
Map<String, String> myMap = new HashMap<>();
编译器会根据变量声明时的泛型类型自动推断出实例化HashMap时的泛型类型。
同样的,如果编译器可以推导出Lambda表达式中参数的类型,也可以将其省略,例如:
Comparator<String> comp = (first, second) -> Integer.compare(first.length(), second.length());
上例lambda创建了一个函数式接口Comparator的对象(后文将介绍函数式接口),编译器根据声明,可以推断出first和second的类型为String。此时,参数类型可省略。在只有一个参数,且可推断出其类型的情况下,可以再将括号省略:
EventHandler<ActionEvent> listener = event ->System.out.println("Thanks for clicking!");
同方法参数一样,表达式参数也可以添加annotations或者final修饰:
(final String name) -> ...
(@NonNull String name) ->
1.2 主体
lambda表达式的主体一定要有返回值。
如果主体只有一句,则可以省略大括号:
Comparator<String> comp = (first, second) -> Integer.compare(first.length(), second.length());
多于一句的情况,需要用{}括上:
(String first, String second) -> {
if (first.length() < second.length()) return -1;
else if (first.length() > second.length()) return 1;
else return 0;
}
主体必须有返回值,只在某些分支上有返回值也是不合法的,例如:
(int x) -> { if (x >= 0) return 1; }
这个例子是不合法的。
2. 函数式接口
只包含一个抽象方法的接口叫做函数式接口。
函数式接口可使用注解@FunctionalInterface标注(不强制,但是如果标注了,编译器就会检查它是否只包含一个抽象方法)
可以通过lambda表达式创建函数式接口的对象,这是lambda表达式在java中做的最重要的事情
在jdk8以前,其实已经存在着一些接口,符合上述函数式接口的定义。
2.1 JDK 8之前已有的函数式接口
java.lang.Runnable
java.util.concurrent.Callable
java.security.PrivilegedAction
java.util.Comparator
java.io.FileFilter
java.nio.file.PathMatcher
java.lang.reflect.InvocationHandler
java.beans.PropertyChangeListener
java.awt.event.ActionListener
javax.swing.event.ChangeListener
在jdk8以前,这些接口的使用方式与其他接口并无不同。
通过两个例子来说明lambda表达式如何创建函数式接口实例
1.创建Runnable函数式接口实例,以启动线程——jdk8以前:
import java.util.*; public class OldStyle {
public static void main(String[] args) {
// 启动一个线程
Worker w = new Worker();
new Thread(w).start();
// 启动一个线程
new Thread(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
}).start(); }
} class Worker implements Runnable {
public void run() {
System.out.println(Thread.currentThread().getName());
}
}
运行结果:
Thread-0
Thread-1
从代码角度来看,不管是通过内部类还是通过匿名内部类,启动线程需要编写的代码都较为繁琐,其中,由程序员自定义的仅仅是run方法中的这一句话:
System.out.println(Thread.currentThread().getName());
lambda表达式风格的启动线程:
// 启动一个线程
Runnable runner = () -> System.out.println(Thread.currentThread().getName());
runner.run();
第一行实际上创建了一个函数式接口Runnable的实例runner,可以看出,lambda表达式的实体,恰好是run方法的方法体部分。
2.创建Comparator函数式接口实例,实现根据String的长度来排序一个String数组——jdk8以前:
import java.util.*; public class OldStyle {
public static void main(String[] args) {
// 排序一个数组
class LengthComparator implements Comparator<String> {
public int compare(String first, String second) {
return Integer.compare(first.length(), second.length());
}
}
String[] strings = "Mary had a little lamb".split(" ");
Arrays.sort(strings, new LengthComparator());
System.out.println(Arrays.toString(strings));
}
}
lambda表达式:
import java.util.*; public class LambdaStyle {
public static void main(String[] args) {
// 排序一个数组
String[] strings = "Mary had a little lamb".split(" ");
Arrays.sort(strings, (first, second) -> Integer.compare(first.length(), second.length()));
System.out.println(Arrays.toString(strings));
}
}
可以看出,函数式接口通过lambda表达式创建实例,是如此的精简
jdk8的java.util.function包下,又
定义了一些函数式接口以及针对基本数据类型的子接口。
Predicate -- 传入一个参数,返回一个bool结果, 方法为boolean test(T t)
Consumer -- 传入一个参数,无返回值,纯消费。 方法为void accept(T t)
Function<t,r> -- 传入一个参数,返回一个结果,方法为R apply(T t)
Supplier -- 无参数传入,返回一个结果,方法为T get()
UnaryOperator -- 一元操作符, 继承Function<t,t>,传入参数的类型和返回类型相同。
BinaryOperator -- 二元操作符, 传入的两个参数的类型和返回类型相同, 继承BiFunction
3. 方法引用
方法引用增强了lambda表达式的可读性
方法表达式的三种主要情况:
- 类::静态方法
- 对象::实例方法
- 类::实例方法
方法引用将会执行该类(对象)的指定静态(实例)方法。
方法引用例1:根据字母顺序(不区分大小写)排序一个字符串数组:
import java.util.*; public class LambdaStyle {
public static void main(String[] args) {
// 排序一个数组
String[] strings = "Mary had a little lamb".split(" ");
Arrays.sort(strings, (s1, s2) -> {
int n1 = s1.length();
int n2 = s2.length();
int min = Math.min(n1, n2);
for (int i = 0; i < min; i++) {
char c1 = s1.charAt(i);
char c2 = s2.charAt(i);
if (c1 != c2) {
c1 = Character.toUpperCase(c1);
c2 = Character.toUpperCase(c2);
if (c1 != c2) {
c1 = Character.toLowerCase(c1);
c2 = Character.toLowerCase(c2);
if (c1 != c2) {
// No overflow because of numeric promotion
return c1 - c2;
}
}
}
}
return n1 - n2;
}); System.out.println(Arrays.toString(strings));
}
}
上述例子,由于lambda表达式的主体代码较长,导致代码可读性下降,通过方法引用可以解决这个问题
方法引用例2:类::静态方法
import java.util.*; public class LambdaStyle {
public static void main(String[] args) {
// 排序一个数组
String[] strings = "Mary had a little lamb".split(" ");
Arrays.sort(strings, LambdaStyle::myCompareToIgnoreCase);
System.out.println(Arrays.toString(strings));
} public static int myCompareToIgnoreCase(String s1, String s2){
int n1 = s1.length();
int n2 = s2.length();
int min = Math.min(n1, n2);
for (int i = 0; i < min; i++) {
char c1 = s1.charAt(i);
char c2 = s2.charAt(i);
if (c1 != c2) {
c1 = Character.toUpperCase(c1);
c2 = Character.toUpperCase(c2);
if (c1 != c2) {
c1 = Character.toLowerCase(c1);
c2 = Character.toLowerCase(c2);
if (c1 != c2) {
// No overflow because of numeric promotion
return c1 - c2;
}
}
}
}
return n1 - n2;
}
}
将主体代码抽出来写到一个方法中,然后引用这个方法。
方法引用例3:对象::实例方法
import java.util.*; public class LambdaStyle {
public static void main(String[] args) {
// 排序一个数组
String[] strings = "Mary had a little lamb".split(" ");
LambdaStyle lambdaStyle = new LambdaStyle();
Arrays.sort(strings, lambdaStyle::myCompareToIgnoreCase);
System.out.println(Arrays.toString(strings));
} public int myCompareToIgnoreCase(String s1, String s2){
int n1 = s1.length();
int n2 = s2.length();
int min = Math.min(n1, n2);
for (int i = 0; i < min; i++) {
char c1 = s1.charAt(i);
char c2 = s2.charAt(i);
if (c1 != c2) {
c1 = Character.toUpperCase(c1);
c2 = Character.toUpperCase(c2);
if (c1 != c2) {
c1 = Character.toLowerCase(c1);
c2 = Character.toLowerCase(c2);
if (c1 != c2) {
// No overflow because of numeric promotion
return c1 - c2;
}
}
}
}
return n1 - n2;
}
}
对类::实例方法这种情况的方法引用来说,第一个参数会成为执行方法的对象。
通过一个例子来说明。在String类中实际上已经提供了不区分大小写比较字符串的方法:
public int compareToIgnoreCase(String str)
这个方法的用法为:
String s = "jdfjsjfjskd";
String ss = "dskfksdkf";
int i = s.compareToIgnoreCase(ss);
System.out.println(i);
方法引用例4:类::实例
import java.util.*; public class LambdaStyle {
public static void main(String[] args) {
// 排序一个数组
String[] strings = "Mary had a little lamb".split(" ");
Arrays.sort(strings, String::compareToIgnoreCase);
System.out.println(Arrays.toString(strings));
}
}
分析例4,对于函数式接口Comparator来说,它的抽象方法为:
int compare(T o1, T o2);
这个方法有两个参数,对于例1来说,出现在lambda表达式参数中的s1,s2,实际上就是这两个参数。例2,例3中的方法myCompareToIgnoreCase的参数也是如此。
而对于第三个关于方法引用的例子,String的compareToIgnoreCase方法只有一个参数。这时,第一个参数将会作为执行方法的对象,(s1.compareToIgnoreCase(s2))
另外,也可以通过如下形式方法引用:
this::实例方法
super::实例方法
方法引用例5:
public class SuperTest {
public static void main(String[] args) {
class Greeter {
public void greet() {
System.out.println("Hello, world!");
}
} class ConcurrentGreeter extends Greeter {
public void greet() {
Thread t = new Thread(super::greet);
t.start();
}
} new ConcurrentGreeter().greet();
}
}
4. 构造器引用
和方法引用相似,只不过通过如下方式引用:
类::new
构造器引用可以生成一个类的实例
例1
Stream<Button> stream = labels.stream().map(Button::new);
Button[] buttons4 = stream.toArray(Button[]::new);
5.变量作用域
lambda表达式引用值,而不是变量。
lambda表达式中引用的局部变量必须是:显示声明为final的,或者虽然没有被声明为final,但实际上也算是有效的final的。
在Java中与其相似的是匿名内部类关于局部变量的引用。
例1:匿名内部类引用局部变量——jdk8以前
public class Outter { public static void main(String[] args) {
final String s1 = "Hello ";
new Inner() {
@Override
public void printName(String name) {
System.out.println(s1 + name);
}
}.printName("Lucy"); }
} interface Inner{
public void printName(String name); };
如例1所示,在jdk8以前,匿名内部类引用外部类定义的局部变量,则该变量必须是final的。
jdk8将这个条件放宽,匿名内部类也可以访问外部类有效的final局部变量——即这个变量虽然没有显示声明为final,但定义后也没有再发生变化。
例2:匿名内部类引用局部变量——jdk8
public class Outter { public static void main(String[] args) {
String s1 = "Hello ";
new Inner() {
@Override
public void printName(String name) {
System.out.println(s1 + name);
}
}.printName("Lucy"); }
} interface Inner{
public void printName(String name); };
匿名内部类引用的外部类变量s1可以不显示定义为final。但是s1必须在初始化后不再改变。
lambda表达式对于引用局部变量的规则同jdk8中的匿名内部类一样:显示声明为final的,或者虽然没有被声明为final,但实际上也算是有效的final的
import java.io.*;
import java.nio.charset.*;
import java.nio.file.*;
import java.util.*;
import java.util.stream.*; public class VariableScope {
public static void main(String[] args) {
repeatMessage("Hello", 100);
} public static void repeatMessage(String text, int count) {
Runnable r = () -> {
for (int i = 0; i < count; i++) {
System.out.println(text);
Thread.yield();
}
};
new Thread(r).start();
} public static void repeatMessage2(String text, int count) {
Runnable r = () -> {
while (count > 0) {
// count--; // Error: Can't mutate captured variable
System.out.println(text);
Thread.yield();
}
};
new Thread(r).start();
} public static void countMatches(Path dir, String word) throws IOException {
Path[] files = getDescendants(dir);
int matches = 0;
for (Path p : files)
new Thread(() -> {
if (contains(p, word)) {
// matches++;
// ERROR: Illegal to mutate matches
}
}).start();
} private static int matches; public static void countMatches2(Path dir, String word) {
Path[] files = getDescendants(dir);
for (Path p : files)
new Thread(() -> {
if (contains(p, word)) {
matches++;
// CAUTION: Legal to mutate matches, but not threadsafe
}
}).start();
} // Warning: Bad code ahead
public static List<Path> collectMatches(Path dir, String word) {
Path[] files = getDescendants(dir);
List<Path> matches = new ArrayList<>();
for (Path p : files)
new Thread(() -> {
if (contains(p, word)) {
matches.add(p);
// CAUTION: Legal to mutate matches, but not threadsafe
}
}).start();
return matches;
} public static Path[] getDescendants(Path dir) {
try {
try (Stream<Path> entries = Files.walk(dir)) {
return entries.toArray(Path[]::new);
}
} catch (IOException ex) {
return new Path[0];
}
} public static boolean contains(Path p, String word) {
try {
return new String(Files.readAllBytes(p),
StandardCharsets.UTF_8).contains(word);
} catch (IOException ex) {
return false;
}
}
}
-----
-----
---
、
Java SE 8 for the Really Impatient读书笔记——Java 8 Lambda表达式的更多相关文章
- 《深入了解java虚拟机》高效并发读书笔记——Java内存模型,线程,线程安全 与锁优化
<深入了解java虚拟机>高效并发读书笔记--Java内存模型,线程,线程安全 与锁优化 本文主要参考<深入了解java虚拟机>高效并发章节 关于锁升级,偏向锁,轻量级锁参考& ...
- 《分布式Java应用之基础与实践》读书笔记二
远程调用方式就是尽可能地使系统间的通信和系统内一样,让使用者感觉调用远程同调用本地一样,但其实没没有办法做到完全透明,例如由于远程调用带来的网络问题.超时问题.序列化/反序列化问题.调式复杂的问题等. ...
- [读书笔记]java核心技术
ps:有时间好好整理下格式.从别的编辑器拷贝过来啥都没了. ~~~~~~~~~~~~~~· 2.java程序设计环境 JDK 开发java使用的软件: JRE 运行java使用的软件: SE 用于桌面 ...
- [读书笔记]java中的类加载器
以下内容大多来自周志明的<深入理解Java虚拟机>. 类加载器是java的一项创新,也是java流行的重要原因之一,它最初是为了满足java applet的需求而开发出来. 什么是appl ...
- 夯实Java基础(二十二)——Java8新特性之Lambda表达式
1.前言 Java 8于14年发布到现在已经有5年时间了,经过时间的磨练,毫无疑问,Java 8是继Java 5(发布于2004年)之后的又一个非常最重要的版本.因为Java 8里面出现了非常多新的特 ...
- [读书笔记]java中的volatile关键词
以下内容大多来自周志明的<深入理解Java虚拟机>. 当一个变量被volatile修饰后,它将具备两种特性: 1. 保证此变量对所有线程的可见性,这里的“可见性”是指当一条线程修改了这个变 ...
- [读书笔记]Java之动态分派
以下内容来自周志明的<深入理解Java虚拟机>. 前一篇说了静态分派和重载有关,现在的动态分派就和覆盖Override有关了. 先看代码: public class DynamicDisp ...
- [读书笔记]Java之静态分派
以下内容来自周志明的<深入理解Java虚拟机>. 静态分派和重载有关. 先看代码: public static void main(String[] args) { SuperClass ...
- <代码整洁之道>、<java与模式>、<head first设计模式>读书笔记集合
一.前言 几个月前的看书笔记 ...
随机推荐
- php Debugging with Xdebug and Sublime Text 3(转)
Debugging – we all do it a lot. Writing code perfectly the first time around is hard and only a few ...
- Spring 之 注解详解
概述 注释配置相对于 XML 配置具有很多的优势: 它可以充分利用 Java 的反射机制获取类结构信息,这些信息可以有效减少配置的工作.如使用 JPA 注释配置 ORM 映射时,我们就不需要指定 PO ...
- css模板之 web模板一
效果 <html><head><style type="text/css">div.container{width:100%;margin:0p ...
- hadoop源码编译
为何要自行编译hadoop源码,往往是由于官方提供的hadoop发行版都是基于32位操作系统,在操作hadoop时会发生warn. 准备软件: 1)JDK 2)Hadoop源码 3)Maven 4 ...
- Spring配置扫描mybatis的mapper文件注意:
一般会将不业务的mapper文件放到不同的包中: spring配置扫描就需要配置下面的方式(两个*): <!-- mybatis文件配置,扫描所有mapper文件 --> <bean ...
- Leetcode算法刷题:第14题 Longest Common Prefix
Longest Common Prefix 题目 给予一个列表,元素为字符串,写一个程序找出最长公共前缀 解题思路 先比较两个字符串,如果第一个字符不一样,则返回空值,比较完成后,用这个公共字符串和下 ...
- win7 PHP7.0的PDO扩展
一个非常棘手的问题,win7(64位)环境,编译安装的mysql,php无法使用pdo扩展. 而我的centos中yum安装的php,pdo是好用的. 百度了一大堆,都无法解决. 基本上百度到的都是要 ...
- SQL Server 解决CPU 高占用的一般step
第一步: 看这些CPU是不是SQL Server用的. 第二步: 确定SQL Server 有没有引发17883\17884错误 第三步: 找出使用CPU最高的语句进行优化.(sys.dm_exec_ ...
- placeholder在不同浏览器下的表现及兼容方法 placeholder兼容
1.什么是placeholder? placeholder是html5新增的一个属性,当input或者textarea设置了该属性后,该值的内容将作为灰字提示显示在文本框中,当文本框获得焦点(或 ...
- 做为一个Java程序员,你需要哪些傍身的技能?
最近总有些断断续续的思考,想想从我入行以来,我到底学会了什么,做成过什么,以后要做什么,如何提升自己······· 工作3年了,常听人说3年,5年,10年是程序员的坎,每过一个都会有新的想法,新的改变 ...