Stream流 及 方法引用

1 Stream流

        说到Stream便容易想到I/O Stream,而实际上,谁规定“流”就一定是"IO流”呢?在java8中,得益于Lambda所带来的函数式编程,引入了一个全新的Stream概念,用于解决已有集合类库既有的弊端。


1.1 引言

传统集合的多步遍历代码

        几乎所有的集合(如Collection 接口或Map 接口等)都支持直接或间接的遍历操作。而当我们需要对集合中的元素进行操作的时候,除了必需的添加、删除、获取外,最典型的就是集合遍历。

循环遍历的弊端

        Java 8的Lambda让我们可以更加专注于做什么(What),而不是怎么做(How),这点此前已经结合内部类进行了对比说明。现在,我们仔细体会一下代码,可以发现:

  •         for循环的语法就是“怎么做”
  •         for循环的循环体才是”做什么"

        为什么使用循环?

        因为要进行遍历。但循环是遍历的唯一方式吗?遍历是指每一个元素逐一进行处理,而并不是从第一个到最后一个顺次处理的循环。前者是目的,后者是方式

        试想一下,如果希望对集合中的元素进行筛选过滤:

  1.         将集合A根据条件一过滤为子集B
  2.         然后再根据条件二过滤为子集C

传统方式:

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

public class DemoList {
    public static void main(String[] args) {
        //创建一个Lis集合,存储姓名
        List list=new ArrayList<>();
        list.add("cici");
        list.add("vivi");
        list.add("kaka");
        list.add("vavia");
        list.add("jack");
        //对集合元素进行遍历,以v开头的都存储在集合里
        List listA=new ArrayList<>();
        for (String s : list) {
            if (s.startsWith("v")){
                listA.add(s);
            }
        }
        //对listA集合进行过滤,只要姓名长度为4,再存储到新集和
        List listB=new ArrayList<>();
        for (String s : listA) {
            if (s.length()==4){
                listB.add(s);
            }
        }

        //遍历ListB,依次打印
        for (String s : listB) {
            System.out.println(s);
        }
    }
}

输出:
        vivi

使用Stream流的方式:

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

public class DemoStream {
    public static void main(String[] args) {
        //创建一个Lis集合,存储姓名
        List list=new ArrayList<>();
        list.add("cici");
        list.add("vivi");
        list.add("kaka");
        list.add("vavia");
        list.add("jack");
        //对集合元素进行过滤,以v开头的都存储在集合里
        //对listA集合进行过滤,只要姓名长度为4,再存储到新集合
        //遍历ListB,依次打印
        list.stream()
                .filter(name->name.startsWith("v"))
                .filter(name->name.length()==4)
                .forEach(name-> System.out.println(name));

    }
}

输出:
        vivi

1.2 流式思想概述

注意:请暂时忘记对传统IO流的固有印象!

整体来看,流式思想类似于工厂车间的”生产流水线”

当需要对多个元素进行操作(特别是多步操作)的时候,考虑到性能及便利性,我们应该首先拼好一个“模型"步骤方案,然后再按照方案去执行它。

Stream流 及 方法引用_第1张图片

这张图中展示了过滤、映射、跳过、计数等多步操作,这是一种集合元素的处理方案,而方案就是一种”函数模型"。图中的每一个方框都是一个“流”,调用指定的方法,可以从一个流模型转换为另一个流模型。而最右侧的数字3是最终结果。

这里的filter、map、skip 都是在对函数模型进行操作,集合元素并没有真正被处理。只有当终结方法count执行的时候,整个模型才会按照指定策略执行操作。而这得益于Lambda的延迟执行特性。

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

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

  • 元素是特定类型的对象,形成一个队列。Java中的Stream并不会存储元素,而是按需计算。
  • 数据源是流的来源。可以是集合,数组 等

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

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

当使用一个流的时候,通常包括三个基本步骤

        获取一个数据源(source) ->数据转换->执行操作获取想要的结果,每次转换原有 Stream 对象不改变,返回一个新的 Stream 对象(可以有多次转换),这就允许对其操作可以像链条一样排列,变成一个管道。

1.2.1 Stream流的特点

Stream流属于管道流,只能被消费(使用)一次

第一个Stream流调用完毕方法,数据就会流转到下一个Stream上

而这时第一个Stream流已经使用完毕,就会关闭了

所以第一个Stream流就不能再调用方法了

IllegalStateException: stream has already been operated upon or closed

