JDK1.8新特性(超详细)

2,408 阅读31分钟

Java函数式设计

实现方法:

  • @FunctionalInterface接口
  • Lambda语法
  • 方法引用
  • 接口default方法实现

一、lambda表达式

lambda表达式为匿名内部类的简写,类似于匿名内部类的语法糖;但又区别于匿名内部类(后文会讲解)。

匿名内部类特点:

  • 基于多态(多数基于接口编程)
  • 实现类无需名称
  • 允许多个抽象方法

Lambda的语法简洁,没有面向对象复杂的束缚。 特点:

  1. 使用Lambda必须有接口,并且接口中有且仅有一个抽象方法。 只有当接口中的抽象方法存在且唯一时,才可以使用Lambda,但排除接口默认方法以及声明中覆盖Object的公开方法。
  2. 使用Lambda必须具有上下文推断。 也就是方法的参数或局部变量类型必须为Lambda对应的接口类型,才能使用Lambda作为该接口的实例。

备注:有且仅有一个抽象方法的接口,称为“函数式接口”。

标准格式由三部分组成:

  • 一些参数
  • 一个箭头
  • 一段代码 格式:
(参数列表)->{一些重要方法的代码};
():接口中抽象方法的参数列表,没有参数,就空着;有参数就写出参数,多个参数用逗号分隔。
->:传递:把参数传递给方法体{}
{}:重写接口的抽象方法的方法体

箭头操作符的左侧对应接口中参数列表(lambda表达式的参数列表), 箭头右侧为该抽象方法的实现(lambda表达式所需执行的功能)。

lambda优化写法: 可以推导的,都可以省略(凡是能根据上下文推导出来的内容,都可以省略不写):

(参数列表):括号中参数列表的数据类型,可以省略不写
(参数列表):括号中的参数只有一个,那么类型和()都可以省略
 {一些代码} :如果{}中的代码只有一行,无论是否有返回值,都可以省略({},return,分号)

注意:要省略{},return,分号 必须一起省略

public class MyLambda {
    public static void main(String[] args) {
        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName()+"新线程创建了");
            }
        }).start();

        //使用Lambda
        new Thread(()->{
            System.out.println(Thread.currentThread().getName()+"新线程创建了");
        }).start();

        //优化lambda
        new Thread(()->System.out.println(Thread.currentThread().getName()+"新线程创建了")).start();
    }
}

1.1无参数,无返回

()->System.out.println("hello lambda")

Cook:

public interface Cook {
    public abstract void makeFood();
}

Demo01Cook:

public class Demo01Cook {

    public static void main(String[] args) {
        invokeCook(new Cook() {
            public void makeFood() {
                System.out.println("做饭。。。");
            }
        });
        //使用Lambda
        invokeCook(()->{
            System.out.println("做饭。。。");
        });

        //优化lambda
        invokeCook(()-> System.out.println("做饭。。。"));
    }

    public static void invokeCook(Cook cook){
        cook.makeFood();
    }
}

1.2 有参数,无返回

x->System.out.println("hello lambda")

import java.util.function.Consumer;

/**
 * Created by hongcaixia on 2019/10/31.
 */
public class Demo2 {

    public static void main(String[] args) {
        Consumer<String> consumer = x-> System.out.println(x);
        consumer.accept("有参数无返回");
    }
}

1.3 有参数,有返回

(Person p1,Person p2)->{ return p1.getAge()-p2.getAge(); }

package com.hcx.lambda;

import java.util.Arrays;
import java.util.Comparator;

/**
 * Created by hongcaixia on 2019/10/26.
 */
public class MyArrays {

    public static void main(String[] args) {
        Person[] arr = {new Person("陈奕迅",40),
                        new Person("钟汉良",39),
                        new Person("杨千嬅",38)};

        //对年龄进行排序
        Arrays.sort(arr, new Comparator<Person>() {
            @Override
            public int compare(Person o1, Person o2) {
                return o1.getAge()-o2.getAge();
            }
        });

        //使用lambda
        Arrays.sort(arr,(Person p1,Person p2)->{
            return p1.getAge()-p2.getAge();
        });

        //优化lambda
        Arrays.sort(arr,(p1,p2)->p1.getAge()-p2.getAge());

        Arrays.sort(arr,Comparator.comparing(Person::getAge));

        for (Person p:arr){
            System.out.println(p);
        }
    }
}

二、函数式接口

2.1概念

函数式接口:有且仅有一个抽象方法的接口

函数式接口,即适用于函数式编程场景的接口。而Java中的函数式编程体现就是Lambda,所以函数式接口就是可以适用于Lambda使用的接口。只有确保接口中有且仅有一个抽象方法,Java中的Lambda才能顺利地进行推导。

2.2格式

修饰符 interface 接口名称 {
    public abstract 返回值类型 方法名称(可选参数信息);
    // 其他非抽象方法内容
}

抽象方法的 public abstract 是可以省略的:

public interface MyFunctionalInterface {   
    void myMethod();    
}

2.3@FunctionalInterface注解

与 @Override 注解的作用类似,Java 8中专门为函数式接口引入了一个新的注解: @FunctionalInterface 。用于函数式接口类型声明的信息注解类型,这些接口的实例被Lambda表达式、方法引用或构造器引用创建。函数式接口只能有一个抽象方法,但排除接口默认方法以及声明中覆盖Object的公开方法: @FunctionalInterface注解源码注释:

image.png

object下的public方法.png

源码注释:该注解是定义一个lambda表达式的基础, 即是否是函数式接口可以标注也可以不标注。 函数式接口必须有一个精确的抽象方法,但排除以下两种: ①java8的default,他们不属于抽象方法。 ②如果该接口声明的一个抽象方法覆盖了任意一个object的方法,也排除掉。

@FunctionalInterface
public interface MyFunctionInterface {
    //唯一个抽象方法
    void method();
    //排除用default修饰的方法
    default void method1(){
    }
    //排除Ojbect下的方法
    int hashCode();
}
@FunctionalInterface
public interface MyFunctionalInterface {
	void myMethod();    
}

