Java8的发布带来了很多新特性,特别是Lambda表达式的支持 ,使得函数传递变得很容易,另外StreamApi使得对容器内的数据处理变得异常容易和强大,大大增加了开发效率。
Java语言层面上最受期待的改变,Lambda允许把函数作为一个方法的参数(函数作为参数传递进方法中),代替了原先匿名内部类,使得将行为表达为数据变得很容易,在纯Java语言环境中提供一种优雅的方式来支持函数式编程,从而使开发具有更强表达能力。
Lambda表达式由用逗号分隔的 参数列表 、 –>符号 、 函数体 三部分表示 e -> System.out.println( e ) 举几个例子
Arrays.asList( "a", "b", "d" ).forEach( e -> System.out.println( e ) ); Arrays.asList( "a", "b", "d" ).forEach( ( String e ) -> System.out.println( e ) ); Arrays.asList( "a", "b", "d" ).forEach( e -> { System.out.print( e ); System.out.print( e ); } ); String separator = ","; Arrays.asList( "a", "b", "d" ).forEach( ( String e ) -> System.out.print( e + separator ) ); final String separator = ","; Arrays.asList( "a", "b", "d" ).forEach( ( String e ) -> System.out.print( e + separator ) ); Arrays.asList( "a", "b", "d" ).sort( ( e1, e2 ) -> { int result = e1.compareTo( e2 ); return result; } ); Arrays.asList( "a", "b", "d" ).sort( ( e1, e2 ) -> e1.compareTo( e2 ) ); 复制代码
Java8引入了函数式接口FunctionalInterface概念,来使得现有的函数支持Lambda表达式。
函数式接口就是一个具有一个方法的普通接口,可以被隐式转换为lambda表达式。例如java.lang.Runnable与java.util.concurrent.Callable是函数式接口最典型的两个例子。
在实际使用过程中,函数式接口是容易出错的:如有某个人在接口定义中增加了另一个方法,这时,这个接口就不再是函数式的了,并且编译过程也会失败。为了克服函数式接口的这种脆弱性并且能够明确声明接口作为函数式接口的意图,Java 8增加了一种特殊的注解@FunctionalInterface
@FunctionalInterface public interface Function<T, R> { R apply(T t); } Function<Integer, String> function = (e) -> { e++; return "y = " + e; }; 复制代码
@FunctionalInterface public interface Function<T, R> { R apply(T t); } @FunctionalInterface public interface Consumer<T> { void accept(T t); } @FunctionalInterface public interface Supplier<T> { T get(); } @FunctionalInterface public interface Predicate<T> { boolean test(T t); } public interface Iterable<T> { Iterator<T> iterator(); default void forEach(Consumer<? super T> action) { Objects.requireNonNull(action); for (T t : this) { action.accept(t); } } } 复制代码
Java 8用默认方法与静态方法这两个新概念来扩展接口的声明,可以包含实现代码,允许在已有的接口中添加新方法,而同时又保持了与旧版本代码的兼容性。默认方法与抽象方法不同之处在于抽象方法必须要求实现,但是默认方法则没有这个要求。
Java8通过在接口中提供默认方法的方式,增强了现有接口的功能,比如 java.util.Collection接口增加了stream(),parallelStream(),removeIf();java.lang.Iterable接口增加forEach()
@FunctionalInterface public interface Function<T, R> { R apply(T t); default <V> Function<V, R> compose(Function<? super V, ? extends T> before) { Objects.requireNonNull(before); return (V v) -> apply(before.apply(v)); } default <V> Function<T, V> andThen(Function<? super R, ? extends V> after) { Objects.requireNonNull(after); return (T t) -> after.apply(apply(t)); } static <T> Function<T, T> identity() { return t -> t; } } 复制代码
方法引用提供了非常有用的语法,可以直接引用已有Java类或对象(实例)的方法或构造器。与lambda联合使用,方法引用可以使语言的构造更紧凑简洁,减少冗余代码。
public static class Car { public Car() { } public Car(String name) { this.name = name; } public static Car create(Supplier<Car> supplier) { return supplier.get(); } public static Car create(String name, Function<String, Car> supplier) { return supplier.apply(name); } 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()); } public static void main(String[] args) { // 构造器引用 Car car = Car.create(Car::new); car = Car.create(() -> new Car()); Car car3 = Car.create("test", Car::new); car3 = Car.create("test", (e) -> new Car(e)); List<Car> cars = Arrays.asList(car); // 静态方法引用 cars.forEach(Car::collide); cars.forEach(e -> Car.collide(e)); // 类的任意对象的方法引用 cars.forEach(Car::repair); cars.forEach(e -> e.repair()); // 对象的实例方法引用 Car police = Car.create(Car::new); cars.forEach(police::follow); cars.forEach(e -> police.follow(e)); } } 复制代码
Java8将方法的参数名添加到了字节码文件中(通过新版的javac的–parameters选项),通过反射API与Parameter.getName()方法来获取(接口和抽象方法也能获取)。
在maven项目中,可以通过maven-compiler-plugin的配置可以将-parameters参数添加到编译器中去
<plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>3.1</version> <configuration> <compilerArgument>-parameters</compilerArgument> <source>1.8</source> <target>1.8</target> </configuration> </plugin> 复制代码
到目前为止,臭名昭著的空指针异常是导致Java应用程序失败的最常见原因。以前,为了解决空指针异常,Google公司著名的Guava项目引入了Optional类,Guava通过使用检查空值的方式来防止代码污染,它鼓励程序员写更干净的代码。受到Google Guava的启发,Optional类已经成为Java 8类库的一部分。
Optional实际上是个容器:它可以保存类型T的值,或者仅仅保存null。Optional提供很多有用的方法,这样我们就不用显式进行空值检测。Optional一般只用作方法的返回值。
public static void main(String[] args) { Optional<String> fullName = Optional.ofNullable(null); System.out.println("Full Name is set? " + fullName.isPresent()); System.out.println("Full Name: " + fullName.orElseGet(() -> "[none]")); System.out.println(fullName.map(s -> "Hey " + s + "!").orElse("Hey Stranger!")); Optional< String > firstName = Optional.of( "Tom" ); System.out.println( "First Name is set? " + firstName.isPresent() ); System.out.println( "First Name: " + firstName.orElseGet( () -> "[none]" ) ); System.out.println( firstName.map( s -> "Hey " + s + "!" ).orElse( "Hey Stranger!" ) ); System.out.println(); } 复制代码
Stream API(java.util.stream) 把真正的函数式编程风格引入到Java中,极大简化了集合框架的处理,可以极大提供Java程序员的生产力,让程序员写出高效率、干净、简洁的代码。
先来学习下stream api的使用:
public class SteamsDemo { private enum Status { OPEN, CLOSED } private static final class Task { private final Status status; private final Integer points; Task(final Status status, final Integer points) { this.status = status; this.points = points; } public Integer getPoints() { return points; } public Status getStatus() { return status; } @Override public String toString() { return String.format("[%s, %d]", status, points); } } public static void main(String[] args) { final Collection<Task> tasks = Arrays.asList( new Task(Status.OPEN, 5), new Task(Status.OPEN, 13), new Task(Status.CLOSED, 8) ); // Calculate total points of all active tasks using sum() final long totalPointsOfOpenTasks = tasks .stream() .filter(task -> task.getStatus() == Status.OPEN) .mapToInt(Task::getPoints) .sum(); System.out.println("Total points: " + totalPointsOfOpenTasks); final Collection<String> result = tasks .stream() // Stream< String > .mapToInt(Task::getPoints) // IntStream .asLongStream() // LongStream .mapToDouble(points -> points / totalPoints) // DoubleStream .boxed() // Stream< Double > .mapToLong(weight -> (long) (weight * 100)) // LongStream .mapToObj(percentage -> percentage + "%") // Stream< String> .collect(Collectors.toList()); // List< String > } } 复制代码
stream另一个有价值的地方是能够原生支持并行处理:
// Calculate total points of all tasks final double totalPoints = tasks .stream() .parallel() .map(Task::getPoints) .reduce(0, Integer::sum); 复制代码
Stream API不仅仅处理Java集合框架。像从文本文件中逐行读取数据这样典型的I/O操作也很适合用Stream API来处理:
final Path path = new File( filename ).toPath(); try( Stream< String > lines = Files.lines( path, StandardCharsets.UTF_8 ) ) { lines.onClose(() -> System.out.println("Done!")).forEach( System.out::println); } 复制代码
Stream(流)是一个来自特定数据源的元素组成的队列,并支持各类计算操作
Stream和以前的Collection操作不同,Stream操作有两个基础的特征:
在 Java 8 中, 集合接口有两个方法来生成流:
java.util.stream.Stream中定义了许多流操作的方法,可分为两类:中间操作和最终操作
中间操作又可以分为无状态的(Stateless)和有状态的(Stateful):
Stream操作分类 | ||
中间操作 (Intermediate operations) |
无状态(Stateless) | filter() map() mapToInt() mapToLong() mapToDouble() flatMap() flatMapToInt() flatMapToLong() flatMapToDouble() peek() |
有状态(Stateful) | distinct() sorted() limit() skip() | |
结束操作 (Terminal operations) |
非短路操作 | forEach() forEachOrdered() toArray() reduce() collect() max() min() count() |
短路操作 (short-circuiting) |
anyMatch() allMatch() noneMatch() findFirst() findAny() |
返回一个新的Stream,其中的元素由原有的Stream元素经由mapper函数转换而来(返回类型可以发生变化,元素个数不会发生变化)
<R> Stream<R> map(Function<? super T, ? extends R> mapper); 复制代码
返回一个新的Stream,其中的元素由原有的Stream元素经由mapper函数转换生成的Stream平铺而来(返回类型可以发生变化,元素个数可以发生变化)
<R> Stream<R> flatMap(Function<? super T, ? extends Stream<? extends R>> mapper); 复制代码
返回一个非Stream类型的对象,其值为对Stream中的所有元素进行规约操作accumulator后生成的
<U> U reduce(U identity, BiFunction<U, ? super T, U> accumulator, BinaryOperator<U> combiner); // 执行过程的伪代码 U result = identity; for (T element : this stream) result = accumulator.apply(result, element) return result; // combiner用于合并多个U(并行执行时) // 示例数据 haha:hehe:heihei nihao:hello:words // map与flatMap的差异 try (Stream<String> lines = Files.lines(path, StandardCharsets.UTF_8)) { List<String> wordsList1 = lines.flatMap(line -> Stream.of(line.split(":"))).collect(Collectors.toList()); List<String[]> wordsList2 = lines.map(line -> line.split(":")).collect(Collectors.toList()); } 复制代码
返回一个非Stream类型的对象,对象由supplier提供,对象中的元素是由对Stream中的所有元素进行规约操作accumulator后生成的
<R> R collect(Supplier<R> supplier, BiConsumer<R, ? super T> accumulator, BiConsumer<R, R> combiner); // 执行过程的伪代码 R result = supplier.get(); for (T element : this stream) accumulator.accept(result, element); return result; // 示例 Stream<String> stringStream = Stream.of("3333", "dddd"); List<String> asList = stringStream.collect(ArrayList::new, ArrayList::add, ArrayList::addAll); String concatStr = stringStream.collect(StringBuilder::new, StringBuilder::append, StringBuilder::append).toString(); 复制代码
通过Collection接口的stream()方法,返回一个流对象(ReferencePipeline.Head),后续每调用一个中间操作都会生成一个流对象(StatelessOp/StatefulOp),并与之前的流对象(管道/stage)连接形成一个整体的管道,最后调用终止操作(TerminalOp),会触发真正的操作,将源数据流经管道,由管道中的各个操作Op处理,最后返回给调用方。
AbstractPipeline管道的基类,提供了Stream接口核心实现
Spliterator 分裂器用于遍历和拆分源数据
Sink下沉接口,继承Consumer,用于访问元素
TerminalOp定义结束操作的行为,继承Sink
Arrays.asList("abc", "defg") .stream() .filter(str -> str.length() <= 3) .map(String::toUpperCase) .forEach(System.out::println); 复制代码