JavaSE(十六)Java8新特征

接口的非抽象方法

  接口是对功能的说明而不是对功能的实现,所以接口只能包含抽象方法,但从Java8开始,接口却可以包含非抽象方法(默认方法和静态方法)
  接口的默认方法就是用default修饰的普通方法,它包含了函数体,提供默认方法的目的就是为了减少子类实现接口的工作量。Java的一大特点就是单继承,单继承让编程者从复杂的继承关系中解脱出来(复杂的继承关系会让产生Bug的几率大幅度上升),接口如果能够有默认实现,那么又会将编程者扔进复杂的继承关系中。在Java8的核心包中,很多原有的接口被添加了若干个新的功能(特别是集合框架下的接口),为了减少子类重新实现这些接口的工作量,添加了默认方法这一语法。可见,默认方法是Java核心包的开发者为了偷懒而做的妥协,在实际应用中,尽量不要使用默认方法这一语法
  接口的静态方法和普通类的静态方法拥有相同的语法结构,用static修饰且包含了函数体,类的静态方法虽然无法体现出多态,但可以被继承,而对于接口来说,无论是其子接口还是其实现类,静态方法都不能被继承。

Lambda表达式

函数式接口

  有且仅有一个抽象方法的接口(可以有多个非抽象方法)就是函数式接口。可以使用@FunctionalInterface对函数式接口进行标识,该注解会在编译时检查当前接口是否满足函数式接口的条件,函数式接口并不强制要求必须使用该注解进行标识,就像使用@Override来标识重写函数一样。可见,除了可有可无的@FunctionalInterface注解以外,函数式接口并没有添加新的语法,只是对满足特殊条件的接口进行了概念上的新定义
  函数式接口的用法和普通接口的用法完全一样,提出函数式接口的定义主要是为了实现Lambda表达式。在Java中内置了一些比较常用的函数式接口,以便在接收Lambda表达式时,可以方便地利用已存在的,满足条件的函数式接口(满足条件主要指参数类型和个数以及返回类型,而与接口名和方法名无关),如果这些函数式接口都无法满足需求,依然需要自定义函数式接口。Java中部分内置的函数式接口如下:

接口抽象函数说明
Consumer<T>void accept(T t)消费型接口
BiConsumcr<T, U>void accept(T t, U u)
Supplier<T>T get()供给型接口
Function<T, R>R apply(T t)函数型接口
BiFunction<T, U, R>R apply(T t, U u)
UnaryOperator<T>T apply(T t)
BinaryOperator<T>T apply(T t1, T t2)
ToXxxFunction<T>xxx applyAsXxx(T t)xxx可取int、long、double
XxxFunction<R>R apply(xxx value)xxx可取int、long、double
Predicate<T>boolean test(T t)断言型接口

Lambda表达式

  Lambda表达式虽然在语法上表现为一个表达式,但实际上会被编译为一个实现了某函数式接口的匿名内部类,其语法结构为:(参数列表)->{代码},其中,参数列表在类型和个数上必须和函数式接口中唯一抽象方法的参数列表保持一致(参数名可以不同),而这里的代码将会作为该抽象方法的函数体
  Lambda表达式必须要赋值给一个函数式接口类型的变量(成员变量、局部变量或函数参数),其原因有两点:首先,只有赋值给一个函数式接口,才能确定该Lambda表达式被编译为实现了哪个函数式接口的匿名内部类;其次,Lambda表达式不会自动执行函数体中的代码,必须具有进行方法调用的句柄。大多数时候,Lambda表达式被赋值给一个方法的参数(函数式接口类型),这时在方法中回调函数式接口就可以执行Lambda表达式的代码,由于接口中可以存在非抽象方法,所以可以通过非抽象方法调用抽象方法(Lambda表达式只能实现抽象方法)来实现功能的封装(模板方法模式),这时可以通过回调函数式接口的非抽象方法来执行Lambda表达式的代码。
  Lambda表达式的基本语法结构为:(参数列表)->{代码},其中,参数列表的参数类型可以省略,因为可以根据其代表的抽象方法推导出参数类型(省略参数类型通常是编程人员更喜欢的编程方式),如表达式(int a, String b)->{}可以简写为(a, b)->{}。Java还提供了很多其他的语法糖来实现某些特殊的Lambda表达式:

  1. 省略(),如a->{…},代表(a)->{…},有且仅有一个参数时的语法糖
  2. 省略{},如(…)->表达式,有且仅有一条表达式时的语法糖,当表达式为函数且无返回值时,代表(…)->{表达式;},当表达式为其他形式时(如有返回值的函数、普通的运算表达式等),代表(…)->{return 表达式;}

  在Lambda表达式的代码块中,不但可以使用代码块中的变量、参数列表中的形参,还可以使用Lambda表达式所在类中的成员变量,以及Lambda表达式所在位置可见的局部变量(会自动将局部变量的定义修改为final修饰)。

