美文网首页
Java 8 知多少

Java 8 知多少

作者: 愤怒的谜团 | 来源:发表于2020-09-19 09:45 被阅读0次

    一、函数式接口

    函数式接口的定义:

    函数式接口(Functional Interface)就是一个有且仅有一个抽象方法,但是可以有多个非抽象方法的接口。

    函数式接口可以被隐式转换为 lambda 表达式。

    //如下就是一个函数式的接口
    interface MathOperation {
            int operation(int a, int b);
        }
    

    函数式接口本身并没有过多的价值,主要是配合Lambda表达式进行使用。如果非函数式接口那么就不能使用Lambda表达式,还是得回归之前的匿名函数。

    @FunctionalInterface的妙用,如果不确定自己的接口是不是函数式接口,可以在接口上加上,如果不是编辑会报错的。

    11C91FAA-C5E1-4B85-8730-98868E1DCD6E.png

    二、Lambda表达式

    Lambda允许把函数作为一个方法的参数(函数作为参数传递进方法中)。

    这种写法有一定的局限性,就是必须是函数式接口,当然好处就是语法非常的简洁明了。

    //如下就是一个函数式的接口
    interface MathOperation {
            int operation(int a, int b);
        }
    
    public static int operate(int a, int b, MathOperation mathOperation) {
            return mathOperation.operation(a, b);
        }
    
    public static void main(String[] args) {
            /**
             * java 8之前的使用姿势,结果为15
             */
            System.out.println(operate(5, 10, new MathOperation() {
                @Override
                public int operation(int a, int b) {
                    return a + b;
                }
            }));
        
            /**
             * java 8的使用姿势,结果都为15
             * 1:Lambda表达式会自动推断类型,所以类型声明可以省略
             * 2:当Lambda表达式右侧只有一个简单语句,例如a+b,那么可以自动推断出返回值,不用加return
             * 3:如果处理逻辑比较复杂,那么需要加上{}和return
             */
            System.out.println(operate(5, 10, (int a, int b) -> a + b));
            System.out.println(operate(5, 10, (int a, int b) -> { return a + b; }));
            System.out.println(operate(5, 10, (a, b) -> a + b));
    }
    

    lambda 表达式内部的变量作用域其实跟匿名函数也是一样的,如果lambda表达式访问局部变量或者成员变量都是需要使用final来修饰,当然lambda的语法并没有那么严格,可以不使用final进行修饰,但是要保证lambda表达式不能对变量进行修改。

    int variable = 22;
    final int variable1 = 22;
    //这样都是合法的lambda表达式
    System.out.println(operate(5,10,(a,b) -> a+b+variable));
    System.out.println(operate(5,10,(a,b) -> a+b+variable1));
    
    //这样子就会报错
    System.out.println(operate(5,10,(a,b) -> {
        variable = 222;
    }));
    

    至于为什么lambda表达式或者说匿名函数会这样要求,其实原因也很简单,究其根本就是生命周期和值拷贝。

    成员变量(非static)一旦类被实例化就会存在堆内存当中,生命周期跟随实例创建而创建,跟随实例销毁而销毁。

    局部变量,存在栈当中,方法调用而产生,调用完成就释放。

    lambda表达式/匿名函数其实是跟外部类同级的,如果使用了局部变量,局部变量在方法调用完成就释放了,但是lambda表达式还存在,这就比较尴尬了,所以java的做法是将局部变量的值copy一份使用,如果是引用型变量,copy的就是地址,既然是值copy,为了能够局部变量使用效果一样,自然是不允许修改值的,不然调用就会产生不同的效果。

    三、方法引用

    方法引用也是java 8推出的一个特性,可以使用::来指向对应方法,在介绍方法引用之前先介绍一下java 8推出的新的函数式接口。

    //Consumer<T>其实可以理解为就是一个消费者,需要传递一个Class<T>给Consumer,然后可以自己实现accept的操作
    @FunctionalInterface
    public interface Consumer<T> {
    
        /**
         * Performs this operation on the given argument.
         *
         * @param t the input argument
         */
        void accept(T t);
    
        default Consumer<T> andThen(Consumer<? super T> after) {
            Objects.requireNonNull(after);
            return (T t) -> { accept(t); after.accept(t); };
        }
    }
    //Supplier<T>其实可以理解成一个容器,用于存放Class<T>,然后可以自定义get的操作
    @FunctionalInterface
    public interface Supplier<T> {
    
        /**
         * Gets a result.
         *
         * @return a result
         */
        T get();
    }
    
    //在java 8之前可以使用匿名函数这么玩
    Consumer<Integer> consumer = new Consumer<Integer>() {
        @Override
        public void accept(Integer integer) {
            System.out.println(integer);
        }
    };
    consumer.accept(10);
    
    Supplier<Integer> supplier = new Supplier<Integer>() {
        @Override
        public Integer get() {
            return Integer.MAX_VALUE;
        }
    };
    System.out.println(supplier.get());
    

    理解了上述两个函数式接口的主要用处,接下来可以结合着方法引用来使用。先定义一个测试类,里面有静态方法和非静态方法。

    package com.cainiao.wmp.service;
    
    
    import java.util.Arrays;
    import java.util.List;
    import java.util.function.BiConsumer;
    import java.util.function.Consumer;
    import java.util.function.Function;
    import java.util.function.Supplier;
    
    /**
     * @author huayi.zh
     * @date 2020/09/14
     */
    public class Java8Test {
    
        static class Car {
            //Supplier是jdk1.8的接口,这里和lambda一起使用了
            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());
            }
        }
    
        public static void main(String[] args) {
            /**
             * 使用方法1:引用创建新实例,可以发现Supplier<Car>起到了装配Car实例的作用
             */
            Supplier<Car> supplier = Car::new;
            Car car = Car.create(supplier);
    
            /**
             * 使用方法2:借助forEach方法,用::调用静态函数,其实Car::collide产生一个Consumer<Car>,需要接收一个Car实例,
             * 自然就可以使用forEach方法了,可以将Car::collide看做是对Consumer<Car>接口的一个实现,具体内容就是接收一个
             * Car实例,然后执行collide方法。
             */
            List<Car> cars = Arrays.asList(car);
    //        Consumer<Car> collide = Car::collide;
    //        cars.forEach(collide);
            cars.forEach(Car::collide);
    
            /**
             * 使用方法3:借助forEach方法,用::调用普通函数,分为两种情况,一种是Car类,一种是Car的实例
             * cars.forEach(Car::repair);可以执行,但是cars.forEach(Car::follow);却不行
             */
            Consumer<Car> repair = Car::repair;
            BiConsumer<Car, Car> follow = Car::follow;
            /**
             * 原因很明显了,follow方法参数需要提供一个Car实例,本身又是非静态函数,需要用实例才能调用,需要两个Car实例,
             * 但是repair是空参函数,所以只需要一个,forEach每次循环只能提供一个Car实例
             */
    
            /**
             * 换一种写法,使用Car的实例,然后加上::也是调用的
             */
            cars.forEach(car::follow);
        }
    }
    

    四、默认方法

    Java 8 新增了接口的默认方法。

    简单说,默认方法就是接口可以有实现方法,而且不需要实现类去实现其方法。

    我们只需在方法名前面加个 default 关键字即可实现默认方法。

    为什么要有这个特性?

    首先,之前的接口是个双刃剑,好处是面向抽象而不是面向具体编程,缺陷是,当需要修改接口时候,需要修改全部实现该接口的类,目前的 java 8 之前的集合框架没有 foreach 方法,通常能想到的解决办法是在JDK里给相关的接口添加新的方法及实现。然而,对于已经发布的版本,是没法在给接口添加新方法的同时不影响已有的实现。所以引进的默认方法。他们的目的是为了解决接口的修改与现有的实现不兼容的问题。

    //默认方法语法格式如下:
    public interface Vehicle {
       default void print(){
          System.out.println("我是一辆车!");
       }
    }
    

    java都是单继承,多实现的,目的就是为了避免多个继承,存在冲突实现的方法,那么现在在接口上增加了默认方法,也会存在同样的问题,java 8是这样处理的。

    //第一个解决方案是创建自己的默认方法,来覆盖重写接口的默认方法:
    public class Car implements Vehicle, FourWheeler {
       default void print(){
          System.out.println("我是一辆四轮汽车!");
       }
    }
    //第二种解决方案可以使用 super 来调用指定接口的默认方法:
    public class Car implements Vehicle, FourWheeler {
       public void print(){
          Vehicle.super.print();
       }
    }
    

    五、Stream

    Java 8 API添加了一个新的抽象称为流Stream,可以让你以一种声明的方式处理数据。

    Stream API可以极大提高Java程序员的生产力,让程序员写出高效率、干净、简洁的代码。

    在 Java 8 中, 集合接口有两个方法来生成流:

    • stream() − 为集合创建串行流。
    • parallelStream() − 为集合创建并行流。

    常见算子有:

    forEach:可以迭代流中的每个数据

    List<String> strings = Arrays.asList("abc", "", "bc", "efg", "abcd","", "jkl");
    //forEach可以迭代流中每个元素,具体做什么操作,可以使用lambda表达式来实现。
    strings.stream().forEach(s-> System.out.println(s));
    //等同于以下实现,只不过使用lambda表达式会使代码更加的简洁紧凑。
    strings.stream().forEach(new Consumer<String>() {
        @Override
        public void accept(String s) {
            System.out.println(s);
        }
    });
    
    //forEach的源码,可以发现参数一个Consumer<? super T> action,跟之前的方法引用可以对上,那是不是也可以用方法引用来实现?
    //答案当然是可以的,说明lambda表达式其实就是一种特殊的方法引用,
    void forEach(Consumer<? super T> action);
    //如下来实现
    List<String> strings = Arrays.asList("abc", "", "bc", "efg", "abcd","", "jkl");
    strings.stream().forEach(System.out::println);
    

    map:****用于映射每个元素到对应的结果

    List<String> strings = Arrays.asList("abc", "", "bc", "efg", "abcd", "", "jkl");
    strings.stream().map(s -> "\"" + s + "\"").forEach(s -> System.out.println(s));
    

    filter:根据规则过滤流中每个元素

    List<String> strings = Arrays.asList("abc", "", "bc", "efg", "abcd", "", "jkl");
    strings.stream().filter(s->s != "").forEach(s-> System.out.println(s));
    

    limit:控制获取指定数量的流

    List<String> strings = Arrays.asList("abc", "", "bc", "efg", "abcd", "", "jkl");
    strings.stream().limit(1).forEach(s-> System.out.println(s));
    

    sorted:对流中每个元素进行排序,自定义类需要自己实现compare方法

    List<String> strings = Arrays.asList("abc", "", "bc", "efg", "abcd", "", "jkl");
    strings.stream().sorted((a,b)->a.compareTo(b)).forEach(s-> System.out.println(s));
    

    Collectors:Collectors 类实现了很多归约操作,例如将流转换成集合和聚合元素。

    List<String> strings = Arrays.asList("abc", "", "bc", "efg", "abcd", "", "jkl");
    String collect = strings.stream().collect(Collectors.joining(","));
    List<String> collect1 = strings.stream().collect(Collectors.toList());
    

    六、Optional类

    Optional 类是一个可以为null的容器对象。如果值存在则isPresent()方法会返回true,调用get()方法会返回该对象。

    Optional 是个容器:它可以保存类型T的值,或者仅仅保存null。Optional提供很多有用的方法,这样我们就不用显式进行空值检测。

    Optional 类的引入很好的解决空指针异常。

    常用方法:

    1:static <T> Optional<T> empty()

    返回空的 Optional 实例。

    Optional<Integer> v11 = Optional.empty();
    

    2:****static <T> Optional<T> ofNullable(T value)

    如果为非空,返回 Optional 描述的指定值,否则返回空的 Optional。

    Integer v1 = new Integer(10);
    Integer v2 = null;
    //ofNullable可以传入null,返回一个空的Optional<Integer>,使用get()会抛出异常
    Optional<Integer> v11 = Optional.ofNullable(v1);
    System.out.println(v11.get());
    

    3:****static <T> Optional<T> of(T value)

    返回一个指定非****null****值的****Optional****。

    Integer v1 = new Integer(10);
    Integer v2 = null;
    //of不允许传入null,会抛出异常
    Optional<Integer> v11 = Optional.of(v2);
    

    4:****boolean equals(Object obj)

    判断其他对象是否等于 Optional****。

    Integer v1 = new Integer(10);
    Integer v2 = null;
    Optional<Integer> v11 = Optional.ofNullable(v1);
    Optional<Integer> v21 = Optional.ofNullable(v1);
    boolean equals = v11.equals(v21);
    //true
    System.out.println(equals);
    

    5:****T get()

    如果在这个Optional中包含这个值,返回值,否则抛出异常:NoSuchElementException

    Integer v1 = new Integer(10);
    Integer v2 = null;
    Optional<Integer> v11 = Optional.ofNullable(v1);
    System.out.println(v11.get());//10
    

    6:****boolean isPresent()

    如果在这个Optional中包含这个值,返回值,否则抛出异常:NoSuchElementException

    Integer v1 = new Integer(10);
    Integer v2 = null;
    Optional<Integer> v11 = Optional.ofNullable(v1);
    System.out.println(v11.isPresent());//true
    Optional<Integer> v21 = Optional.ofNullable(v2);
    System.out.println(v21.isPresent());//false
    

    7:T orElse(T other)

    如果存在该值,返回值, 否则返回 other****。

    Integer v1 = new Integer(10);
    Integer v2 = null;
    Optional<Integer> v11 = Optional.ofNullable(v1);
    System.out.println(v11.orElse(20));//10
    Optional<Integer> v21 = Optional.ofNullable(v2);
    System.out.println(v21.orElse(20));//20
    

    相关文章

      网友评论

          本文标题:Java 8 知多少

          本文链接:https://www.haomeiwen.com/subject/qyheyktx.html