本来打算通过看书《Java 8 函数式编程》,然后加深对Java 8特性的学习;但读书还是慢的,可能也有些浮躁,看不下去书。
先速成一波,对一些我不知道的功能先进行一个了解,然后再通过看书进行补充。
概要:
- Lambda 表达式 − Lambda 允许把函数作为一个方法的参数(函数作为参数传递到方法中)。
- Stream API −新添加的Stream API(java.util.stream) 把真正的函数式编程风格引入到Java中。
- 方法引用 −方法引用提供了非常有用的语法,可以直接引用已有Java类或对象(实例)的方法或构造器。与lambda联合使用,方法引用可以使语言的构造更紧凑简洁,减少冗余代码。
- 函数式接口 - 是一个有且仅有一个抽象方法,但是可以有多个非抽象方法的接口。
- 默认方法 − 默认方法就是一个在接口里面有了一个实现的方法。
- 新工具 − 新的编译工具,如:Nashorn引擎 jjs、 类依赖分析器jdeps。
- Date Time API − 加强对日期与时间的处理。
- Optional 类 − Optional 类已经成为 Java 8 类库的一部分,用来解决空指针异常。
- Nashorn, JavaScript 引擎 − Java 8提供了一个新的Nashorn
- javascript引擎,它允许我们在JVM上运行特定的javascript应用。
1、Lambda 表达式:
排序例子:—Java 7重写比较器、Java 8 Lambda表达式
// 使用 java 7 排序
private void sortUsingJava7(List<String> names){
Collections.sort(names, new Comparator<String>() {
@Override
public int compare(String s1, String s2) {
return s1.compareTo(s2);
}
});
}
// 使用 java 8 排序
private void sortUsingJava8(List<String> names){
Collections.sort(names, (s1, s2) -> s1.compareTo(s2));
}
Lambda表达式,又称为闭包。
允许把函数作为一个方法的参数传进方法中。
将匿名内部类中的最核心的内容(方法参数、方法体、返回值)简化出来–函数式编程思想。
基本语法:
简单的Lambda表达式可以用 逗号分隔的参数列表、-> 符号及语句块组成。
- (parameters) -> expression; 或 (parameters) ->{ statements};
注意:—Collections.sort(names, (s1, s2) -> s1.compareTo(s2));
- 不需要声明参数类型,编译器可以统一识别参数值(小括号中的参数类型可以省略) – String name
- 一个参数不需要定义圆括号(),但是多个参数需要定义圆括号(如果小括号中只有一个参数,那么可以省略小括号) – ( name )
如果大括号中只有一条语句,那么可以省略大括号,return,分号 - 如果主体只包含了一个语句,就不需要使用大括号 – {(s1, s2) -> s1.compareTo(s2)}
- 如果主体只有一个表达式返回值则编译器会自动返回值,使用大括号时(即主体有多个语句时)需要指明表达式返回一个数值
简单例子:
// 1. 不需要参数,返回值为 5
() -> 5
// 2. 接收一个参数(数字类型),返回其2倍的值
x -> 2 * x
// 3. 接受2个参数(数字),并返回他们的差值
(x, y) -> x – y
// 4. 接收2个int型整数,返回他们的和
(int x, int y) -> x + y
// 5. 接受一个 string 对象,并在控制台打印,不返回任何值(看起来像是返回void)
(String s) -> System.out.print(s)
实例–解释注意:
public class Java8Tester {
public static void main(String args[]){
Java8Tester tester = new Java8Tester();
// 类型声明
MathOperation addition = (int a, int b) -> a + b;
// 不用类型声明
MathOperation subtraction = (a, b) -> a - b;
// 大括号中的返回语句
MathOperation multiplication = (int a, int b) -> { return a * b; };
// 没有大括号及返回语句
MathOperation division = (int a, int b) -> a / b;
System.out.println("10 + 5 = " + tester.operate(10, 5, addition));
System.out.println("10 - 5 = " + tester.operate(10, 5, subtraction));
System.out.println("10 x 5 = " + tester.operate(10, 5, multiplication));
System.out.println("10 / 5 = " + tester.operate(10, 5, division));
// 不用圆括号
GreetingService greetService1 = message -> System.out.println("Hello " + message);
// 用圆括号
GreetingService greetService2 = (message) -> System.out.println("Hello " + message);
greetService1.sayMessage("Runoob");
greetService2.sayMessage("Google");
}
// 定义一个MathOperation接口,包含一个operation方法
interface MathOperation {
int operation(int a, int b);
}
interface GreetingService {
void sayMessage(String message);
}
private int operate(int a, int b, MathOperation mathOperation){
return mathOperation.operation(a, b);
}
}
变量的作用域
- lambda 表达式只能引用标记了 final 的外层局部变量,这就是说不能在 lambda内部修改定义在域外的局部变量,否则会编译错误。
- 也可以直接在 lambda 表达式中访问外层的局部变量:
- lambda 表达式的局部变量可以不用声明为 final,但是必须不可被后面的代码修改(即隐性的具有 final 的语义)
- 在 Lambda 表达式当中不允许声明一个与局部变量同名的参数或者局部变量。
2、Java 8 流–stream
Stream 使用一种类似用 SQL 语句从数据库查询数据的直观方式来提供一种对 Java 集合运算和表达的高阶抽象。
将要处理的元素集合看作一种流, 流在管道中传输, 并且可以在管道的节点上进行处理, 比如筛选, 排序,聚合等。
元素流在管道中经过中间操作(intermediate operation)的处理,最后由最终操作(terminal operation)得到前面处理的结果。
+--------------------+ +------+ +------+ +---+ +-------+
| stream of elements +-----> |filter+-> |sorted+-> |map+-> |collect|
+--------------------+ +------+ +------+ +---+ +-------+
// 以上的流程转换为 Java 代码为:
List<Integer> transactionsIds =
widgets.stream()
.filter(b -> b.getColor() == RED)
.sorted((x,y) -> x.getWeight() - y.getWeight())
.mapToInt(Widget::getWeight)
.sum();
什么是Stream?
Stream是一个来自数据源并支持聚合操作的元素队列。
- 元素:元素是特定类型的对象,形成一个队列。Stream并不会存储元素,只是进行按需计算。
- 数据源:是流的来源,可以是集合、数组、IO、生成器gennerator等。
- 聚合操作:类似SQL语句,filter、map、reduce、find、match、sorted等。
不同于Collection操作,Stream操作还有:
- Pipelining:中间操作都会返回流对象本身。这样多个操作可以串联成一个管道,可以对操作进行优化–延迟执行(laziness)和短路(short-circuiting)。
- 内部迭代:之前对集合遍历是通过Iterator迭代器或者for-each方式,显式地在集合外部进行迭代–外部迭代。Stream可以通过访问者模式(Visitor)实现内部迭代。
基本用法:
- 1、生成流
stream() - 为集合创建串行流
parallelStream() - 为集合创建并行流
// 将字符串数组去掉空的字符串
List<String> strings = Arrays.asList("abc", "", "bc", "efg", "abcd","", "jkl");
List<String> filtered = strings.stream().filter(string -> !string.isEmpty()).collect(Collectors.toList());
// 从现有数组中获取流
private static Employee[] arrayOfEmps = {
new Employee(1, "Jeff Bezos", 100000.0),
new Employee(2, "Bill Gates", 200000.0),
new Employee(3, "Mark Zuckerberg", 300000.0)
};
Stream.of(arrayOfEmps);
// 从现有列表中获取流
private static List<Employee> empList = Arrays.asList(arrayOfEmps);
empList.stream();
// Java 8向Collection接口添加了新的stream()方法。
// 可以使用Stream.of()从单个对象创建一个流:
Stream.of(arrayOfEmps[0], arrayOfEmps[1], arrayOfEmps[2]);
// 简单地使用Stream.builder()
Stream.Builder<Employee> empStreamBuilder = Stream.builder();
empStreamBuilder.accept(arrayOfEmps[0]);
empStreamBuilder.accept(arrayOfEmps[1]);
empStreamBuilder.accept(arrayOfEmps[2]);
Stream<Employee> empStream = empStreamBuilder.build();
- 2、forEach
新的方法forEach,用于迭代流中的每个数据
// 使用forEach输出10个随机数
Random random = new Random();
random.ints().limit(10).forEach(System.out::println);
这里System.out::println是一个方法引用表达式,是对实例方法的引用,是e -> System.out.println(e)的进一步简写
// 将Integer的流转换为Employee的流:
@Test
public void whenMapIdToEmployees_thenGetEmployeeStream() {
Integer[] empIds = { 1, 2, 3 };
List<Employee> employees = Stream.of(empIds)
.map(employeeRepository::findById)
.collect(Collectors.toList());
assertEquals(employees.size(), empIds.length);
}
// 得到一个整数雇员ID的从数组流。每个Integer都传递给函数employeeRepository :: findById(),该函数 返回相应的Employee对象。这有效地形成了员工流。
- toArray():-从流中获取数组
@Test
public void whenStreamToArray_thenGetArray() {
// Employee [] :: new创建一个空的Employee数组 -然后用流中的元素填充它。
Employee[] employees = empList.stream().toArray(Employee[]::new);
assertThat(empList.toArray(), equalTo(employees));
}
- collect(): 完成所有处理,从流中获取内容
使用toList收集器将所有Stream元素收集到一个List实例中。
@Test
public void whenCollectStreamToList_thenGetList() {
List<Employee> employees = empList.stream().collect(Collectors.toList());
assertEquals(empList, employees);
}
- 3、map
map方法用于映射每个元素到对应的结果。
// 获取对应的平方数
List<Integer> numbers = Arrays.asList(3, 2, 2, 3, 7, 3, 5);
List<Integer> squaresList = numbers.stream().map( i -> i*i).distinct().collect(Collectors.toList());
- flatMap()-帮助展平数据结构简化进一步的操作
flatMap的可以处理更深层次的数据,入参为多个list,结果可以返回为一个list,而map是一对一的,入参是多个list,结果返回必须是多个list。通俗的说,如果入参都是对象,那么flatMap可以操作对象里面的对象,而map只能操作第一层。
// Stream <List <String >>
List<List<String>> namesNested = Arrays.asList(
Arrays.asList("Jeff", "Bezos"),
Arrays.asList("Bill", "Gates"),
Arrays.asList("Mark", "Zuckerberg"));
System.out.println(namesNested);
结果:[[Jeff, Bezos], [Bill, Gates], [Mark, Zuckerberg]]
List<String> namesFlatStream = namesNested.stream()
.flatMap(Collection::stream)
.collect(Collectors.toList());
System.out.println(namesFlatStream);
结果:[Jeff, Bezos, Bill, Gates, Mark, Zuckerberg]
-
peek()
forEach()是一个终端操作。是指返回最终的结果。
而peek是个中间操作。
通过peek可以查看每个值,同时能继续操作流。 -
4、filter
filter方法用于通过设置的条件过滤出元素,会产生一个新流。
// 获取空字符串的数量
List<String>strings = Arrays.asList("abc", "", "bc", "efg", "abcd","", "jkl");
long count = strings.stream().filter(string -> string.isEmpty()).count();
首先过滤掉空的ID,过滤器仅保留薪水超过阈值 的员工。
@Test
public void whenFilterEmployees_thenGetFilteredStream() {
Integer[] empIds = { 1, 2, 3, 4 };
List<Employee> employees = Stream.of(empIds)
.map(employeeRepository::findById)
.filter(e -> e != null)
.filter(e -> e.getSalary() > 200000)
.collect(Collectors.toList());
assertEquals(Arrays.asList(arrayOfEmps[2]), employees);
}
// 将返回薪水大于100000的第一位员工。如果不存在这样的雇员,则返回null。
@Test
public void whenFindFirst_thenGetFirstEmployeeInStream() {
Integer[] empIds = { 1, 2, 3, 4 };
Employee employee = Stream.of(empIds)
.map(employeeRepository::findById)
.filter(e -> e != null)
.filter(e -> e.getSalary() > 100000)
.findFirst()
.orElse(null);
assertEquals(employee.getSalary(), new Double(200000));
}
- 5、limit:limit方法用于获取指定数量的流。
- 6、sorted:sorted方法用于对流进行排序。
- 7、并行(parallel)程序
parallelStream是流并行处理程序的代替方法。
// 获取空字符串的数量
List<String> strings = Arrays.asList("abc", "", "bc", "efg", "abcd","", "jkl");
long count = strings.parallelStream().filter(string -> string.isEmpty()).count();
- 8、Collectors
Collectors类实现了像将流转换成集合、聚合元素等操作。Collectors可用于返回列表或字符串。
Collectors.toList()、Collectors.joining()
- 9、统计
产生统计结果的收集器,主要用于int、double、long等基本类型上。
List<Integer> numbers = Arrays.asList(3, 2, 2, 3, 7, 3, 5);
IntSummaryStatistics stats = numbers.stream().mapToInt((x) -> x).summaryStatistics();
System.out.println("列表中最大的数 : " + stats.getMax());
System.out.println("列表中最小的数 : " + stats.getMin());
System.out.println("所有数之和 : " + stats.getSum());
System.out.println("平均数 : " + stats.getAverage());
3、方法引用
方法引用通过方法的名字来指向一个方法。
使用一对冒号:: 实现方法引用。
0、在Car类中预定义4个方法:
@FunctionalInterface
public interface Supplier<T> {
T get();
}
class Car {
//Supplier是jdk1.8的接口,这里和lamda一起使用了
public static Car create(final Supplier<Car> supplier) {
return supplier.get();
}
public static void collide(final Car car) {
System.out.println("Collided " + car.toString());
}
public void follow(final Car another) {
System.out.println("Following the " + another.toString());
}
public void repair() {
System.out.println("Repaired " + this.toString());
}
}
1、构造器引用:
语法是 Class::new, 或者: Class::new
final Car car = Car.create(Car::new );
final List< Car > cars = Arrays.asList(car);
2、静态方法引用
语法是 Class::static_method
cars.forEach( Car::collide );
3、特定类的任意对象的方法引用
语法是 Class::method
cars.forEach(Car::repair)
4、特定对象的方法引用
语法是 instance::method
final Car police = Car.create( Car::new );
cars.forEach( police::follow );
names.forEach(System.out::println);
- System.out是一个PrintStream实例的引用;System.out::println 是对一个实例方法的引用
该引用同时指定了对实例(System.out)的引用以及对方法(PrintStream::println)的引用 - System.out::println 不是 System.out.println的等价物;前者是一个方法引用表达式,而后者不能单独作为一个表达式,而必须在后面跟上由圆括号包围的参数列表来构成方法调用表达式。
- System.out::println 可以看作 lambda表达式 e -> System.out.println(e) 的缩写形式。
4、函数式接口
函数式接口(Functional Interface)就是一个有且仅有一个抽象方法,但是可以有多个非抽象方法的接口。
函数式接口可以被隐式转换为 lambda 表达式。
// 定义了一个函数式接口
@FunctionalInterface
interface GreetingService {
void sayMessage(String message);
}
// 使用Lambda表达式来表示接口的一个实现
GreetingService greetService1 = message -> System.out.println("Hello " + message);
JDK 1.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
JDK 1.8 新增加的函数接口:
java.util.function
function类包含的函数式接口
5、默认方法
Java 8之前接口中的方法都是抽象方法,没有实现–面向抽象编程;但是需要修改接口时,必须修改全部实现该接口的类。
Java 8中引入了接口的默认方法,接口可以有default实现的默认方法,可以不需要实现类去实现其方法。
接口默认方法存在“类优先”原则
若一个接口中定义了一个默认方法,而另外一个父类或接口中又定义了一个同名的方法时:
- 选择父类中的方法。如果一个父类提供了具体的实现,那么接口中具有相同名称和参数的默认方法会被忽略。
- 接口冲突。如果一个父接口提供一个默认方法,而另一个接口也提供了一个具有相同名称和参数列表的方法(不管方法是否是默认方法),那么必须覆盖该方法来解决冲突。
多个默认方法:
// 一个接口有默认方法,考虑这样的情况,一个类实现了多个接口,且这些接口有相同的默认方法,
public interface vehicle {
default void print(){
System.out.println("我是一辆车!");
}
}
public interface fourWheeler {
default void print(){
System.out.println("我是一辆四轮车!");
}
}
// 第一个解决方案是创建自己的默认方法,来覆盖重写接口的默认方法:
public class car implements vehicle, fourWheeler {
public void print(){
System.out.println("我是一辆四轮汽车!");
}
}
// 第二种解决方案可以使用 super 来调用指定接口的默认方法:
public class car implements vehicle, fourWheeler {
public void print(){
vehicle.super.print();
}
}
静态方法:
public interface vehicle {
default void print() {
System.out.println("我是一辆车!");
}
// 静态方法
static void blowHorn() {
System.out.println("按喇叭!!!");
}
}