import java.util.stream.Stream;

public class DemoStream_filter {
    public static void main(String[] args) {
        //获取一个Stream流
        Stream stream = Stream.of("cici", "vivi", "kaka", "mima", "vack");
        //对Stream流中的元素进行过滤,只要开头是v的人
        Stream stream2 = stream.filter(name -> name.startsWith("v"));
        //遍历stream2
        stream2.forEach(name-> System.out.println(name));


        stream.forEach(name-> System.out.println(name));
    }
}

输出:

1.3 获取流

java.util.stream.Stream是java 8新加入的最常用的流接口。(这并不是一个函数式接口。)

获取一个流非常简单,有以下几种常用的方式:

        1.所有的Collection集合都可以通过Stream默认方法获取流

                default Stream stream( )

        2.Stream接口的静态方法of 可以获取数组对应的流

                static Stream of (T... values )

                参数是一个可变参数,那么我们就可以传递一个数组

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

public class DemoGetStream {
    public static void main(String[] args) {
        //把集合转换为Stream流
        List list=new ArrayList<>();
        Stream stream1 = list.stream();

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

        Map map=new HashMap<>();
        //获取键,存储到set集合中
        Set keySet=map.keySet();
        Stream stream3 = keySet.stream();
        //获取值,存储到Collection集合中
        Collection values = map.values();
        Stream stream4 = values.stream();
        //获取键值对,(键与值映射关系)entrySet
        Set> entries = map.entrySet();
        Stream> stream5 = entries.stream();


        //把数组转换成Stream流
        Stream stream6 = Stream.of(1, 2, 3, 4, 5);
        //可变参数,可以传递数组
        Integer[] arr={1,2,3,4,5};
        Stream stream7 = Stream.of(arr);
        String[] arr2={"a","b","c","d"};
        Stream stream8 = Stream.of(arr2);
    }
}

1.4 常用方法

Stream流 及 方法引用_第2张图片

流模型的操作很丰富,这里介绍一些常用的API。这些方法可以被分成两种:

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

备注:本小节之外的更多方法,请自行参考API文档

1.3.1 逐一处理:forEach

虽然方法名字叫 forEach,但是与for循环中的“for-each”昵称不同。 

        void forEach(Consumer action);

该方法接收一个Consumer 接口函数,会将每一个流元素交给该函数进行处理。

复习Consumer接口

        java.util.function.Consumer接口是一个消费型接口

        Consumer接口中包含抽象方法void accept(T t),意为消费一个指定泛型的数据

简单记:
        forEach方法,用来遍历流中的数据是一个终结方法,遍历之后就不能续调用Stream流中的其他方法。

import java.util.stream.Stream;

public class DemoStream_forEach {
    public static void main(String[] args) {
        //获取一个Stream流
        Stream stream = Stream.of("cici", "vivi", "kaka", "mima", "jack");
        //使用stream流中的方法forEach对Stream流中的数据进行遍历
//        stream.forEach((String name)->{
//            System.out.println(name);
//        });
        //Lambda优化
        stream.forEach(name-> System.out.println(name));
    }
}

输出:

        

1.3.2 过滤:filter

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

        Stream filter(Predicatec? super T> predicate);

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

Stream流 及 方法引用_第3张图片


复习Predicate接口:
        此前我们已经学习过 java.uti.stream.Predicate 函数式接口,其中唯一的抽象方法为:
                boolean test(T t);

该方法将会产生一个boolean值结果,代表指定的条件是否满足。如果结果为true,那么Stream流的 filter 方法将会留用元素; 如果结果为false,那么 fiter 方法将会舍弃元素。

import java.util.stream.Stream;

public class DemoStream_filter {
    public static void main(String[] args) {
        //获取一个Stream流
        Stream stream = Stream.of("cici", "vivi", "kaka", "mima", "vack");
        //对Stream流中的元素进行过滤,只要开头是v的人
        Stream stream2 = stream.filter(name -> name.startsWith("v"));
        //遍历stream2
        stream2.forEach(name-> System.out.println(name));
    }
}

输出:
        Stream流 及 方法引用_第4张图片

1.3.3 映射:map

如果需要将流中的元素映射到另一个流中,可以使用 map 方法。方法签名:

         Stream map(Function mapper);

该接口需要一个 Function 函数式接口参数,可以将当前流中的T类型数据转换为另一种R类型的流。

Stream流 及 方法引用_第5张图片