注意:@FuncationlInterface不能标注在注解、类以及枚举上。一旦使用该注解来定义接口,编译器将会强制检查该接口是否确实有且仅有一个抽象方法,否则将会报错。需要注意的是,即使不使用该注解,只要满足函数式接口的定义,这仍然是一个函数式接口,使用起来都一样。

MyFunctionalInterface:

@FunctionalInterface
public interface MyFunctionalInterface {
    //定义一个抽象方法
    public abstract void method();
}

MyFunctionalInterfaceImpl:

public class MyFunctionalInterfaceImpl implements MyFunctionalInterface{
    @Override
    public void method() {

    }
}

Demo:

/*
    函数式接口的使用:一般可以作为方法的参数和返回值类型
 */
public class Demo {
    //定义一个方法,参数使用函数式接口MyFunctionalInterface
    public static void show(MyFunctionalInterface myInter){
        myInter.method();
    }

    public static void main(String[] args) {
        //调用show方法,方法的参数是一个接口,所以可以传递接口的实现类对象
        show(new MyFunctionalInterfaceImpl());

        //调用show方法,方法的参数是一个接口,所以我们可以传递接口的匿名内部类
        show(new MyFunctionalInterface() {
            @Override
            public void method() {
                System.out.println("使用匿名内部类重写接口中的抽象方法");
            }
        });

        //调用show方法,方法的参数是一个函数式接口,所以我们可以Lambda表达式
        show(()->{
            System.out.println("使用Lambda表达式重写接口中的抽象方法");
        });

        //简化Lambda表达式
        show(()-> System.out.println("使用Lambda表达式重写接口中的抽象方法"));
    }
}

案例: 分析:函数式接口和普通方法的差异

import java.util.function.Supplier;

/**
 * Created by hongcaixia on 2019/11/3.
 */
public class MyTest {
    public static void main(String[] args) {
        print1("hello,world");
        print2(()->"hello world");
    }

    public static void print1(String message){
        System.out.println(message);
    }

    public static void print2(Supplier<String> message){
        System.out.println(message.get());
    }
}

以上代码会得到同样的结果,但使用了函数式接口相当于把数据进行了延迟加载。使用函数式接口,数据并没有完全确定,等到真正调用的时候才确定,类似推模型。

Demo01Logger:

public class Demo01Logger {
    //定义一个根据日志的级别,显示日志信息的方法
    public static void showLog(int level, String message){
        //对日志的等级进行判断,如果是1级别,那么输出日志信息
        if(level==1){
            System.out.println(message);
        }
    }

    public static void main(String[] args) {
        //定义三个日志信息
        String msg1 = "Hello";
        String msg2 = "World";
        String msg3 = "Java";

        //调用showLog方法,传递日志级别和日志信息
        showLog(2,msg1+msg2+msg3);

    }
}

Demo02Lambda:

/*
    使用Lambda优化日志案例
    Lambda的特点:延迟加载
    Lambda的使用前提,必须存在函数式接口
 */
public class Demo02Lambda {
    //定义一个显示日志的方法,方法的参数传递日志的等级和MessageBuilder接口
    public static void showLog(int level, MessageBuilder mb){
        //对日志的等级进行判断,如果是1级,则调用MessageBuilder接口中的builderMessage方法
        if(level==1){
            System.out.println(mb.builderMessage());
        }
    }

    public static void main(String[] args) {
        //定义三个日志信息
        String msg1 = "Hello";
        String msg2 = "World";
        String msg3 = "Java";

        //调用showLog方法,参数MessageBuilder是一个函数式接口,所以可以传递Lambda表达式
        /*showLog(2,()->{
            //返回一个拼接好的字符串
            return  msg1+msg2+msg3;
        });*/

        showLog(1,()->{
            System.out.println("不满足条件不执行");
            //返回一个拼接好的字符串
            return  msg1+msg2+msg3;
        });
    }
}

MessageBuilder:

@FunctionalInterface
public interface MessageBuilder {
    //定义一个拼接消息的抽象方法,返回被拼接的消息
    public abstract String builderMessage();
}

分析:使用Lambda表达式作为参数传递,仅仅是把参数传递到showLog方法中,只有满足条件,日志的等级是1级才会调用接口MessageBuilder中的方法builderMessage,才会进行字符串的拼接; 如果条件不满足,日志的等级不是1级,那么MessageBuilder接口中的方法builderMessage也不会执行,所以拼接字符串的代码也不会执行,所以不会存在性能的浪费

2.4使用函数式接口作为方法的参数

Demo01Runnable:

public class Demo01Runnable {
    //定义一个方法startThread,方法的参数使用函数式接口Runnable
    public static void startThread(Runnable run){
        //开启多线程
        new Thread(run).start();
    }

    public static void main(String[] args) {
        //调用startThread方法,方法的参数是一个接口,那么我们可以传递这个接口的匿名内部类
        startThread(new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName()+"-->"+"线程启动了");
            }
        });

        //调用startThread方法,方法的参数是一个函数式接口,所以可以传递Lambda表达式
        startThread(()->{
            System.out.println(Thread.currentThread().getName()+"-->"+"线程启动了");
        });

        //优化Lambda表达式
        startThread(()->System.out.println(Thread.currentThread().getName()+"-->"+"线程启动了"));
    }
}

2.5使用函数式接口作为方法的返回值

Demo02Comparator:

import java.util.Arrays;
import java.util.Comparator;

/*
    如果一个方法的返回值类型是一个函数式接口,那么就可以直接返回一个Lambda表达式。
    当需要通过一个方法来获取一个java.util.Comparator接口类型的对象作为排序器时,就可以调该方法获取。
 */
public class Demo02Comparator {
    //定义一个方法,方法的返回值类型使用函数式接口Comparator
    public static Comparator<String> getComparator(){
        //方法的返回值类型是一个接口,那么我们可以返回这个接口的匿名内部类
        /*return new Comparator<String>() {
            @Override
            public int compare(String o1, String o2) {
                //按照字符串的降序排序
                return o2.length()-o1.length();
            }
        };*/

        //方法的返回值类型是一个函数式接口,所以我们可以返回一个Lambda表达式
        /*return (String o1, String o2)->{
            //按照字符串的降序排序
            return o2.length()-o1.length();
        };*/

        //继续优化Lambda表达式
        return (o1, o2)->o2.length()-o1.length();
    }

