1. Lambda表达式:
Lambda表达式(也称为闭包)是Java 8中最大和最令人期待的语言改变。它允许我们将函数当成参数传递给某个方法,或者把代码本身当作数据处理。
格式如下:
(parameters) -> expression
或
(parameters) ->{ statements; }
- 可选类型声明:不需要声明参数类型,编译器可以统一识别参数值。
- 可选的参数圆括号:一个参数无需定义圆括号,但多个参数需要定义圆括号。
- 可选的大括号:如果主体包含了一个语句,就不需要使用大括号。
- 可选的返回关键字:如果主体只有一个表达式返回值则编译器会自动返回值,大括号需要指定明表达式返回了一个数值。
举例:
int a1 = 12;
Arrays.asList("23232", "3243", "3243").forEach(item -> System.out.println("item: " + item + "; " + a1));
Consumer<String> consumer = (String s) -> {System.out.println(s);};
Comparator<String> comparator = (s1, s2) -> {
int sl1 = 0;
char[] sc1 = s1.toCharArray();
for(char c: sc1) {
sl1 += c;
}
int sl2 = 0;
char[] sc2 = s2.toCharArray();
for(char c: sc2) {
sl2 += c;
}
System.out.println("sl1="+sl1+"; sl2="+sl2);
return sl1 > sl2? 1: (sl1 == sl2? 0: -1);
};
System.out.println(comparator.compare("23dewe", "re34"));
注意:第二/三种列子是跟函数式接口结合的写法。这个下面会说到。
Lambda表达式可以引用类成员和局部变量(会将这些变量隐式得转换成final的)。所以上面说a1前面可添可不添final限制的。 不过要注意的时,因为会隐形变成final,所以后面不能再对a1进行修改,会报错。
int a1 = 12;
Arrays.asList("23232", "3243", "3243").forEach(item -> System.out.println("item: " + item + "; " + a1));
a1 = 34;/** 这个会报错的。因为a1已经隐形变成final了 **/
在 Lambda 表达式当中不允许声明一个与局部变量同名的参数或者局部变量。
String a = "";
Comparator<String> comparator = (a, b) -> Integer.compare(a.length(), b.length()); //编译会出错
2. 函数式接口:
函数式接口(Functional Interface)就是一个有且仅有一个抽象方法,但是可以有多个非抽象方法的接口,这样的接口可以隐式转换为Lambda表达式。
但是这样定义的函数式接口非常脆弱:只要某个开发者在该接口中添加一个函数,则该接口就不再是函数式接口进而导致编译失败。为了克服这种代码层面的脆弱性,并显式说明某个接口是函数式接口,Java 8 提供了一个特殊的注解@FunctionalInterface,举个简单的函数式接口的定义:
@FunctionalInterface
public interface MyFunctionalInterfaceApi {
String toShow(Object obj);
}
注意:默认方法和静态方法不会破坏函数式接口的定义
@FunctionalInterface
public interface MyFunctionalInterfaceApi {
String toShow(Object obj);
static String getInfo(Object obj) {
return obj == null? "NULL": obj.toString();
}
default int getLength(Object obj) {
return obj == null? 0: obj.toString().length();
}
}
JDK 1.8之前已有的函数式接口:
- java.lang.Runnable
- java.util.concurrent.Callable
- java.util.Comparator
- java.io.FileFilter
- java.nio.file.PathMatcher
- java.beans.PropertyChangeListener
- java.awt.event.ActionListener
- javax.swing.event.ChangeListener
JDK 1.8 新增加的函数接口:
-java.util.function目录下
目录下面很多,主要的有几大类:
接口名称 | 抽象方法 | 描述 |
---|---|---|
Consumer<T> | void accept(T t); | 代表了接受一个输入参数并且无返回的操作 |
Supplier<T> | T get(); | 无参数,返回一个结果。 |
Predicate<T> | boolean test(T t); | 接受一个输入参数,返回一个布尔值结果。 |
Function<T,R> | R apply(T t); | 接受一个输入参数,返回一个结果。 |
其余的 IntConsumer、IntFcuntion、IntPredicate、IntSupplier等其实就是运用自动装箱和拆箱的具体应用,参数变成基本类型而已,省去了箱操作的消耗和提供性能的。
3. 接口的默认方法和静态方法:
java 8使用两个新概念扩展了接口的含义:默认方法和静态方法。
默认方法使得开发者可以在 不破坏二进制兼容性的前提下,往现存接口中添加新的方法,即不强制那些实现了该接口的类也同时实现这个新加的方法。
- 默认方法和抽象方法之间的区别在于抽象方法需要实现,而默认方法不需要。接口提供的默认方法可以被接口的实现类继承或者覆写。由于JVM上的默认方法的实现在字节码层面提供了支持,因此效率非常高。默认方法允许在不打破现有继承体系的基础上改进接口。该特性在官方库中的应用是:给java.util.Collection接口添加新方法,如stream()、parallelStream()、forEach()和removeIf()等等。
Java 8带来的另一个有趣的特性是在接口中可以定义静态方法:
@FunctionalInterface
public interface MyFunctionalInterfaceApi {
String toShow(Object obj);
static String getInfo(Object obj) {
return obj == null? "NULL": obj.toString();
}
default int getLength(Object obj) {
return obj == null? 0: obj.toString().length();
}
}
String str = "main02";
System.out.println(api01.toShow(str) + " ******** " + api01.getLength(str));
4. 方法引用:
方法引用使得开发者可以直接引用现存的方法、Java类的构造方法或者实例对象。方法引用和Lambda表达式配合使用,使得java类的构造方法看起来紧凑而简洁,没有很多复杂的模板代码。方法引用使用一对冒号 :: 。
public static class Car {
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() );
}
}
- 构造器引用:它的语法是Class::new,或者更一般的Class< T >::new实例如下
final Car car = Car.create( Car::new );
final List< Car > cars = Arrays.asList( car );
- 静态方法引用:它的语法是Class::static_method,实例如下:
cars.forEach( Car::collide );
- 特定类的任意对象的方法引用:它的语法是Class::method实例如下:
cars.forEach( Car::repair );
- 特定对象的方法引用:它的语法是instance::method实例如下:
final Car police = Car.create( Car::new );
cars.forEach( police::follow );
5. Optional 类:
Optional 类是一个可以为null的容器对象。如果值存在则isPresent()方法会返回true,调用get()方法会返回该对象。
Optional 是个容器:它可以保存类型T的值,或者仅仅保存null。Optional提供很多有用的方法,这样我们就不用显式进行空值检测。
Optional<String> op1 = Optional.ofNullable(null);
Optional<String> op2 = Optional.ofNullable("dfsfdfd");
System.out.println(op1.orElseGet(() -> "df1232"));
op2.ifPresent((str) -> System.out.println("1:"+str));
op2.filter((str) -> str.matches(".*sfd.*")).ifPresent((str) -> System.out.println("2:"+str));
op2.filter((str) -> str.matches(".*122.*")).ifPresent((str) -> System.out.println("3:"+str));
一些基本方法:
方法 | 描述 |
---|---|
static <T> Optional<T> empty() | 返回空的 Optional 实例。 |
boolean equals(Object obj) | 判断其他对象是否等于 Optional。 |
Optional<T> filter(Predicate<? super <T> predicate) | 如果值存在,并且这个值匹配给定的 predicate,返回一个Optional用以描述这个值,否则返回一个空的Optional。 |
<U> Optional<U> flatMap(Function<? super T,Optional<U>> mapper) | 如果值存在,返回基于Optional包含的映射方法的值,否则返回一个空的Optional(flatMap跟map对比,是把多维的数据整合成一维的) |
T get() | 如果在这个Optional中包含这个值,返回值,否则抛出异常:NoSuchElementException |
int hashCode() | 返回存在值的哈希码,如果值不存在 返回 0。 |
void ifPresent(Consumer<? super T> consumer) | 如果值存在则使用该值调用 consumer , 否则不做任何事情。 |
boolean isPresent() | 如果值存在则方法会返回true,否则返回 false。 |
<U>Optional<U> map(Function<? super T,? extends U> mapper) | 如果存在该值,提供的映射方法,如果返回非null,返回一个Optional描述结果。 |
static <T> Optional<T> of(T value) | 返回一个指定非null值的Optional。T不能为NULL |
static <T> Optional<T> ofNullable(T value) | 如果为非空,返回 Optional 描述的指定值,否则返回空的 Optional。T可能为NULL也可能不为NULL |
T orElse(T other) | 如果存在该值,返回值, 否则返回 other。 |
T orElseGet(Supplier<? extends T> other) | 如果存在该值,返回值, 否则触发 other,并返回 other 调用的结果。(与orElse的区别是orElseGet为NULL时,才会去执行other方法,生成T对象;而orElse在运行的时候就要有T对象了) |
<X extends Throwable> T orElseThrow(Supplier<? extends X> exceptionSupplier) | 如果存在该值,返回包含的值,否则抛出由 Supplier 继承的异常 |
String toString() | 返回一个Optional的非空字符串,用来调试 |
6. Stream:
Java 8 中的 Stream 是对集合(Collection)对象功能的增强,它专注于对集合对象进行各种非常便利、高效的聚合操作(aggregate operation),或者大批量数据操作 (bulk data operation)。它提供串行和并行两种模式进行汇聚操作,并发模式能够充分利用多核处理器的优势,使用 fork/join 并行方式来拆分任务和加速处理过程。
Stream 就如同一个迭代器(Iterator),单向,不可往复,数据只能遍历一次,遍历过一次后即用尽了,就好比流水从面前流过,一去不复返。
Stream 的另外一大特点是,数据源本身可以是无限的。
使用流时,一般是三个阶段:
从上图就能看出流的操作分为两种类型:
- Intermediate:一个流可以后面跟随零个或多个 intermediate操作。其目的主要是打开流,做出某种程度的数据映射/过滤,然后返回一个新的流,交给下一个操作使用。这类操作都是惰性化的(lazy),就是说,仅仅调用到这类方法,并没有真正开始流的遍历。
- Terminal:一个流只能有一个 terminal操作,当这个操作执行后,流就被使用“光”了,无法再被操作。所以这必定是流的最后一个操作。Terminal操作的执行,才会真正开始流的遍历,并且会生成一个结果,或者一个 side effect。
注意:一个流是终端操作执行时才会真正执行。中间过程并不会真正执行的。
Stream.of('a', 'b', 'e', '5', '7', 'k')
.map(c-> {int cc = Integer.valueOf(c); System.out.println(c + " to " + cc); return cc;})
.peek(System.out::println)
.filter(num -> num > 5)
.sorted()
.forEach(System.out::println);
如图,流的操作可以链接起来。在对于一个 Stream 进行多次转换操作 (Intermediate 操作),每次都对 Stream 的每个元素进行转换,而且是执行多次,这样时间复杂度就是 N(转换次数)个 for 循环里把所有操作都做掉的总和吗?其实不是这样的,转换操作都是 lazy 的,多个转换操作只会在 Terminal 操作的时候融合起来,一次循环完成。我们可以这样简单的理解,Stream 里有个操作函数的集合,每次转换操作就是把转换函数放入这个集合中,在 Terminal 操作的时候循环 Stream 对应的集合,然后对每个元素执行所有的函数。
还有一种操作被称为 short-circuiting。用以指:
- 对于一个 intermediate 操作,如果它接受的是一个无限大(infinite/unbounded)的
Stream,但返回一个有限的新 Stream。 - 对于一个 terminal 操作,如果它接受的是一个无限大的 Stream,但能在有限的时间计算出结果。
当操作一个无限大的 Stream,而又希望在有限时间内完成操作,则在管道内拥有一个 short-circuiting 操作是必要非充分条件。
创建
Collection.stream()、Collection.parallelStream()、Arrays.stream(T array) or Stream.of()、 java.io.BufferedReader.lines()、 java.util.stream.IntStream.range()、 java.nio.file.Files.walk()、java.util.Spliterator、 Random.ints()、BitSet.stream()、Pattern.splitAsStream(java.lang.CharSequence)、JarFile.stream()Intermediate:
map (mapToInt, flatMap 等)、 filter、 distinct、 sorted、 peek、 limit、 skip、 parallel、 sequential、 unorderedTerminal:
forEach、 forEachOrdered、 toArray、 reduce、 collect、 min、 max、 count、 anyMatch、 allMatch、 noneMatch、 findFirst、 findAny、 iteratorShort-circuiting:
anyMatch、 allMatch、 noneMatch、 findFirst、 findAny、 limit
其实stream还是有很多细节的,一些应用方法的。大家用到的时候可以具体了解下的。