java8

Java8——快速入门手册(学习笔记)

 

目录

Java8特性学习笔记
Lambda表达式
Lambda语法
Lambda表达式的特征
Lambda表达式例子
方法引用
方法引用的种类
方法引用的例子
默认方法
默认方法(defalut)
静态方法(static)
函数接口
java.util.function.Predicate
java.util.Comparator
java.util.function.Supplier
java.util.function.Consumer
Function
说明
主要方法
方法详解
Stream
中间操做
最终操做
Optional API
Date Time API

github博文传送门php

Java8特性学习笔记

  Java8中新增了许多的新特性,在这里本人研究学习了几个较为经常使用的特性,在这里与你们进行分享。(这里推荐深刻理解Java 8用于理解基础知识)本文分为如下几个章节:html

  • Lambda 表达式
  • 方法引用
  • 默认方法
  • 函数接口
  • Function
  • Stream
  • Optional API
  • Date Time API

Lambda表达式

Lambda 表达式,也可称为闭包。Lambda 容许把函数做为一个方法的参数(函数做为参数传递进方法中)。使用 Lambda 表达式可使代码变的更加简洁紧凑。 Lambda表达式能够替代之前普遍使用的内部匿名类,各类回调,好比事件响应器、传入Thread类的Runnable等。java

Lambda语法

lambda 表达式的语法格式以下:git

(parameters) -> expression  
或  
(parameters) ->{ statements; }

Lambda表达式的特征

  • 类型声明(可选):能够不须要声明参数类型,编译器会识别参数值。
  • 参数圆括号(可选):在单个参数时能够不使用括号,多个参数时必须使用。
  • 大括号和return关键字(可选):若是只有一个表达式,则能够省略大括号和return关键字,编译器会自动的返回值;相对的,在使用大括号的状况下,则必须指明返回值。

Lambda表达式例子

这里以经常使用的list排序功能为例:github

private static List<People> peopleList = new ArrayList<People>(); { peopleList.add(new People("a",17)); peopleList.add(new People("b",16)); peopleList.add(new People("c",19)); peopleList.add(new People("d",15)); } @Test public void testLambda(){ System.out.println("排序前:"+peopleList); //第一种,传统匿名Compartor接口排序 Collections.sort(peopleList, new Comparator<People>() { @Override public int compare(People o1, People o2) { return o1.getAge().compareTo(o2.getAge()); } }); System.out.println("匿名接口方法——排序后:"+peopleList); //第二种,使用Lambda表达式来代替匿名接口方法 //1.声明式,不使用大括号,只能够写单条语句 Collections.sort(peopleList,(People a,People b)->a.getAge().compareTo(b.getAge())); System.out.println("Lambda表达式一、排序:"+peopleList);; //2.不声明式,使用大括号,能够写多条语句 Collections.sort(peopleList,(a,b)->{ System.out.print("——————————————"); return a.getAge().compareTo(b.getAge()); }); System.out.println(); System.out.println("Lambda表达式二、排序:"+peopleList); //第三种,使用Lambda表达式调用类的静态方法 Collections.sort(peopleList,(a,b)->People.sortByName(a,b)); System.out.println("Lambda表达式调用静态方法:"+peopleList); //第四种,使用Lambda表达式调用类的实例方法 Collections.sort(peopleList,(a,b)->new People().sortByAge(a,b)); System.out.println("Lambda表达式调用实例方法:"+peopleList); }

对应的运行结果:

注意:在Lambda表达式中只能对final的对象进行操做,声明的对象也为final)express

有的朋友应该已经观察到了,Lambda 表达式与C中的函数指针,JavaScript的匿名function均有些类似。其实,Lambda表达式本质上是一个匿名的方法,只不过它的目标类型必须是“函数接口(functional interface)”,这是Java8引入的新概念,在接下来会进行更加详细的介绍。编程

方法引用

在一些Lambda中可能只是单纯的调用方法,好比前例中的3、四,在这种状况下,就可使用方法引用的方式来提升可读性。api

方法引用的种类

  • 类静态方法引用
    Class::staticMethodName
  • 某个对象的方法引用
    instance::instanceMethodName
  • 特定类的任意对象的方法引用:
    Class::method
  • 构造方法引用:
    Class::new

方法引用的例子