    public static void main(String[] args) {
        //创建一个字符串数组
        String[] arr = {"a","bb","ccc","dddd"};
        //输出排序前的数组
        System.out.println(Arrays.toString(arr));
        //调用Arrays中的sort方法,对字符串数组进行排序
        Arrays.sort(arr,getComparator());
        //输出排序后的数组
        System.out.println(Arrays.toString(arr));
    }

}

每次都要声明一个接口,写一个抽象方法,然后再用这个接口作为参数去用lambda实现。。。当果不需要!这个新特性就是为了我们使用简单的呀,所以java已经内置了一堆函数式接口了。

先来个表格整体概览一下常用的一些:

函数式接口 参数类型 返回类型 用途
Supplier 供给型 T 返回类型为T的对象,方法:T get()
Consumer消费型 T void 对类型为T的对象应用操作,方法:void accept(T t)
Predicate 断定型 T boolean 确定类型为T的对象是否满足某种约束,返回布尔值,方法:boolean test(T t)
Function<T,R>函数型 T R 对类型为T的对象应用操作,并返回R类型的对象,方法:R apply(T,t)

2.6常用函数式接口

①提供类型:Supplier接口

特点:只出不进,作为方法/构造参数、方法返回值 java.util.function.Supplier接口仅包含一个无参的方法:T get()。 用来获取一个泛型参数指定类型的对象数据。

Supplier接口被称之为生产型接口,指定接口的泛型是什么类型,那么接口中的get方法就会生产什么类型的数据

import java.util.function.Supplier;

/**
 * Created by hongcaixia on 2019/10/29.
 */
public class MySupplier {

    public static String getString(Supplier<String> supplier){
        return supplier.get();
    }

    public static void main(String[] args) {
        getString(new Supplier<String>() {
            @Override
            public String get() {
                return null;
            }
        });

        String s = getString(()->"Eason");
        System.out.println(s);
    }
}

GetMax:

import java.util.function.Supplier;

/**
 * Created by hongcaixia on 2019/10/29.
 */
public class GetMax {

    public static int getMaxNum(Supplier<Integer> supplier){
        return supplier.get();
    }

    public static void main(String[] args) {
        int[] arr = {-1,0,1,2,3};
        int maxValue = getMaxNum(()->{
            int max = arr[0];
            for(int i=0;i<arr.length;i++){
                if(arr[i]>max){
                    max = arr[i];
                }
            }
            return max;
        });
        System.out.println("数组元素的最大值是:"+maxValue);
    }

}
②消费类型:Consumer接口

特点:只进不出,作为方法/构造参数 java.util.function.Consumer接口则正好与Supplier接口相反, 它不是生产一个数据,而是消费一个数据,其数据类型由泛型决定。 Consumer接口中包含抽象方法void accept(T t),意为消费一个指定泛型的数据。

Consumer接口是一个消费型接口,泛型执行什么类型,就可以使用accept方法消费什么类型的数据 至于具体怎么消费(使用),需要自定义

import java.util.function.Consumer;

/**
 * Created by hongcaixia on 2019/10/29.
 */
public class MyConsumer {

    public static void method(String name, Consumer<String> consumer){
        consumer.accept(name);
    }

    public static void main(String[] args) {
        method("小哇", new Consumer<String>() {
            @Override
            public void accept(String s) {
                System.out.println("s是:"+s);
            }
        });

        method("小哇",(name)->{
            String s = new StringBuffer(name).reverse().toString();
            System.out.println("s是:"+s);
        });
    }
}

andThen: Consumer接口的默认方法andThen 作用:需要两个Consumer接口,可以把两个Consumer接口组合到一起,在对数据进行消费

import java.util.function.Consumer;

/**
 * Created by hongcaixia on 2019/10/30.
 */
public class AndThen {

    public static void method(String s, Consumer<String> consumer1,Consumer<String> consumer2){
//        consumer1.accept(s);
//        consumer2.accept(s);
        //使用andThen方法,把两个Consumer接口连接到一起,在消费数据
        //con1连接con2,先执行con1消费数据,在执行con2消费数据
        consumer1.andThen(consumer2).accept(s);
    }

    public static void main(String[] args) {
        method("Hello",
                (t)-> System.out.println(t.toUpperCase()), //消费方式:把字符串转换为大写输出
                (t)-> System.out.println(t.toLowerCase()));//消费方式:把字符串转换为小写输出


        method("Hello", new Consumer<String>() {
            @Override
            public void accept(String s) {
                System.out.println(s.toUpperCase());
            }},new Consumer<String>() {
                @Override
                public void accept(String s1) {
                    System.out.println(s1.toUpperCase());
                }
        });
    }
}

按照格式“姓名:XX。性别:XX。”的格式将信息打印 要求将打印姓名的动作作为第一个Consumer接口的Lambda实例, 将打印性别的动作作为第二个Consumer接口的Lambda实例, 将两个Consumer接口按照顺序“拼接”到一起。

public class DemoTest {
    //定义一个方法,参数传递String类型的数组和两个Consumer接口,泛型使用String
    public static void printInfo(String[] arr, Consumer<String> con1,Consumer<String> con2){
        //遍历字符串数组
        for (String message : arr) {
            //使用andThen方法连接两个Consumer接口,消费字符串
            con1.andThen(con2).accept(message);
        }
    }

    public static void main(String[] args) {
        //定义一个字符串类型的数组
        String[] arr = { "陈奕迅,男", "钟汉良,男", "胡歌,男" };

        //调用printInfo方法,传递一个字符串数组,和两个Lambda表达式
        printInfo(arr,(message)->{
            //消费方式:对message进行切割,获取姓名,按照指定的格式输出
            String name = message.split(",")[0];
            System.out.print("姓名: "+name);
        },(message)->{
            //消费方式:对message进行切割,获取年龄,按照指定的格式输出
            String age = message.split(",")[1];
            System.out.println(";年龄: "+age+"。");
        });
    }
}
③断定类型:Predicate接口