复习Function接口

        此前我们已经学习过 java.util.stream.Function 函数式接口,其中唯一的抽象方法为:
                R apply(T t);

这可以将一种T类型转换成为R类型,而这种转换的动作,就称为”映射”。

import java.util.stream.Stream;

public class DemoStream_map {
    public static void main(String[] args) {
        //获取一个Stream流
        Stream stream = Stream.of("1", "2", "3", "4", "5");
        //使用map方法把字符串类型整数转换为Integer类型整数
        Stream stream2 = stream.map(name -> Integer.parseInt(name));
        //遍历stream2流
        stream2.forEach(name-> System.out.println(name));
    }
}

输出:

        

1.3.4 统计个数:count

Stream流中的常用方法 count:用于统计stream流中元素的个数

        long count();

count方法是一个终结方法,返回值是一个long类型的整数

所以不能再续调用Stream流中的其他方法了

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

public class DemoStream_count {
    public static void main(String[] args) {
        //获取一个Stream流
        ArrayList list=new ArrayList<>();
        list.add(1);
        list.add(2);
        list.add(3);
        list.add(4);
        list.add(5);
        list.add(6);
        list.add(7);
        Stream stream = list.stream();
        long count = stream.count();
        System.out.println(count);
    }
}

输出:

        7

1.3.5 取用前几个:limit

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

        Stream limit(long maxsize);

参数是一个long型,如果集合当前长度大于参数则进行截取;否则不进行操作。

Stream流 及 方法引用_第6张图片

        limit方法是一个延迟方法,它只对流中元素进行截取,返回的是一个新的流,所以可以继续调用Stream流中的其他方法。 

基本使用:

import java.util.stream.Stream;

public class DemoStream_limit {
    public static void main(String[] args) {
        //获取一个Stream流
        String[] arr={"cici", "vivi", "kaka", "mima", "vack"};
        Stream stream = Stream.of(arr);
        //使用limit方法对Stream流中的元素进行截取,只要前三个元素
        Stream stream2 = stream.limit(3);
        stream2.forEach(name-> System.out.println(name));
    }
}

输出:

        Stream流 及 方法引用_第7张图片

1.3.6 跳过前几个:skip

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

        stream skip(long n);

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

Stream流 及 方法引用_第8张图片

基本使用:

import java.util.stream.Stream;

public class DemoStream_skip {
    public static void main(String[] args) {
        //获取一个Stream流
        String[] arr={"cici", "vivi", "kaka", "mima", "vack"};
        Stream stream = Stream.of(arr);
        //使用skip方法跳过前3个元素
        Stream stream2 = stream.skip(3);
        stream2.forEach(name-> System.out.println(name));
    }
}

输出:
        Stream流 及 方法引用_第9张图片

1.3.7 组合:concat

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

        static Stream concat(stream a, Stream b)

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

该方法的基本使用代码如下:

import java.util.stream.Stream;

public class DemoStream_concat {
    public static void main(String[] args) {
        //获取一个Stream流
        Stream stream = Stream.of("1", "2", "3", "4", "5");
        //获取一个Stream流
        String[] arr={"cici", "vivi", "kaka", "mima", "vack"};
        Stream stream2 = Stream.of(arr);
        //把以上两个流组合为一个流
        Stream concat = Stream.concat(stream, stream2);
        concat.forEach(name-> System.out.println(name));
    }
}

输出:

        Stream流 及 方法引用_第10张图片

1.4 练习:集合元素处理(传统方式)

题目

现在有两个ArrayList 集合存储队伍当中的多个成员姓名,要求使用传统的for循环(或增强for循环)依次进行以下若干操作步骤:

        1.第一个队伍只要名字为3个字的成员姓名;存储到一个新集合中。
        2.第一个队伍筛选之后只要前3个人,存储到一个新集合中。
        3.第二个队伍只要姓张的成员姓名,存储到一个新集合中
        4.第二个队伍筛选之后不要前2个人;存储到一个新集合中。
        5.将两个队伍合并为一个队伍;存储到一个新集合中。
        6.根据姓名创建 Person 对象;存储到一个新集合中
        7.打印整个队伍的Person对象信息

两个队伍(集合)的代码如下:

import java.util.ArrayList;