@Test public void testMethodReference() { //第一种,引用类的静态方法 Collections.sort(peopleList, People::sortByName); System.out.println("引用类的静态方法:" + peopleList); //第二种,引用类的实例方法 Collections.sort(peopleList, new People()::sortByAge); System.out.println("引用类的实例方法:" + peopleList); //第三种,特定类的方法调用() Integer[] a = new Integer[]{3, 1, 2, 4, 6, 5}; Arrays.sort(a, Integer::compare); System.out.println("特定类的方法引用:" + Arrays.toString(a)); //第四种,引用类的构造器 Car car = Car.create(Car::new); System.out.println("引用类的构造器:" + car); }
public static Car create(Supplier<Car> supplier){ return supplier.get(); }

默认方法

在Java8以前的时代,为已存在接口增长一个通用的实现是十分困难的,接口一旦发布以后就等于定型,若是这时在接口内增长一个方法,那么就会破坏全部实现接口的对象。
默认方法(以前被称为 虚拟扩展方法 或 守护方法)的目标便是解决这个问题,使得接口在发布以后仍能被逐步演化。数组

默认方法(defalut)

public interface vehicle { default void print(){ System.out.println("我是一辆车!"); } }

静态方法(static)

public interface vehicle { static void blowHorn() { System.out.println("按喇叭!!!"); } }

注:静态方法与默认方法都可以有多个,默认方法能够被覆盖。promise

函数接口

“函数接口(functional interface)”,就是除去默认方法以及继承的抽象方法,只有显式声明一个抽象方法的接口。它使用@FunctionalInterface注解在类上进行标注,也能够省略,Java会自动识别。接下来介绍一些常见的函数接口:

java.util.function.Predicate

该接口包含方法boolean test(T t),该接口通常用于条件的检测,内部包含三个默认方法:and、or、negate、,即与或非,用于各式的条件判断。例:

Predicate<Integer> predicate = x -> x > 3; predicate.test(10);//true predicate.negate().test(10);//false predicate.or(x -> x < 1).and(x -> x > -1).negate().test(-1);//true

注意:在这里与或非的判断顺序是从左到右的,调用的顺序会影响结果。

java.util.Comparator

Comparator是Java中的经典接口,在排序中较为经常使用。Java8在此之上添加了一些新的默认方法,来丰富该接口的功能。例:

Integer[] a = new Integer[]{3, 1, 2, 4, 6, 5}; Comparator<Integer> comparator = Integer::compare; Arrays.sort(a, comparator); System.out.println("升序:" + Arrays.toString(a)); Arrays.sort(a,comparator.reversed()); System.out.println("降序:"+Arrays.toString(a));

结果

升序:[1, 2, 3, 4, 5, 6] 降序:[6, 5, 4, 3, 2, 1]

java.util.function.Supplier

该类只包含方法:
T get();
Supplier接口是在1.8中新出现的函数接口,用于支持函数式编程。它用于返回一个任意泛型的实例对象,与工厂的功能相似。

java.util.function.Consumer

该接口表示一个接受单个输入参数而且没有返回值的操做。不像其余函数式接口,Consumer接口指望执行修改内容的操做。例如 ,咱们须要一个批量修改People的方法,利用Predicate和Consumer就能够这么写
在People内增长updateMany方法:

public static List updateMany(List<People> peopleList, Predicate<People> predicate, Consumer<People> consumer) { for (int i = 0; i < peopleList.size(); i++) { if (predicate.test(peopleList.get(i))) { consumer.accept(peopleList.get(i)); } } return peopleList; }

调用:

//批量修改,将age<18的对象的age改成18 People.updateMany(peopleList, p -> p.getAge() < 18, p -> p.setAge(18)); System.out.println("修改后的结果:" + peopleList);

经过这种方式,能够将内部的判断逻辑与修改代码放至外部调用,而将for、if等语句封装至内部,提升代码的可读性。

其余的还有一些函数接口,如Runnable,InvocationHandler等,在这里就不阐述了。有兴趣的你们能够自行查询资料。Stream、Function、Optional也是函数接口,将在下面进行详细介绍。

Function

说明

Java8提供的java.util.function包的核心函数接口有4个。

  • 函数型T ->R,完成参数类型T向结果类型R的转换和数据处理。核心函数接口Function
  • 判断型T ->boolean,核心函数接口Predicate
  • 消费型T ->void,核心函数接口Consumer
  • 供给型void->T,核心函数接口Supplier

Function接口是为Java8提供了函数式编程的基础,apply方法与Consumer的accept方法功能相似,可是提供了返回及类型转换的可能,功能更增强大;再经过andThen与compose方法可使Function组成Function功能链,进行多级数据处理及转换。

主要方法

  • R apply(T t) – 将Function对象应用到输入的参数上,而后返回计算结果。

  • default Function<T,V> andThen(Function<? super R,? extends V> after) 返回一个先执行当前函数对象apply方法再执行after函数对象apply方法的函数对象。

  • default Function<T,V> compose(Function<? super V,? extends T> before)返回一个先执行before函数对象apply方法再执行当前函数对象apply方法的函数对象。

  • static Function<T,T> identity() 返回一个执行了apply()方法以后只会返回输入参数的函数对象。

方法详解

apply:
R apply(T t); 

接收类型:T
返回类型:R
类型转换:T→R

Function接口的核心方法,能够执行任意的操做,且具备返回值。接收一个T类型的对象,在通过处理后,返回一个R类型的对象。主要功能为类型转换及数据处理。

compose:
default <V> Function<V, R> compose(Function<? super V, ? extends T> before) { Objects.requireNonNull(before); return (V v) -> apply(before.apply(v)); }

接收类型:Function<? super V, ? extends T>
返回类型:Function<V, R>
类型转换:(V+)→(T-)→T→R
apply执行顺序:before→this

此处“V+”指代“? super V”,表示包含V在内的V的任意父类;"T-"指代“? extends T”,表示包含T在内的T的任意子类。compose方法返回一个Function<V,R>,这个Function先执行before的apply方法,将V+类型的数据转换为T-类型,再将T-做为参数传递给this的apply方法,将T类型转换为R类型。
经过compose方法,能够在某个Function执行以前插入一个Function执行。因为返回类型依旧为Function,能够重复调用compose方法造成方法链。

andThen:
default <V> Function<T, V> andThen(Function<? super R, ? extends V> after) { Objects.requireNonNull(after); return (T t) -> after.apply(apply(t)); }

接收类型:Function<? super R, ? extends V>
返回类型:Function<T, V>
类型转换:T→R→(R+)→(V-)
apply执行顺序:this→after

此处“R+”指代“? super R”,表示包含R在内的R的任意父类;"V-"指代“? extends V”,表示包含V在内的V的任意子类。andThen方法返回一个Function<T,V>,这个Function先执行this的apply方法,将T类型的数据转换为R类型,再将R做为参数传递给after的apply方法,将R+类型转换为V-类型。
经过andThen方法,能够在某个Function执行以后插入一个Function执行。因为返回类型依旧为Function,能够重复调用andThen方法造成方法链。

identity:
static <T> Function<T, T> identity() { return t -> t; }

接收类型:无
返回类型:Function<T, T>
类型转换:T→T

该方法的说明是:返回一个函数,它老是返回输入参数。调用该方法能够获得一个返回输入参数的Funtion,这个Function就能够单纯的用来作数据处理,而不用类型转换。

Stream

Java8中提供了Stream API,即流式处理。能够经过将List、Set、Array等对象转换成流进行操做。Stream内的流操做分为两种:中间操做和最终操做,中间操做会返回一个全新的Stream对象,意味着你的操做不会影响最初的流;最终操做会将流进行转换或者操做,返回非Stream的对象。Stream能够替代传统的循环操做,从线程上区别,Stream分为串行(Stream)和并行(parallelStream),关于Stream的性能分析能够查看这篇文章《Stream性能分析》。下面来看下Strea内的一些方法:

中间操做

  • distinct
    java Stream<T> distinct();
    去除Stream中重复的对象,并返回一个流。(使用对象的equals方法)

  • skip
    java Stream<T> skip(long n);
    跳过Stream中的前n个对象,将其余对象返回一个Stream。若是n超过了Stream中对象的个数,则会返回一个空的Stream。

  • limit
    java Stream<T> limit(long maxSize);
    截取Stream的前maxSize个对象,并造成一个新Stream。

  • filter
    java Stream<T> filter(Predicate<? super T> predicate);
    根据给定的predicate来过滤对象,返回知足条件的对象构成的Stream。

  • map

    <R> Stream<R> map(Function<? super T, ? extends R> mapper);

    经过给定的mapper,将T类型的流转换为R类型的Stream。

  • flatMap
    java <R> Stream<R> flatMap(Function<? super T, ? extends Stream<? extends R>> mapper);
    flatMap也是将Stream进行转换,flatMap与map的区别在于 flatMap是将一个Stream中的每一个值都转成一个个Stream,而后再将这些流扁平化成为一个Stream。
    例(转自:Java8 新特性之流式数据处理):
    假设咱们有一个字符串数组String[] strs = {"java8", "is", "easy", "to", "use"};,咱们但愿输出构成这一数组的全部非重复字符,那么咱们可能首先会想到以下实现:
    java List<String[]> distinctStrs = Arrays.stream(strs) .map(str -> str.split("")) // 映射成为Stream<String[]> .distinct() .collect(Collectors.toList());
    在执行map操做之后,咱们获得是一个包含多个字符串(构成一个字符串的字符数组)的流,此时执行distinct操做是基于在这些字符串数组之间的对比,因此达不到咱们但愿的目的,此时的输出为:
    [j, a, v, a, 8] [i, s] [e, a, s, y] [t, o] [u, s, e]
    distinct只有对于一个包含多个字符的流进行操做才能达到咱们的目的,即对Stream进行操做。此时flatMap就能够达到咱们的目的:
    java List<String> distinctStrs = Arrays.stream(strs) .map(str -> str.split("")) // 映射成为Stream<String[]> .flatMap(Arrays::stream) // 扁平化为Stream<String> .distinct() .collect(Collectors.toList());
    flatMap将由map映射获得的Stream<String[]>,转换成由各个字符串数组映射成的流Stream,再将这些小的流扁平化成为一个由全部字符串构成的大流Steam,从而可以达到咱们的目的。

  • sorted
    java Stream<T> sorted(); Stream<T> sorted(Comparator<? super T> comparator);
    sorted方法能够对Stream进行排序。排序的对象必须实现Comparable,若是没实现会抛出ClassCastException;不提供comparator时,则会调用compareTo方法。

  • peek
    java Stream<T> peek(Consumer<? super T> action);
    对流中的每一个对象执行提供的action操做。
    在Stack中,peek用于查看一个对象。在流中也是同样,用于在流循环时,根据给定的action进行查看对象。虽然能够进行元素修改操做,但不建议。

  • 综合例:
    java Integer[] a = new Integer[]{3, 1, 2, 5, 11, 4, 6, 5, 3, 1}; List<Integer> aList = Arrays.stream(a) .distinct() .skip(1) .filter((e) -> e < 6) .peek(e -> System.out.println("循环1次")) .limit(4) .sorted() .collect(Collectors.toList()); System.out.println(aList);
    输出:
    循环1次 循环1次 循环1次 循环1次 [1, 2, 4, 5]

    最终操做

  • 聚合
    • max & min
    Optional<T> min(Comparator<? super T> comparator); Optional<T> max(Comparator<? super T> comparator);

    根据给定的comparator返回Stream中的max或min。

    • count
    long count();

    返回Stream中对象的个数。

  • 匹配
    • anyMatch & allMatch & noneMatch
    boolean anyMatch(Predicate<? super T> predicate); boolean allMatch(Predicate<? super T> predicate); boolean noneMatch(Predicate<? super T> predicate);

    根据给定的predicate判断Stream是否匹配条件。

    • collect
    <R, A> R collect(Collector<? super T, A, R> collector);

    根据给定的collector对Stream中的元素进行操做,返回复杂数据结构的对象。用于将Stream中的对象转换成咱们想要的结构,如list、map、set等。
    前例中就使用collect(Collectors.toList())将Stream中的对象转换成List。

    • reduce
    Optional<T> reduce(BinaryOperator<T> accumulator); T reduce(T identity, BinaryOperator<T> accumulator);

    若是咱们不知但愿单纯的返回List这样的类型,而是但愿将整个Stream通过一些操做后,规约成一个对象返回,就能够用到规约操做。reduce方法有两个参数,其中accumulator表明着规约的操做,即用何种的方式进行参数化处理;identity则是accumulator的标识值(具体用处暂不明)。
    例:求和
    java Integer[] a = new Integer[]{3, 1, 2, 5, 11, 4, 6, 5, 3, 1}; int sum = Arrays.stream(a) .distinct() .filter((e) -> e < 6) .reduce(0, (x, y) -> x + y);//或.reduce(0, Integer::sum); System.out.println(sum);//15

    • toArray
    Object[] toArray();

    将Stream中的对象返回成一个Object数组。

    • forEach
    void forEach(Consumer<? super T> action);

    顾名思义,对Stream中每一个元素进行action操做,与peek相似,但forEach是一个最终操做,通常在结束时查看对象使用。

    • findFirst & findAny
    Optional<T> findFirst(); Optional<T> findAny();

    findFirst能够返回Stream中第一个对象,并将它封装在Optional中。
    findAny则不是返回第一个对象,而是任意一个对象。在顺序Stream中findFirst和findAny的结果是一致的,但在并行Stream中,findFirst存在着限制,故在并行Stream中须要使用findAny(findAny源码注释中写的是some element?)。一样将对象封装在Optional中。

Optional API

在java8以前的编程中,咱们老是须要进行if(obj=null)来防止NullPointException,而在java8后,提供了Optional类,它一方面用于防止NullPotinException的判断,另外一方面则为流式编程与函数式变成提供了更好的支持;Optional是一个包含对象的容器,它能够包含null值。在Optional类中封装了许多的方法,来让咱们更好的处理咱们的代码。接下来看看Optional中几个经常使用的方法:

  • empty & of & ofNullable
    java public static <T> Optional<T> empty(){...} public static <T> Optional<T> of(T value) {return new Optional<T>(value);} public static <T> Optional<T> ofNullable(T value){return value == null ? empty() : of(value);}
    首先,Optioanl的构造方法是私有的,只能经过以上三个静态方法来获取Optional的实例。empty方法会返回Optional中的常量EMPTY对象,通常在compare时使用,注意这里的EMPTY是单例的并且为常量;通常咱们须要构造一个Optional,使用of或ofNullable方法,of方法会将咱们的传值构造一个新的Optional返回,而ofNullable则在接收null时返回EMPTY实例。

  • isPresent
    java public boolean isPresent() {return value != null;} public void ifPresent(Consumer<? super T> consumer) {if (value != null)consumer.accept(value);}
    isPresent方法用于判断Optional包含的value是否为null,第一种方法返回一个boolean;第二种方法则根据判断,为null则什么都不执行,不为null则执行一个consumer操做。

  • map & flatMap
    java public<U> Optional<U> map(Function<? super T, ? extends U> mapper){...} public<U> Optional<U> flatMap(Function<? super T, Optional<U> mapper){...}
    map与flatMap与Stream中用法与功能大体相同,都是转换及合并转换,再也不赘述。

  • get
    java public T get() {...}
    get方法用于获取value。须要注意的是,若是value为null,则会抛出NoSuchElementException。

  • filter
    java public Optional<T> filter(Predicate<? super T> predicate) {...}
    filter方法也是获取value,它能够传入一个predicate,用于判断value是否知足条件。若是value为null,则会返回this;若是predicate.test为true,则返回this,不然会返回EMPTY。

  • orElse & orElseGet & orElseGet
    java public T orElse(T other) {return value != null ? value : other;} public T orElseGet(Supplier<? extends T> other) {return value != null ? value : other.get();} public <X extends Throwable> T orElseThrow(Supplier<? extends X> exceptionSupplier) throws X{...}
    这三个方法都用于获取value,同时能够在value==null的状况下作出不一样的操做。orElse能够传入一个other,当value==null时则返回null;orElseGet则是使用Supplier,为null时调用get方法;orElseThrow则是接收一个Supplier包含某种异常的exceptionSupplier,为null时则会调用get方法抛出一个异常。

Date Time API

Java8使用新的日期时间API覆盖旧的日期时间API的,处理了如下缺点。

  • 线程安全 - java.util.Date不是线程安全的,所以开发者必须在使用日期处理并发性问题。新的日期时间API是不可变的,而且没有setter方法。
  • 设计问题 - 默认的开始日期为1900年,月的开始月份为0而不是1,没有统一。不直接使用方法操做日期。新的API提供了这样操做实用方法。
  • 时区处理困难 - 开发人员必须编写大量的代码来处理时区的问题。新的API设计开发为这些特定领域提供了帮助。

JAVA8引入了java.time包,一个新的日期时间API。限于篇幅与精力问题,这里不对java.time进行过多的介绍,这里推荐几篇我的以为不错的博文以供研究:
Java 类库的新特性之日期时间API (Date/Time API )
Java 8 之 java.time 包

相关文章
相关标签/搜索