特点:boolean类型判断,作为方法/构造参数 java.util.function.Predicate接口 作用:对某种数据类型的数据进行判断,结果返回一个boolean值

Predicate接口中包含一个抽象方法: boolean test(T t):用来对指定数据类型数据进行判断的方法 结果: 符合条件,返回true 不符合条件,返回false

import java.util.function.Predicate;

/**
 * Created by hongcaixia on 2019/10/30.
 */
public class MyPredicate1 {

    public static boolean validateStr(String str, Predicate<String> predicate){
        return predicate.test(str);
    }

    public static void main(String[] args) {
        String str = "abcdef";
        boolean b = validateStr(str,string->str.length()>5);
        System.out.println(b);


        boolean b1 = validateStr(str, new Predicate<String>() {
            @Override
            public boolean test(String s) {
                return s.length()>5;
            }
        });

        System.out.println(b1);
    }

}

and方法: Predicate接口中有一个方法and,表示并且关系,也可以用于连接两个判断条件

default Predicate<T> and(Predicate<? super T> other) {
        Objects.requireNonNull(other);
        return (t) -> this.test(t) && other.test(t);
}

MyPredicateAnd :

import java.util.function.Predicate;

/**
 * Created by hongcaixia on 2019/10/30.
 */
public class MyPredicateAnd {

    public static boolean validateStr(String str, Predicate<String> pre1,Predicate<String> pre2){
//        return pre1.test(str) && pre2.test(str);
        return pre1.and(pre2).test(str);
    }

    public static void main(String[] args) {
        String s = "abcdef";
        boolean b = validateStr(s,str->str.length()>5,str->str.contains("a"));
        System.out.println(b);
    }
}

or方法: Predicate接口中有一个方法or,表示或者关系,也可以用于连接两个判断条件

    default Predicate<T> or(Predicate<? super T> other) {
        Objects.requireNonNull(other);
        return (t) -> test(t) || other.test(t);
    }

MyPredicateOr:

package com.hcx.lambda;

import java.util.function.Predicate;

/**
 * Created by hongcaixia on 2019/10/30.
 */
public class MyPredicateOr {
    public static boolean validateStr(String s, Predicate<String> pre1,Predicate<String> pre2){
//        return pre1.test(s) || pre2.test(s);
        return pre1.or(pre2).test(s);
    }

    public static void main(String[] args) {
        String s = "acdef";
        boolean b = validateStr(s,str->str.length()>5,str->str.contains("a"));

        validateStr(s, new Predicate<String>() {
            @Override
            public boolean test(String str) {
                return s.length()>5;
            }
        }, new Predicate<String>() {
            @Override
            public boolean test(String str) {
                return s.contains("a");
            }
        });

        System.out.println(b);
    }
}

negate方法: Predicate接口中有一个方法negate,也表示取反的意思

    default Predicate<T> negate() {
        return (t) -> !test(t);
    }

MyPredicateNegate:

import java.util.function.Predicate;

/**
 * Created by hongcaixia on 2019/10/30.
 */
public class MyPredicateNegate {

    public static boolean validateStr(String s, Predicate<String> pre){
//        return !pre.test(s);
        return pre.negate().test(s);
    }

    public static void main(String[] args) {
        String s = "acde";
        boolean b = validateStr(s,str->str.length()>5);

        System.out.println(b);
    }
}

MyTest:

package com.hcx.lambda;

import java.util.ArrayList;
import java.util.function.Predicate;

/**
 * Created by hongcaixia on 2019/10/30.
 */
public class MyTest {

    public static ArrayList<String> filter(String[] arr, Predicate<String> pre1, Predicate<String> pre2) {
        ArrayList<String> list = new ArrayList<>();
        for (String s : arr) {
            boolean b = pre1.and(pre2).test(s);
            if (b) {
                list.add(s);
            }
        }
        return list;
    }

    public static void main(String[] args) {
        String[] array = {"迪丽热巴,女", "古力娜扎,女", "佟丽娅,女", "赵丽颖,女"};
        ArrayList<String> list = filter(array,
                s -> s.split(",")[1].equals("女"),
                s -> s.split(",")[0].length() == 4);
        for(String s : list){
            System.out.println(s);
        }
    }
}

④转换类型:Function接口

特点:有输入,有输出 java.util.function.Function<T,R>接口用来根据一个类型的数据得到另一个类型的数据, 前者称为前置条件,后者称为后置条件。

Function接口中最主要的抽象方法为:R apply(T t),根据类型T的参数获取类型R的结果。 使用的场景例如:将String类型转换为Integer类型。

package com.hcx.lambda;

import java.util.function.Function;

/**
 * Created by hongcaixia on 2019/10/30.
 */
public class MyFunction {

    public static void change(String str, Function<String,Integer> function){
//        Integer i = function.apply(str);
        //自动拆箱 Integer自动转为int
        int i = function.apply(str);
        System.out.println(i);
    }

    public static void main(String[] args) {
        String s = "1234";
        change(s,str->Integer.parseInt(str));

        int i = Integer.parseInt(s);
        System.out.println(i);
    }
}

andThen方法:

package com.hcx.lambda;

import java.util.function.Function;

/**
 * 1.String转Integer,加10
 *      Function<String,Integer> fun1 :Integer i = fun1.apply("123")+10;
 * 2.Interger转String
 *      Function<Integer,String> fun2 :String s = fun2.apply(i);
 * Created by hongcaixia on 2019/10/31.
 */
public class MyFunctionTest {

    public static void change(String str, Function<String,Integer> fun1,Function<Integer,String> fun2){
        String string = fun1.andThen(fun2).apply(str);
        System.out.println(string);
    }

    public static void main(String[] args) {
        change("123",str->Integer.parseInt(str)+10,i->i+"");
    }
}

自定义函数模型拼接Demo:

package com.hcx.lambda;

import java.util.function.Function;

