Java 8 知多少

一、函数式接口

函数式接口的定义:

函数式接口(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
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,968评论 6 482
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,601评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 153,220评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,416评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,425评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,144评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,432评论 3 401
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,088评论 0 261
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,586评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,028评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,137评论 1 334
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,783评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,343评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,333评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,559评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,595评论 2 355
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,901评论 2 345