java8特性

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 的另外一大特点是,数据源本身可以是无限的。

使用流时,一般是三个阶段:

Created with Raphaël 2.1.2 数据源 中间操作(Intermediate) 端点操作(Terminal)

从上图就能看出流的操作分为两种类型:

  • 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、 unordered

  • Terminal:
    forEach、 forEachOrdered、 toArray、 reduce、 collect、 min、 max、 count、 anyMatch、 allMatch、 noneMatch、 findFirst、 findAny、 iterator

  • Short-circuiting:
    anyMatch、 allMatch、 noneMatch、 findFirst、 findAny、 limit

其实stream还是有很多细节的,一些应用方法的。大家用到的时候可以具体了解下的。

猜你喜欢

转载自blog.csdn.net/zcl111/article/details/81302183