public class DemoStream {
    public static void main(String[] args) {
        //第一支队伍
        ArrayList one = new ArrayList<>();
        one.add("迪西热巴");
        one.add("宋远桥");
        one.add("苏星河");
        one.add("石破天");
        one.add("石中玉");
        one.add("老子");
        one.add("庄子");
        one.add("洪七公");
        //1.第一个队伍只要名字为3个字的成员姓名;存储到一个新集合中。
        ArrayList one1=new ArrayList<>();
        for (String name : one) {
            if (name.length()==3){
                one1.add(name);
            }
        }
        //2.第一个队伍筛选之后只要前3个人,存储到一个新集合中。
        ArrayList one2=new ArrayList<>();
        for (int i = 0; i < 3; i++) {
            one2.add(one1.get(i));
        }

        //第二支队伍
        ArrayList two = new ArrayList<>();
        two.add("古力娜扎");
        two.add("张无忌");
        two.add("赵西颖");
        two.add("张二丰");
        two.add("尼古拉斯赵四");
        two.add("张天爱");
        two.add("张二狗");

        //3.第二个队伍只要姓张的成员姓名,存储到一个新集合中
        ArrayList two1=new ArrayList<>();
        for (String name : two) {
            if (name.startsWith("张")){
                two1.add(name);
            }
        }
        //4.第二个队伍筛选之后不要前2个人;存储到一个新集合中。
        ArrayList two2=new ArrayList<>();
        for (int i = 2; i < two1.size(); i++) {
            two2.add(two1.get(i));
        }

        //5.将两个队伍合并为一个队伍;存储到一个新集合中。
        ArrayList all=new ArrayList<>();
        all.addAll(one2);
        all.addAll(two2);

        //6.根据姓名创建 Person 对象;存储到一个新集合中
        ArrayList list=new ArrayList<>();
        for (String name : all) {
            list.add(new Person(name));
        }

        //7.打印整个队伍的Person对象信息
        for (Person person : list) {
            System.out.println(person);
        }
    }
}

输出 :

        Stream流 及 方法引用_第11张图片

1.5 练习: 集合元素处理(Stream方式)

题目

        将上一题当中的传统for循环写法更换为Stream流式处理方式。两个集合的初始内容不变,Person 类的定义也不变。

解答

        等效的Stream流式处理代码为:

public class DemoStream1 {
    public static void main(String[] args) {
        //第一支队伍
        ArrayList one = new ArrayList<>();
        one.add("迪西热巴");
        one.add("宋远桥");
        one.add("苏星河");
        one.add("石破天");
        one.add("石中玉");
        one.add("老子");
        one.add("庄子");
        one.add("洪七公");

        //1.第一个队伍只要名字为3个字的成员姓名;存储到一个新集合中。
        Stream one1 = one.stream().filter(name -> name.length() == 3);

        //2.第一个队伍筛选之后只要前3个人,存储到一个新集合中。
        Stream one2 = one1.limit(3);

        //第二支队伍
        ArrayList two = new ArrayList<>();
        two.add("古力娜扎");
        two.add("张无忌");
        two.add("赵西颖");
        two.add("张二丰");
        two.add("尼古拉斯赵四");
        two.add("张天爱");
        two.add("张二狗");

        //3.第二个队伍只要姓张的成员姓名,存储到一个新集合中
        Stream two1 = two.stream().filter(name -> name.startsWith("张"));

        //4.第二个队伍筛选之后不要前2个人;存储到一个新集合中。
        Stream two2 = two1.skip(2);

        //5.将两个队伍合并为一个队伍;存储到一个新集合中。
        Stream concat = Stream.concat(one2, two2);

        //6.根据姓名创建 Person 对象;存储到一个新集合中
        //7.打印整个队伍的Person对象信息
        concat.map(name->new Person(name)).forEach(p-> System.out.println(p));
    }
}

输出:

        Stream流 及 方法引用_第12张图片

2 方法引用

在使用Lambda表达式的时候,我们实际上传递进去的代码就是一种解决方案:拿什么参数做什么操作。那么考虑种情况:如果我们在Lambda中所指定的操作方案,已经有地方存在相同方案,那是否还有必要再写重复逻辑?

2.1 冗余的Lambda场景

来看一个简单的函数式接口以应用Lambda表达式

        @FunctionalInterface
        public interface Printable {
                void print(String str);

        }

在printable 接口当中唯一的抽象方法 print 接收一个字符串参数,目的就是为了打印显示它。那么通过Lambda来使用它的代码很简单:

分析:
        Lambdo表达式的目的,打印参数传递的字符串

        把参数s,传递给了System.out对象,调用out对象中的方法println对字符串进行了输出