方法引用

  方法引用是Lambda表达式的一种特殊表现形式,通过指向一个已实现的方法来与函数式接口进行绑定,它可以使语言的构造更紧凑简洁,减少冗余代码。方法引用的主要格式如下:

  1. 实例::实例方法名,如instance::fun,等效于(X x, Y y…)->instance.fun(x, y…)
  2. 类名::静态方法名,如MyClass::fun,等效于(X x, Y y…)->MyClass.fun(x, y…)
  3. 类名::实例方法名,如MyClass::fun,等效于(MyClass clazz, X x…)->clazz.fun(x…)
  4. 类名::new,如MyClass::new,等效于(X x, Y y…)->new MyClass(x, y)

  被引用方法的参数列表与其函数式接口方法的参数列表必须具有严格的对应关系。

集合新特征

可迭代对象的遍历

  对于可迭代的类(实现了Iterable接口的类,如集合),可以通过Iterator来进行元素的遍历,而在Java8中,又引入了新的遍历方式,那就是通过Iterable的forEach方法。forEach函数的参数是一个函数式接口,该函数式接口的抽象方法的参数,就是每次被迭代处理的元素,可以用Lambda表达式来对每次迭代的元素进行处理,如iterableObject.forEach(e->{…对本次迭代的元素e进行处理…})。通过Iterator来进行迭代,需要自己手动写for循环语句,这种迭代方式被叫着外部迭代;提供函数并在函数内部进行循环,只需要传递针对各个元素进行处理的处理对象给函数,这种迭代方法叫着内部迭代

public interface Iterable<T> {
    Iterator<T> iterator();
    
    default void forEach(Consumer<? super T> action) {
        for (T t : this) {
            action.accept(t);
        }
    }
}

列表排序

  对于列表的排序,可以使用工具类Collections的静态方法sort,而在Java8中,又提供了对列表进行排序的新方法,那就是List的sort方法。sort方法只接收一个Comparator参数来对元素进行比较(排序本来就是以比较作为基础的),由于Comparator本身是个函数式接口,所以可以用Lambda表达式来对元素进行比较,如listObject.sort((a, b)->{…a与b进行比较,并返回比较结果…})。

public interface List<E> extends Collection<E> {
	default void sort(Comparator<? super E> c) {
        Object[] a = this.toArray();
        Arrays.sort(a, (Comparator) c);
        ListIterator<E> i = this.listIterator();
        for (Object e : a) {
            i.next();
            i.set((E) e);
        }
    }
    ...
}

Stream

  Stream使用一种类似SQL语句的直观方式来提供一种对Java集合进行运算和表达的高阶抽象,这种风格将要处理的元素集合看作一种流,流在管道中传输,并且可以在管道的节点上进行处理,比如筛选,排序,聚合等。
  每个流都绑定着一个数据源(集合、阵列、IO、生成函数等),但它不会改变数据源,只是按照特定的规则对数据进行处理,如同SQL中的select语句。流的处理分为中间处理和最终处理,中间处理会将流进行处理并返回一个新的流(新的流绑定着一个新的数据源),最终处理才是为了得到最后的结果,流的存在只是作为处理数据源的一种工具,所以如果没有最终处理,再多的中间处理都是没有意义的。每一次流操作,应该包含数据源、零次或多次中间处理、一次最终处理,只有在执行最终处理时,流操作才会真正的执行

public interface Stream<T> extends BaseStream<T, Stream<T>> {
    Stream<T> filter(Predicate<? super T> predicate); // 过滤
    Stream<T> sorted(Comparator<? super T> comparator); // 排序
    Stream<T> sorted(); // 对实现了Comparable的元素进行排序
    Stream<T> limit(long maxSize); // 最多保留maxSize个元素
    Stream<T> distinct(); // 根据元素的equal方法去重
    Stream<T> peek(Consumer<? super T> action); // 新流与原流数据完全相同,但在新流中执行最终操作前,会先对每个元素执行action操作,常用于并行流的同步控制
    Stream<T> skip(long n); // 忽略n个元素,由其他元素组成新流
    
