zoukankan      html  css  js  c++  java
  • 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博文传送门

    Java8特性学习笔记

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

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

    Lambda表达式

    Lambda 表达式,也可称为闭包。Lambda 允许把函数作为一个方法的参数(函数作为参数传递进方法中)。使用 Lambda 表达式可以使代码变的更加简洁紧凑。 Lambda表达式可以替代以前广泛使用的内部匿名类,各种回调,比如事件响应器、传入Thread类的Runnable等。

    Lambda语法

    lambda 表达式的语法格式如下:

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

    Lambda表达式的特征

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

    Lambda表达式例子

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

    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表达式1、排序:"+peopleList);;
        //2.不声明式,使用大括号,可以写多条语句
        Collections.sort(peopleList,(a,b)->{
            System.out.print("——————————————");
            return a.getAge().compareTo(b.getAge());
        });
        System.out.println();
        System.out.println("Lambda表达式2、排序:"+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)

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

    方法引用

    在一些Lambda中可能只是单纯的调用方法,比如前例中的三、四,在这种情况下,就可以使用方法引用的方式来提高可读性。

    方法引用的种类

    • 类静态方法引用
      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("按喇叭!!!");
       }
    }

    注:静态方法与默认方法均可以有多个,默认方法可以被覆盖。

    函数接口

    “函数接口(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 包

  • 相关阅读:
    [置顶] 大型网站技术架构(六)网站的伸缩性架构
    [置顶] 大型网站技术架构(五)网站高可用架构
    [置顶] 大型网站技术架构(五)网站高可用架构
    [置顶] 大型网站技术架构(四)网站的高性能架构
    [置顶] 大型网站技术架构(四)网站的高性能架构
    [置顶] 大型网站技术架构(三)架构核心要素
    [置顶] 大型网站技术架构(二)架构模式
    [置顶] 大型网站技术架构(二)架构模式
    [置顶] 大型网站技术架构(一)大型网站架构演化
    [置顶] 大型网站技术架构(一)大型网站架构演化
  • 原文地址:https://www.cnblogs.com/zcy1995/p/11791909.html
Copyright © 2011-2022 走看看