Java 8新特性
- 速度更快
- 代码更少,增加了新的语法,Lambda表达式
- 强大的
Stream API
- 便于并行
- 最大化减少空指针异常:
Optional
类
Nashorn
引擎,允许在JVM上运行JS应用
Lambda表达式
Lambda是一个匿名函数,我们可以把Lambda表达式理解为是一段可以传递的代码(将代码像数据一样进行传递)。使用它可以写出更简洁、更灵活的代码。作为一种更紧凑的代码风格,使Java的语言表达能力得到了提升。
本质
作为接口的实例
基本语法
->
lambda操作符,或箭头操作符
->
左边:lambda形参列表,其实就是接口中的抽象方法的形参列表
->
右边:lambda体,其实就是重写的抽象方法的方法体
语法格式
Runnable r2 = () -> { System.out.println("Hello world"); }
- 语法格式二:lambda需要一个参数,但是没有返回值
Consumer<String> con1 = (String s) -> { System.out.println(s); }
- 语法格式三:数据类型可以省略,因为可由编译器推断得出,称为类型推断
Consumer<String> con1 = (s) -> { System.out.println(s); }
- 语法格式四:lambda若只需要一个参数时,参数的小括号可以省略
Consumer<String> con1 = s -> { System.out.println(s); }
- 语法格式五:lambda需要两个或以上的参数,多条执行语句,并且可以有返回值
Comparator<Integer> com2 = (o1, o2) -> { return Integer.compare(o1, o2); };
- 语法格六:当lambda体只有一条语句时, return 与大括号若有,都可以省略
Comparator<Integer> com2 = (o1, o2) -> Integer.compare(o1, o2);
Consumer<String> con1 = s -> System.out.println(s);
总结
->
左边:lambda形参列表的参数类型可以省略(类型推断);如果lambda形参列表只有一个参数,其一对()
也可以省略
->
右边:lambda体应该使用一对{}
包裹;如果lambda体只有一条执行语句(可能是 return
语句),则可以省略 {}
和 return
关键字
使用场景
以前使用匿名实现类表示的现在都可以用 Lambda 表达式来写。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72
| package com.xzt.lambda_;
import org.junit.jupiter.api.Test;
import java.util.Comparator; import java.util.function.Consumer;
public class Lambda_ { public static void main(String[] args) { Runnable r1 = new Runnable() { @Override public void run() { System.out.println("Hello world"); } };
r1.run();
Runnable r2 = () -> System.out.println("Hello world");
r2.run(); }
@Test public void Test() { Comparator<Integer> com1 = new Comparator<Integer>() { @Override public int compare(Integer o1, Integer o2) { return Integer.compare(o1, o2); } };
int compare = com1.compare(12, 21); System.out.println(compare);
Comparator<Integer> com2 = (o1, o2) -> Integer.compare(o1, o2);
int compare1 = com2.compare(12, 21); System.out.println(compare1); Comparator<Integer> com3 = Integer::compare;
int compare2 = com2.compare(12, 21); System.out.println(compare2); }
@Test public void test2() { Consumer<String> con = new Consumer<String>() { @Override public void accept(String s) { System.out.println(s); } }; con.accept("abcdefd");
Consumer<String> con1 = (String s) -> System.out.println(s); con1.accept("abcdefd");
} }
|
函数式(Function)接口
如果一个接口中,只声明了一个抽象方法,则此接口就称为函数式接口
可以使用@FunctionalInterface
注解,不加也是函数式接口,加上可以帮助校验。
1 2 3 4 5 6 7 8 9 10
| package com.xzt.functioninterface_;
@FunctionalInterface public interface MyInterface { void method(); }
|
1 2 3 4 5 6 7 8 9 10 11 12 13
| package com.xzt.functioninterface_;
public class FunctionalInterface_ { public static void main(String[] args) { MyInterface myInterface = () -> System.out.println("Hell world"); myInterface.method(); } }
|
注意事项
- 只包含一个抽象方法的接口,称为函数式接口
- 你可以通过Lambda表达式来创建该接口的对象。(若Lambda表达式抛出一个受检异常(即:非运行时异常),那么该异常需要在目标接口的抽象方法上进行声明)
- 我们可以在一个接口上使用
@FunctionalInterface
注解,这样做可以检查它是否在一个函数是接口。同时 javadoc 也会包含一条声明,说明这个接口是一个函数式接口
- 在
java.util.function
包下定义了 Java 8 的丰富的函数式接口
内置核心函数式接口、
四大核心内置核心函数式接口
函数式接口 |
参数类型 |
返回类型 |
用途 |
Consumer<T> 消费型接口 |
T |
void |
对类型为T的对象应用操作,包含方法:void accept(T t) |
supplier<T> 供给型接口 |
T |
T |
返回类型为T的对象,包含方法:T get() |
Function<T, R> 函数型接口 |
T |
R |
对类型为T的对象应用此操作,并返回结果。结果是R类型的对象。包含方法R apply(T t) |
Predicate<T> 断定型接口 |
T |
Boolean |
确定类型为T的对象是否满足某约束,并返回Boolean值,包含方法:boolean test(T t) |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64
| package com.xzt.functioninterface_;
import org.junit.jupiter.api.Test;
import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.function.Consumer; import java.util.function.Predicate; import java.util.function.Supplier;
public class MainFunctionalInterface { public static void main(String[] args) { Consumer<Integer> consumer = new Consumer<Integer>() { @Override public void accept(Integer integer) { System.out.println("数 = " + integer); } };
Consumer<Integer> consumer1 = (integer) -> System.out.println("数 = " + integer);
Supplier<Integer> supplier = new Supplier<Integer>() { @Override public Integer get() { return 20; } }; Supplier<Integer> supplier1 = () -> 20;
}
@Test public void test() { List<String> list = Arrays.asList("北京", "南京", "普京", "天津", "东京"); List<String> filterStrs = filterString(list, new Predicate<String>() { @Override public boolean test(String s) { return s.contains("京"); } }); System.out.println(filterStrs);
List<String> filterStrs1 = filterString(list, s -> s.contains("京")); System.out.println(filterStrs1); }
public List<String> filterString(List<String> list, Predicate<String> pre) { ArrayList<String> strings = new ArrayList<>();
for (String s : list) { if (pre.test(s)) { strings.add(s); } } return strings; } }
|
方法引用和构造器引用
方法引用
当要传递给Lambda 体的操作,已经有实现的方法了,可以使用方法引用
- 方法引用可以看作是Lambda表达式深层次的表达,换句话说,方法引用就是Lambda表达式,也就是函数式接口的一个实例,通过方法的名字来指向一个方法,可以认为是Lambda表达式的一个语法堂。
- 要求:实现接口的抽象方法的参数列表和返回值类型,必须与方法引用的方法的参数列表和返回值类型保持一致。
- 格式:使用操作符
::
将类或对象与方法名分割开来
使用情况
对象::实例化方法名
类::静态方法名
类::实例化方法名
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
| package com.xzt.methodreference;
import org.junit.jupiter.api.Test;
import java.util.function.Consumer; import java.util.function.Supplier;
public class MethodReference {
@Test public void test(){ Consumer<String> consumer = str -> System.out.println(str); consumer.accept("北京");
Consumer<String> consumer1 = System.out::println;
consumer1.accept("北京"); }
@Test public void test1(){ String name = "abcd"; Supplier<String> supplier = () -> name; System.out.println(supplier.get()); Supplier<String> supplier1 = name::toString; System.out.println(supplier1.get()); } }
|
构造器引用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| package com.xzt.methodreference;
import java.util.function.Supplier;
public class MethodReference01 { public static void main(String[] args) { Supplier<Employee> sup = new Supplier<Employee>() { @Override public Employee get() { return new Employee(); } };
Supplier<Employee> sup1 = () -> new Employee();
Supplier<Employee> sup2 = Employee::new; } }
class Employee{ public Employee get() { return new Employee(); } }
|
强大的Stream API
把真正的函数式编程风格引入到Java中,这是目前为止对Java类库最好的补充,因为 Stream API可以极大的提供Java程序员的生产力,让程序员写出高效率、干净、简洁的代码。
- Stream API 对集合数据进行操作,就类似于使用SQL执行的数据库查询,也可以使用Stream API来并行执行操作。
- Stream API 提供了一种高效且易于使用的处理数据的方式。
Stream 和 Collection集合的区别
- Collection 是一种面向静态的内存数据结构,而 Stream 是有关计算的。前者主要是面向内存的,存储在内存中,后者主要是面向CPU,通过CPU实现计算。
注意
- Stream 自己不会存储元素
- Stream 不会改变源对象,相反,他们会返回一个持有结果的新Stream
- Stream 操作是延迟执行的,这意味者他们会等到需要结果的时候才执行
操作步骤
- 创建 Stream
一个数据源(如:集合、数组),获取一个流
- 中间操作
一个中间操作链,对数据员的数据进行处理
- 终止操作(终端操作)
一旦执行终止操作,就执行中间操作链,并产生结果。之后,不会再使用
创建对象的方式
通过集合
Java 8中的 Collection
接口被扩展,提供两个获取流的方法:
default Stream<E> stream()
返回一个顺序流
default Stream<E> parallelStream()
返回一个并行流
1 2 3 4 5 6 7 8 9 10
| @Test public void test(){ List<String> strings = new ArrayList<>();
Stream<String> stream = strings.stream();
Stream<String> stringStream = strings.parallelStream(); }
|
通过数组
Java 8中的 Arrays
的静态方法 stream()
可以获取数组流
1 2 3 4 5
| @Test public void test2() { int[] arr = {1, 2, 3, 9, 6, 4}; Arrays.stream(arr); }
|
通过 Stream 的 of
可以调用Stream 类的静态方法 of()
,通过显示值创建一个流。它可以接受任意数量的参数。
public static<T> Stream<T> of(T...values)
返回一个流
1 2 3 4
| @Test public void test3() { Stream.of(1, 2, 3, 4); }
|
创建无限流
1 2 3 4 5 6 7 8
| @Test public void test4() { Stream.iterate(0, t -> t + 2).limit(10).forEach(System.out::println);
Stream.generate(Math::random).limit(10).forEach(System.out::println); }
|
Stream 的中间操作
多个中间操作可以连接起来形成一个流水线,除非流水线上触发终止操作,否则中间操作不会执行任何的处理,而在终止操作时一次性全部处理,称为”惰性求值“