    // 将流中的每一个数据都进行特定的处理,然后组成新的流
    <R> Stream<R> map(Function<? super T, ? extends R> mapper);
    IntStream mapToInt(ToIntFunction<? super T> mapper);
    LongStream mapToLong(ToLongFunction<? super T> mapper);
    DoubleStream mapToDouble(ToDoubleFunction<? super T> mapper);
    <R> Stream<R> flatMap(Function<? super T, ? extends Stream<? extends R>> mapper); // 每一个元素进行处理后都能得到一个流,最后把所有流进行合并得到新流,例如已有一个学校的班级流,mapper就是根据班级流的每一个班级得到该班级的学生流,那么该方法最后得到的是学校的学生流
    IntStream flatMapToInt(Function<? super T, ? extends IntStream> mapper);
    LongStream flatMapToLong(Function<? super T, ? extends LongStream> mapper);
    DoubleStream flatMapToDouble(Function<? super T, ? extends DoubleStream> mapper);
    
    boolean allMatch(Predicate<? super T> predicate); // 所有元素都满足条件时返回true,否则false
    boolean anyMatch(Predicate<? super T> predicate); // 只要有元素满足条件就返回true,否则false
    boolean noneMatch(Predicate<? super T> predicate); // 没有任何元素满足条件时返回true,否则false
    
    // 对流中的各个元素进行处理
    void forEach(Consumer<? super T> action); // 在并行流中无法保证消费元素的顺序
    void forEachOrdered(Consumer<? super T> action); // 在并行流中也能保证消费元素的顺序
    
    // 将流处理为一个最终数据,reduce适用于以返回值的方式得到处理结果,collect适用于以参数的方式得到处理结果
    T reduce(T identity, BinaryOperator<T> accumulator); // accumulator的apply方法返回处理结果,apply方法参数分别为上一次处理的结果和新的迭代元素,identity为上一次处理结果的初始值(在迭代第一个元素时使用)
    Optional<T> reduce(BinaryOperator<T> accumulator); // 第一次迭代的结果就是第一个元素,从第二个元素开始使用accumulator
    <U> U reduce(U identity, BiFunction<U, ? super T, U> accumulator, BinaryOperator<U> combiner); // 对于并行流和串行流,该方法展现出完全不同的行为
    				// 对于串行流,第三个参数被忽略,计算方式与两个参数的reduce方法相似,只是处理的结果类型不一定与原流的元素类型相等
    				// 对于并行流,会先使用accumulator对每一个元素进行一次处理得到新的流(必然与原流个数相等),然后使用combiner消费新的流中的任意两个元素并将结果加入新流,直到新流中只有一个元素为止
    <R> R collect(Supplier<R> supplier, BiConsumer<R, ? super T> accumulator, BiConsumer<R, R> combiner); // 对于并行流和串行流,该方法展现出完全不同的行为
    				// 对于串行流,第三个参数被忽略,supplier只会产生一个容器,accumulator处理的第一个参数就是supplier产生的唯一容器
    				// 对于并行流,先对每一个元素都调用accumulator处理(方法的第一个参数每次都由supplier新产生,最后将产生的supplier组成一个新的流),然后使用combiner消费新的流中的任意两个元素(第一个元素会保留在新流中),直到新流中只有一个元素
    <R, A> R collect(Collector<? super T, A, R> collector); // collector是对具有三个参数的collect方法的参数的封装,Collectors提供了许多常用的collector,比如将Stream转换为list
    
    long count(); // 统计流中元素个数
    Optional<T> findFirst(); // 返回第一个元素,如果是无序流,返回的可能是任一元素
    Optional<T> findAny(); // 返回任一元素
    Optional<T> min(Comparator<? super T> comparator); // 返回最小元素
    Optional<T> max(Comparator<? super T> comparator); // 返回最大元素
    
    // 创建Stream并关联数据源,我们常用Collection的stream方法来创建串行流或parallelStream方法来创建并行流(流关联的数据源就是当前Collection)
    public static<T> Builder<T> builder(); // 通过builder的方式创建流
    public static<T> Stream<T> empty(); // 绑定的数据源为空数据
    public static<T> Stream<T> of(T... values); // 绑定的数据源为values
    public static<T> Stream<T> generate(Supplier<T> s); // 创建一个流,每次获取元素时通过提供者生成元素
    public static <T> Stream<T> concat(Stream<? extends T> a, Stream<? extends T> b); // 将两个流合并为一个流
}