/**
 * String str = "赵丽颖,20";
 * 1.将字符串截取数字年龄部分,得到字符串;
 * 2.将上一步的字符串转换成为int类型的数字;
 * 3.将上一步的int数字累加100,得到结果int数字。
 * Created by hongcaixia on 2019/10/31.
 */
public class MyFunctionTest2 {

    public static int change(String str, Function<String,String> fun1,Function<String,Integer> fun2,
                             Function<Integer,Integer> fun3){
        return fun1.andThen(fun2).andThen(fun3).apply(str);
    }

    public static void main(String[] args) {
        int num = change("赵丽颖,32",str->str.split(",")[1],
                str->Integer.parseInt(str),
                i->i+100);
        System.out.println(num);
    }
}

注意:使用匿名内部类会编译后会多产生一个类,而使用lambda,底层是invokedynamic指令,不会有多余的类

三、方法引用

若lambda体中的内容,有方法已经实现了,则可以使用方法引用。方法引用是对lambda的简化

MyPrintable:

public interface MyPrintable {
    void print(String str);
}

DemoPrint:

public class DemoPrint {

    private static void printString(MyPrintable data){
        data.print("Hello,World");
    }

    public static void main(String[] args) {
        printString(s->System.out.println(s));

        printString(new MyPrintable() {
            @Override
            public void print(String str) {
                System.out.println(str);
            }
        });
    }
}

改进:

public class DemoPrint {

    private static void printString(MyPrintable data){
        data.print("Hello,World");
    }

    public static void main(String[] args) {
        //printString(s->System.out.println(s));
        printString(System.out::println);
    }
}

方法引用

双冒号::为引用运算符,而它所在的表达式被称为方法引用。 如果Lambda要表达的函数方案已经存在于某个方法的实现中,那么则可以通过双冒号来引用该方法作为Lambda的替代者。

三种格式:

  • 对象::实例方法名
  • 类::静态方法名
  • 类::实例方法名

分析

  • Lambda表达式写法: s -> System.out.println(s);
  • 方法引用写法: System.out::println

以上两种写法完全等效: 第一种:拿到参数之后通过Lambda传递给 System.out.println 方法去处理。 第二种:直接让 System.out 中的 println 方法来取代Lambda。

注意:lambda体中调用的方法的参数列表和返回值类型要与函数式接口的抽象方法的参数列表与返回值类型一致。 Lambda 中 传递的参数一定是方法引用中的那个方法可以接收的类型,否则会抛出异常

image.png

3.1 通过对象名引用成员方法

    @Test
    public void test(){
        Person person = new Person();
        Supplier<String> supplier =() -> person.getName();
        System.out.println(supplier.get());

        Supplier<Integer> supplier1 = person::getAge;
        System.out.println(supplier1.get());
    }

MyPrintable:

public interface MyPrintable {
    void print(String str);
}

MethodReadObj:

public class MethodReadObj {
    public void printUpperCaseString(String str){
        System.out.println(str.toUpperCase());
    }
}

MethodReference1:

public class MethodReference1 {

    public static void printString(MyPrintable p){
        p.print("hello");
    }

    public static void main(String[] args) {
        printString(str-> {
            MethodReadObj methodReadObj = new MethodReadObj();
            methodReadObj.printUpperCaseString(str);
        });

        /**
         * 使用方法引用:
         * 1.MethodReadObj对象已经存在
         * 2.成员方法printUpperCaseString已经存在
         * 所以可以使用对象名引用成员方法
         */
        MethodReadObj methodReadObj = new MethodReadObj();
        printString(methodReadObj::printUpperCaseString);

    }
}

3.2 通过类名称引用静态方法

类已经存在,静态方法已经存在,则可以通过类名直接引用静态成员方法

@Test
public void test1(){
    Comparator<Integer> comparator = (x,y)->Integer.compare(x,y);
    Comparator<Integer> comparator1 = Integer::compare;
}

MyCalc:

public interface MyCalc {
    int calc(int num);
}

MethodRerference2:

public class MethodRerference2 {

    public static int method(int num,MyCalc c){
        return c.calc(num);
    }

    public static void main(String[] args) {
        int number = method(-10, num -> Math.abs(num));

        int number1 = method(-10, Math::abs);
        System.out.println(number);
        System.out.println(number1);
    }
}

通过类名引用实例方法

@Test
public void test2(){
    BiPredicate<String,String> biPredicate = (x,y) -> x.equals(y);
    BiPredicate<String,String> biPredicate1 = String::equals;
}

注意:以上这种情况,需要满足一定的条件:lambda表达式中第一个参数是lambda体中的调用者,第二个参数是lambda体中的参数

3.3 通过super引用成员方法

如果存在继承关系,当Lambda中需要出现super调用时,也可以使用方法引用进行替代。 MyMeet :

@FunctionalInterface
public interface MyMeet {
    void meet();
}

Parent:

public class Parent {
    public void hello(){
        System.out.println("hello,I'm Parent");
    }
}

Child:

public class Child extends Parent{
    @Override
    public void hello() {
        System.out.println("hello,I'm Child");
    }

    public void method(MyMeet myMeet){
        myMeet.meet();
    }

    public void show(){
        method(()->{
            Parent parent = new Parent();
            parent.hello();
        });

        //使用super关键字调用父类
        method(()->super.hello());

        /**
         * 使用方法引用:使用super引用父类的成员方法:
         * 1.super已经存在
         * 2.父类的成员方法hello已经存在
         * 可以直接使用super引用父类的成员方法
         */
        method(super::hello);
    }

    public static void main(String[] args) {
        new Child().show();
    }
}

3.4 通过this引用成员方法

this代表当前对象,如果需要引用的方法就是当前类中的成员方法,那么可以使用 this::成员方法的格式来使用方法引用 MyWallet:

@FunctionalInterface
public interface MyWallet {
    void buy();
}

BuyThing:

public class BuyThing {

    public void buyCar(){
        System.out.println("买了一辆别摸我");
    }

    public void getSalary(MyWallet myWallet){
        myWallet.buy();
    }