注意:

        1.System.out对象是已经存在的

        2.println方法也是已经存在的

所以我们可以使用方法引用来优化Lambda表达式

可以使用System.out方法直接引用(调用)println方法

@FunctionalInterface
public interface Printable {
    void print(String str);
}
public class DemoPrintable {
    //定义一个方法,参数传递Printable接口,对字符串进行打印
    public static void printString(Printable p){
        p.print("helloWorld");
    }

    public static void main(String[] args) {
        //调用printString方法,方法参数是一个函数式接口,可以传递Lambda表达式
        printString((s)->{
            System.out.println(s);
        });

        printString(System.out::println);
    }
}

输出:
        Stream流 及 方法引用_第13张图片

2.2 方法引用符

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

语义分析
例如上例中,System.out 对象中有一个重载的 println(String)方法恰好就是我们所需要的。那么对于printString 方法的函数式接口参数,对比下面两种写法,完全等效:

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

第一种语义是指:拿到参数之后经Lambda之手,继而传递给 ystem.out.println 方法去处理

第二种等效写法的语义是指:直接让System.out 中的 println 方法来取代Lambda。两种写法的执行效果完全样,而第二种方法引用的写法复用了已有方案,更加简洁。

注:Lambda 中 传递的参数 一定是方法引用中 的那个方法可以接收的类型,否则会抛出异常

推导与省略

如果使用Lambda,那么根据“可推导就是可省略”的原则,无需指定参数类型,也无需指定的重载形式一一它们都将被自动推导。而如果使用方法引用,也是同样可以根据上下文进行推导。

函数式接口是Lambda的基础,而方法引用是Lambda的孪生兄弟

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

@FunctionalInterface
public interface Printable {
    void print(String str);
}
public class MethondReferanceObject {
    //定义一个成员方法,传递字符串,把字符串按照大写输出
    public void printUpperCaseString(String str){
        System.out.println(str.toUpperCase());
    }
}
import JavaSE.Day20.DemoMethodReferance.Printable;

/**
 * 通过对象名引用成员方法
 * 前提:
 * 对象名已经存在
 * 成员方法也已经存在
 * 就可以使用对象名引用成员方法
 */
public class DemoObjectMethodReferance {
    //定义一个方法,参数传递Printable接口
    public static void printString(Printable p){
        p.print("hello");
    }

    public static void main(String[] args) {
        printString((s)->{
            //创建methodReferanceObject对象
            MethondReferanceObject obj=new MethondReferanceObject();
            //调用对象中的成员方法
            obj.printUpperCaseString(s);
        });
        /**
         * 使用方法引用优化Lambda
         * 对象是已经存在的MethodReferanceObject
         * 成员方法也是已经存在的
         * 所以可以使用对象名来引用成员方法
         */
        MethondReferanceObject obj=new MethondReferanceObject();
        printString(obj::printUpperCaseString);
    }
}

输出:
        Stream流 及 方法引用_第14张图片

2.4 通过类名引用静态方法

通过类名引用静态成员方法

类已经存在,静态成员方法也已经存在

就可以通过类名直接引用静态成员方法

public class DemoStaticMethodReferance {
    //定义一个方法。方法的参数传递要计算绝对值的整数和函数式接口calcable
    public static int method(int number,Calcable c){
        return c.calAbs(number);
    }
    public static void main(String[] args) {
        //调用mathod方法。传递计算绝对值得整数,和Lambda表达式
        int num=method(-10,(n)->{
            //对参数进行绝对值的计算并返回结果
            return Math.abs(n);
        });
        System.out.println(num);

        /**
         * 使用方法引用优化Lambda表达式
         * Math类是存在的
         * abs计算绝对值的静态方法也是已经存在的
         * 所以我们可以直接通过类名引用静态方法
         */
        int num2 = method(-10, Math::abs);
        System.out.println(num2);
    }
}

输出:

        

2.5 通过super引用成员方法

//定义见面的函数式接口
@FunctionalInterface
public interface Greetable {
    //定义一个见面的方法
    public void greet();
}
/**
 * 定义父类
 */
public class Human {
    //定义一个sayHello的方法
    public void sayHello(){
        System.out.println("hello,我是Human");
    }
}
//定义子类
public class Man extends Human{
    //子类重写父类sayHello的方法