public interface BaseStream<T, S extends BaseStream<T, S>> extends AutoCloseable {
    Iterator<T> iterator(); // 返回对流的迭代器
    boolean isParallel(); // 是否是并行流
    S sequential(); // 返回等效的串行流,当前流是串行流时返回本身
    S parallel(); // 返回等效的并行流,当前流是并行流时返回本身
    S unordered(); // 返回等效的无序流
    S onClose(Runnable closeHandler); // 关闭流时的额外操作
    void close(); // 关闭流
}

  几个将集合进行过滤的例子,帮助理解流的各个方法:

// 最常用的方法
public static  List<String> normal(Stream<String> stream, String start) {
    return stream.filter(e->e.startsWith(start)).collect(Collectors.toList());
}

public static List<String> reduce(Stream<String> stream, String start) {
    if (stream.isParallel()) {
        return stream.reduce(new ArrayList(),
                (x, e)->{
                    ArrayList<String> list = new ArrayList<>();
                    if (e.startsWith(start)) list.add(e);
                    return list;
                },
                (x1, x2)-> {
                    x1.addAll(x2);
                    return x1;
                }
        );
    }

    return stream.reduce(
            new ArrayList<String>(),
            (list, e)->{
                if (e.startsWith(start))
                    list.add(e);
                return list;
             },
            (x, y)->null
    );
}

public static List<String> collect(Stream<String> stream, String start) {
    if (stream.isParallel()) {
        return stream.collect(ArrayList::new,
                    (list, e)->{
                        if (e.startsWith(start))
                            list.add(e);
                    },
                    (list1, list2)->{
                        list1.addAll(list2);
                    }
                );
    }

    return stream.collect(ArrayList::new,
                (list, e)->{
                    if (e.startsWith(start))
                        list.add(e);
                },
                (x, y)->{}
            );
}

Optional

  Optional对象是一个可以存放单个对象的容器,它可能存放了一个对象,也可能没有存放任何对象(存放的null值被当做没有存放对象)

public final class Optional<T> {
    public static<T> Optional<T> empty(); // 创建一个空的Optional
    public static <T> Optional<T> of(T value); // 创建一个存放了value的Optional,value不能为null,否则抛出异常
    public static <T> Optional<T> ofNullable(T value); // value为null时等同于empty(),否则等同于of(value)
    public boolean isPresent(); // 是否存放了对象
    public T get(); // 返回存放的对象,若未存放对象抛出异常
    public T orElse(T other); // 返回存放的对象,若未存放对象就返回other
    public T orElseGet(Supplier<? extends T> other); // 返回存放的对象,若未存放对象就由other生成一个对象返回
    public <X extends Throwable> T orElseThrow(Supplier<? extends X> exceptionSupplier) throws X; // 返回存放的对象,若未存放对象则抛出由exceptionSupplier生成的异常
    public Optional<T> filter(Predicate<? super T> predicate); // 如果存放了对象且对象满足predicate则返回该Optional,否则返回空Optional对象
    public void ifPresent(Consumer<? super T> consumer); // 如果存放了对象就用consumer来处理,否则不做任何操作
    public<U> Optional<U> map(Function<? super T, ? extends U> mapper); // 如果存放了对象则对存放的对象进行处理得到一个新的对象,并将对象存放到一个Optional返回,否则返回空Optional
    public<U> Optional<U> flatMap(Function<? super T, Optional<U>> mapper);
}

新的日期与时间API

  在Java8之前,通过java.util.Date与Calendar来处理日期和时间,在Java8中,对日期和时间模块进行了大幅度的重构,主要包含了以下新的类:

  • 时间戳Instant、时钟Clock,时间戳就是时刻,同一时刻在不同的时区表现为不同的时间,而时钟主要用于获取当前的时间戳
  • 时区时间ZonedDateTime,某一时刻在某一时区表现出来的时间,是时间戳的一种表现形式
  • 日期LocalDate、时间LocalTime、时间与日期LocalDateTime,用于记录时间,与时区无关,如生日,上班时间等
  • 时区ZoneId、相对于格林尼治时间的时间偏差ZoneOffset
  • 时间间隔Duration、时间段Peroid

其他新功能

  • 添加了类Base64来完成Base64格式的加解密,详见章节《加密与安全》。
  • 支持重复注解,详见章节《反射与注解》。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值