    public void method(){
        getSalary(()->this.buyCar());

        /**
         * 1.this已经存在
         * 2.本类的成员方法buyCar已经存在
         * 所以可以直接使用this引用本类的成员方法buyCar
         */
        getSalary(this::buyCar);

    }

    public static void main(String[] args) {
        new BuyThing().method();
    }
}

3.5 类的构造器引用

由于构造器的名称与类名完全一样,并不固定。所以构造器引用使用 类名称::new 的格式表示。

public void test3(){
    Supplier<Person> personSupplier = ()->new Person();
    //构造器引用 此处引用的是无参构造器 ,因为Supplier中的get方法没有参数
    Supplier<Person> personSupplier1 = Person::new;
}

public void test4(){
    Function<Integer,Person> personFunction = (x)->new Person(x);
    //构造器引用 此处引用的是整型的一个参数的构造器 ,因为Function中的apply方法只有一个参数
    Function<Integer,Person> personFunction1 = Person::new;
}

注意:需要调用的构造器的参数列表要与函数式接口中抽象方法的参数列表一致

Person:

@Data
@NoArgsConstructor
@AllArgsConstructor
public class Person {
    private String name;
}

PersonBuilder:

@FunctionalInterface
public interface PersonBuilder {
    //根据传递的姓名,创建Perosn对象
    Person builderPerson(String name);
}

Demo:

public class Demo {

    //传递姓名和PersonBuilder接口,通过姓名创建Person对象
    public static void printName(String name,PersonBuilder personBuilder){
        Person person = personBuilder.builderPerson(name);
        System.out.println(person.getName());
    }

    public static void main(String[] args) {
        printName("hongcaixia",str->new Person(str));

        /**
         * 使用方法引用:
         * 1.构造方法new Person(String name)已知
         * 2.创建对象已知
         * 可以使用Person引用new创建对象
         */
        printName("hongcaixia",Person::new);
    }

}

3.6 数组的构造器引用

数组也是 Object 的子类对象,所以同样具有构造器,只是语法稍有不同。 格式:Type[]::new

public void test5() {
    Function<Integer, String[]> function = (x) -> new String[x];
    String[] strings = function.apply(10);

    Function<Integer,String[]> function1 = String[]::new;
    String[] strings1 = function1.apply(10);
}

ArrayBuilder:

@FunctionalInterface
public interface ArrayBuilder {
    //创建int类型数组的方法,参数传递数组的长度,返回创建好的int类型数组
    int[] builderArray(int length);
}

DemoArrayBuilder:

public class DemoArrayBuilder {

    public static int[] createArray(int length,ArrayBuilder arrayBuilder){
        return arrayBuilder.builderArray(length);
    }

    public static void main(String[] args) {
        int[] arr1 = createArray(5,length -> new int[length]);

        System.out.println(arr1.length);

        /**
         * 1.已知创建的是int[]数组
         * 2.创建的数组长度也是已知的
         * 使用方法引用,int[]引用new,根据参数传递的长度创建数组
         */
        int[] arr2 = createArray(10,int[]::new);
        System.out.println(Arrays.toString(arr2));
        System.out.println(arr2.length);
    }
}

四、StreamAPI

流是数据渠道,用于操作数据源(集合、数组等)生成的元素序列。

package com.hcx.stream;

import java.util.ArrayList;
import java.util.List;

/**
 * Created by hongcaixia on 2019/10/31.
 */
public class MyStream1 {

    public static void main(String[] args) {
        List<String> list = new ArrayList<>();
        list.add("陈奕迅");
        list.add("陈小春");
        list.add("钟汉良");
        list.add("陈七");
        list.add("陈伟霆");
        //筛选陈开头,名字是三个字的
        List<String> chenList = new ArrayList<>();
        for(String item : list){
            if(item.startsWith("陈")){
                chenList.add(item);
            }
        }

        List<String> threeList = new ArrayList<>();
        for(String item : chenList){
            if(item.length()==3){
                threeList.add(item);
            }
        }

        //遍历输出符合条件的
        for(String item : threeList){
            System.out.println(item);
        }

        System.out.println("=====================");

        //使用Stream流
        list.stream().filter(str->str.startsWith("陈"))
                .filter(str->str.length()==3)
                .forEach(str-> System.out.println(str));
    }
}

Stream(流)是一个来自数据源的元素队列

  • 元素是特定类型的对象,形成一个队列。
  • 数据源流的来源。 可以是集合,数组等。
  • Stream自己不会存储元素,而是按需计算。
  • Stream不会改变源对象,并且能返回一个持有结果的新流
  • Stream操作是延迟操作,意味着他们会等到需要结果的时候才执行

和以前的Collection操作不同, Stream操作还有两个基础的特征:

  • Pipelining: 中间操作都会返回流对象本身。 这样多个操作可以串联成一个管道, 如同流式风格(fluent style)。 这样做可以对操作进行优化, 比如延迟执行(laziness)和短路( short-circuiting)。
  • 内部迭代: 以前对集合遍历都是通过Iterator或者增强for的方式, 显式的在集合外部进行迭代, 这叫做外部迭代。 Stream提供了内部迭代的方式,流可以直接调用遍历方法。

Stream操作的三个步骤:

  • 创建Stream:一个数据源(如集合、数组),获取一个流
  • 中间操作:一个操作链,对数据源的数据进行处理
  • 终止操作:一个终止操作,执行中间操作链并产生结果

注意:“Stream流”其实是一个集合元素的函数模型,它并不是集合,也不是数据结构,其本身并不存储任何元素(或其地址值)。

import java.util.*;
import java.util.function.Predicate;

/**
 * Created by hongcaixia on 2019/10/31.
 */
public class MyPredicate {

    public static void main(String[] args) {
        List<Integer> nums = Arrays.asList(10,20,3,-5,-8);
        Collection<Integer> positiveNum = filter(nums,num->num>0);
        Collection<Integer> negativeNum = filter(nums,num->num<0);
        System.out.println(positiveNum);
        System.out.println(negativeNum);
        
    }

