JDK1.8新特性(超详细)

Java函数式设计

实现方法:java

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

1、lambda表达式

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

匿名内部类特色:数组

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

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

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

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

标准格式由三部分组成:多线程

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

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

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

(参数列表):括号中参数列表的数据类型,能够省略不写
(参数列表):括号中的参数只有一个,那么类型和()均可以省略
 {一些代码} :若是{}中的代码只有一行,不管是否有返回值,均可以省略({},return,分号)
复制代码

注意:要省略{},return,分号 必须一块儿省略dom

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")ide

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、函数式接口

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指令,不会有多余的类

3、方法引用

若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);
    }
}
复制代码

4、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);
}
复制代码
相关文章
相关标签/搜索