    @Override
    public void sayHello() {
        System.out.println("hello,我是man");
    }
    //定义一个方法,参数传递Greetable接口
    public void method(Greetable greetable){
        greetable.greet();
    }
    public void show(){
        //调用method方法,方法参数Greetable是函数式接口,所以可以传递Lambda表达式
//        method(()->{
//            //创建父类Human对象
//            Human h=new Human();
//            //调用父类的sayHello方法
//            h.sayHello();
//
//        });

        //因为有子父类关系,所以存在一个关键字super代表父类,所以可以直接使用super调用父类成员方法
//        method(()->{
//            super.sayHello();
//        });
        
        //使用super引用父类的成员方法
        //super是已经存在的,父类成员方法也是已经存在的
        //所以可以使用super引用父类成员方法
        method(super::sayHello);
    }

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

输出:
        hello,我是Human

2.6 通过this引用成员方法

//定义一个富有的函数式接口
@FunctionalInterface
public interface Richable {
    //定义一个想买就买什么的方法
    void buy();
}
/**
 * 通过this引用本类的成员方法
 */
public class Husband {
    //定义一个买房子的方法
    public void buyHouse(){
        System.out.println("北京二环内买一套四合院。");
    }
    //定义一个结婚的方法,参数传递Richable接口
    public void marry(Richable r){
        r.buy();
    }
    //定义一个非常高兴的方法
    public void soHappy(){
        //调用结婚的方法,方法的参数是函数式接口,可以传递Lambda表达式
//        marry(()->{
//            //使用this.成员方法调用本类买房子的方法
//            this.buyHouse();
//        });

        //使用方法引用优化Lambda表达式
        /**
         * this已经存在,本类的成员方法buyHouse也已经存在
         * 所以可以直接使用this引用本类的成员方法buyHouse
         */
        marry(this::buyHouse);
    }

    public static void main(String[] args) {
        new Husband().soHappy();
    }
}

输出:

        北京二环内买一套四合院。

2.7 类的构造器引用

public class Person {
    private String name;

    public Person() {
    }

    public Person(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                '}';
    }
}
/**
 * 定义一个创建Person对象的函数式接口
 *
 */
@FunctionalInterface
public interface PersonBuider {
    //定义一个方法,根据传递的姓名创建Person对象并返回
    Person buildPerson(String name);
}
/**
 * 类的构造器引用,构造器也叫构造方法
 */
public class Demo {
    //定义一个方法,参数传递姓名和PersonBuilder接口,方法中通过姓名创建Person对象
    public static void printName(String name, PersonBuider pb){
        Person person=pb.buildPerson(name);
        System.out.println(person.getName());
    }

    public static void main(String[] args) {
        //调用printName方法,方法参数PersonBuilder接口是函数式接口,可以传递Lambda表达式
        printName("cici",(String name)->{
            return new Person(name);
        });

        /**
         * 使用方法引用优化Lambda表达式
         *构造方法new Person(String name)已知
         * 创建对象的方式已知new
         * 可以使用Person引用new创建对象
         */
        printName("cici",Person::new);//使用Person类的带参构造方法,通过传递的姓名创建对象
    }
}

输出:
        

2.8 数组的构造器引用

/**
 * 定义一个创建数组的函数式接口
 */
@FunctionalInterface
public interface ArrayBuilder {
    //定义一个创建int类型数组的方法,参数传递数组长度,返回创建好的int类型数组
    int[] builderArray(int length);
}
import java.util.Arrays;

/**
 * 数组的构造器引用
 */
public class Demo {
    //定义一个方法,方法的参数传递创建数组的长度和arrayBuilder接口,方法内部根据传递的长度使用ArrayBuilder中的方法创建数组并返回
    public static int[] createArray(int length,ArrayBuilder ab){
        return ab.builderArray(length);
    }

    public static void main(String[] args) {
        /**
         * 调用createArray方法传递数组长度和Lambda表达式
         */
        int[] arr1=createArray(10,(len)->{
            //根据数组的长度创建数组并返回
            return new int[len];
        });
        System.out.println(arr1.length);

        /**
         * 使用方法引用优化Lambda表达式
         * 已知创建的就是int类型的数组,数组的长度也是已知的
         * 所以可以使用方法引用
         * int[]引用new,根据参数传递的长度创建数组
         */
        int[] arr2=createArray(10,int[]::new);
        System.out.println(Arrays.toString(arr2));
        System.out.println(arr2.length);
    }
}

输出:
        Stream流 及 方法引用_第15张图片

你可能感兴趣的:(JavaSE,开发语言,后端,java,数据结构)