    private static <E> Collection<E> filter(Collection<E> source, Predicate<E> predicate){
        List<E> list = new ArrayList<>(source);
        Iterator<E> iterator = list.iterator();
        while (iterator.hasNext()){
            E element = iterator.next();
            if(!predicate.test(element)){
                iterator.remove();
            }
        }
        return Collections.unmodifiableList(list);
    }
}

4.1获取流

java.util.stream.Stream<T>是Java8新加入的常用的流接口。(不是函数式接口) 获取一个流有以下几种常用的方式: ①根据Collection获取流 java.util.Collection接口中加入了default方法 stream 用来获取流,所以其所有实现类均可获取流。

  • stream():获取串行流
  • parallelStream():获取并行流
package com.hcx.stream;

import java.util.*;
import java.util.stream.Stream;

/**
 * Created by hongcaixia on 2019/10/31.
 */
public class GetStreamFromCollection {

    public static void main(String[] args) {
        
        List<String> list = new ArrayList<>();
        Stream<String> stream1 = list.stream();

        Set<String> set = new HashSet<>();
        Stream<String> stream2 = set.stream();

        Vector<String> vector = new Vector<>();
        Stream<String> stream3 = vector.stream();

    }
}

②根据Map获取流 java.util.Map 接口不是Collection的子接口,且其K-V数据结构不符合流元素的单一特征,所以获取对应的流需要分key、value或entry等情况:

package com.hcx.stream;

import java.util.HashMap;
import java.util.Map;
import java.util.stream.Stream;

/**
 * Created by hongcaixia on 2019/10/31.
 */
public class GetStreamFromMap {

    public static void main(String[] args) {
        Map<String,String> map = new HashMap<>();

        Stream<Map.Entry<String, String>> stream1 = map.entrySet().stream();

        Stream<String> stream2 = map.keySet().stream();
        Stream<String> stream3 = map.values().stream();
        
    }
}

③根据数组获取流 如果使用的不是集合或映射而是数组,由于数组对象不可能添加默认方法,所以 Stream 接口中提供了静态方法of

package com.hcx.stream;

import java.util.stream.Stream;

/**
 * Created by hongcaixia on 2019/10/31.
 */
public class GetStreamFromArray {

    public static void main(String[] args) {
        String[] array = {"陈奕迅","钟汉良","杨千嬅"};
        Stream<String> stream = Stream.of(array);
    }
}

④获取无限流

public void test6() {
    Stream<Integer> stream = Stream.iterate(0, x -> x + 2);
}

public void test7() {
    Stream<Double> stream = Stream.generate(() -> Math.random());
}

注意:of 方法的参数是一个可变参数,所以支持数组。

总结:

  • 所有的 Collection 集合都可以通过stream默认方法获取流;
  • Stream 接口的静态方法of可以获取数组对应的流

4.2 流常用方法

方法可以被分成两种:

  • 延迟方法:返回值类型仍然是 Stream 接口自身类型的方法,因此支持链式调用。(除了终结方法外,其余方法均为延迟方法。)
  • 终结方法:返回值类型不再是 Stream接口自身类型的方法,因此不再支持类似 StringBuilder 那样的链式调用。终结方法包括 count 和 forEach 方法。

①逐一处理:forEach

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

该方法接收一个Consumer接口函数,会将每一个流元素交给该函数进行处理。 Consumer是一个消费型的函数式接口,可传递lambda表达式,消费数据

package com.hcx.stream;

import java.util.stream.Stream;

/**
 * Created by hongcaixia on 2019/10/31.
 */
public class StreamForEach {

    public static void main(String[] args) {
        Stream<String> stream = Stream.of("张三", "李四", "王五");
        stream.forEach(str-> System.out.println(str));
    }
}

②过滤:filter 可以通过 filter 方法将一个流转换成另一个子集流。方法签名:

Stream<T> filter(Predicate<? super T> predicate);

该接口接收一个Predicate 函数式接口参数(可以是一个Lambda或方法引用)作为筛选条件。

java.util.stream.Predicate函数式接口唯一的抽象方法为: boolean test(T t); 该方法将会产生一个boolean值结果,代表指定的条件是否满足: 如果结果为true,那么Stream流的filter方法将会留用元素; 如果结果为false,那么filter 方法将会舍弃元素。

public class StreamFilter {
    public static void main(String[] args) {
        Stream<String> stream = Stream.of("陈奕迅", "陈伟霆", "陈七", "钟汉良");
        Stream<String> stream1 = stream.filter(str -> str.startsWith("陈"));
        stream1.forEach(str-> System.out.println(str));
    }
}

注意:Stream属于管道流,只能被消费一次 Stream流调用完毕方法,数据就回流到下一个Steam上, 而这时第一个Stream流已经使用完毕,就会关闭了, 所以第一个Stream流就不能再调用方法了。

③映射:map 接收lambda,将元素转换成其他形式或提取信息,接收一个函数作为参数,该函数会被应用到每个元素上,并将其映射成一个新的元素。即将流中的元素映射到另一个流中。 方法签名:

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

该接口需要一个 Function 函数式接口参数,可以将当前流中的T类型数据转换为另一种R类型的流。 java.util.stream.Function 函数式接口,其中唯一的抽象方法为: R apply(T t); 这可以将一种T类型转换成为R类型,而这种转换的动作,就称为“映射”。

public class StreamMap {

    public static void main(String[] args) {
        Stream<String> stream = Stream.of("1", "2", "3");
        Stream<Integer> integerStream = stream.map(str -> Integer.parseInt(str));
        integerStream.forEach(i-> System.out.println(i));
    }
}
    public void test8() {
        Person person = new Person("hcx",24);
        Person person1 = new Person("hcx2",24);
        List<Person> list = new ArrayList<>();
        list.add(person);
        list.add(person1);
        list.stream().map(Person::getName).forEach(System.out::println);
    }

④flatMap 接收一个函数作为参数,将流中的每个值都换成另一个流,然后把所有流连接成一个流。即对流扁平化处理,浅显一点解释就是把几个小的list转换到一个大的list 如:[['a','b'],['c','d']] - > ['a','b','c','d'] 如果我们使用常用的map()方法获取的lowercaseWords数据结构为:[['a','b','c'],['m','d','w'],['k','e','t']]。如果我们要得到如:['a','b','c','m','d','w','k','e','t']这样数据结构的数据,就需要使用flatMap()方法。

public void test9() {
    List<String> list = Arrays.asList("a","b","c");
    Stream<Stream<Character>> streamStream = list.stream().map(MethodReference::filterCharacter);
    streamStream.forEach((stream)->stream.forEach(System.out::println));

    //使用flatMap
    Stream<Character> characterStream = list.stream().flatMap(MethodReference::filterCharacter);
    characterStream.forEach(System.out::println);
}

public static Stream<Character> filterCharacter(String str){
    List<Character> list = new ArrayList<>();
    for(Character c : str.toCharArray()){
        list.add(c);
    }
    return list.stream();
}

⑤规约reduce 将流中元素反复结合起来,得到一个值

import java.util.stream.Stream;

/**
 * Created by hongcaixia on 2019/10/31.
 */
public class StreamReduce {

    public static void main(String[] args) {
        sum(1,2,3,4,5);
    }

    private static void sum(Integer... nums){
        Stream.of(nums).reduce(Integer::sum).ifPresent(System.out::println);
    }
}
@Test
public void test10() {
    List<Integer> list = Arrays.asList(1,2,3,4,5);
    Integer sum = list.stream().reduce(0,(x,y)->x+y);
    System.out.println(sum);
}

⑥统计个数:count 终结方法 流提供 count方法来数一数其中的元素个数 该方法返回一个long值代表元素个数:

package com.hcx.stream;

import java.util.ArrayList;
import java.util.stream.Stream;

/**
 * Created by hongcaixia on 2019/10/31.
 */
public class StreamCount {
    public static void main(String[] args) {
        ArrayList<Integer> list = new ArrayList<>();
        list.add(1);
        list.add(2);
        list.add(3);
        Stream<Integer> stream = list.stream();
        long count = stream.count();
        System.out.println(count);
    }
}

⑦取用前几个:limit limit 方法可以对流进行截取,只取用前n个。方法签名:

Stream<T> limit(long maxSize);

参数是一个 long型,如果集合当前长度大于参数则进行截取;否则不进行操作 延迟方法,只是对流中的元素进行截取,返回的是一个新的流,还可以继续调用Stream中的其他方法

public class StreamLimit {
    public static void main(String[] args) {
        String[] str = {"1","2","3","4","5"};
        Stream<String> stream = Stream.of(str);
        Stream<String> limitStream = stream.limit(3);
        limitStream.forEach(string-> System.out.println(string));
    }
}

⑧跳过前几个:skip 如果希望跳过前几个元素,可以使用skip方法获取一个截取之后的新流:

Stream<T> skip(long n);

如果流的当前长度大于n,则跳过前n个;否则将会得到一个长度为0的空流

public class StreamSkip {
    public static void main(String[] args) {
        String[] str = {"1","2","3","4","5"};
        Stream<String> stream = Stream.of(str);
        Stream<String> skipStream = stream.skip(3);
        skipStream.forEach(string-> System.out.println(string));
    }
}

⑨组合:concat 如果有两个流,希望合并成为一个流,那么可以使用 Stream 接口的静态方法 concat

static <T> Stream<T> concat(Stream<? extends T> a, Stream<? extends T> b)

注意:这是一个静态方法,与 java.lang.String 当中的 concat 方法不同

public class StreamConcat {
    public static void main(String[] args) {
        Stream<String> stream1 = Stream.of("陈奕迅", "陈伟霆", "陈七", "钟汉良");
        String[] arr = {"1","2","3"};
        Stream<String> stream2 = Stream.of(arr);
        Stream<String> concatStream = Stream.concat(stream1, stream2);
        concatStream.forEach(str-> System.out.println(str));
    }
}

⑩排序:sorted

  • sorted() 自然排序
  • sorted(Comparator com) 定制排序
  • allMatch 检查是否匹配所有元素
  • anyMatch 检查是否至少匹配一个元素
  • noneMatch 检查是否没有匹配所有元素
  • findFirst 返回第一个元素
  • findAny 返回当前流中的任意元素
  • count 返回流中元素的总个数
  • max 返回流中最大值
  • min 返回流中最小值
public class StreamSort {
    public static void main(String[] args) {
        Integer[] nums = {2,9,0,5,-10,90};
        Stream<Integer> numsStream = Stream.of(nums);
        Stream<Integer> sortedStram = numsStream.sorted();
        sortedStram.forEach(num -> System.out.println(num));
    }
}

⑪Collect 将流转换为其他形式。接收一个Collector的实现,用于给stream中元素做汇总的方法

import java.util.LinkedList;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 * Created by hongcaixia on 2019/10/31.
 */
public class StreamCollect {

    public static void main(String[] args) {
        List<Integer> list = Stream.of(1,2,3,4,5).collect(Collectors.toList());
        List<Integer> list1 = Stream.of(1,2,3,4,5).collect(LinkedList::new,List::add,List::addAll);
        System.out.println(list.getClass());//class java.util.ArrayList
        System.out.println(list1.getClass());//class java.util.LinkedList
    }
}
@Test
public void test11() {
    Person person = new Person("hcx",24);
    Person person1 = new Person("hcx2",24);
    List<Person> list = new ArrayList<>();
    list.add(person);
    list.add(person1);
    List<String> collect = list.stream().map(Person::getName).collect(Collectors.toList());
}

4.3 并行流和顺序流

并行流是把一个内容分成多个数据块,并用不同的线程分别处理每个数据块的流。 通过parallel()与sequential()可以实现并行流和顺序流之间的切换。

Fork/Join框架.png
Fork/Join框架:在必要的情况下,将一个大任务进行拆分(fork)成若干小任务(拆到不可再拆时),再将一个个小任务运算的结果进行join汇总。

@Test
public void test12() {
    //顺序流
    LongStream.rangeClosed(0,100).reduce(0,Long::sum);
    //并行流
    long reduce = LongStream.rangeClosed(0, 100).parallel().reduce(0, Long::sum);
    //5050
    System.out.println(reduce);
}