#Java-stream流和方法引用

问题:

传入参数的问题:

Integer[] arr = new Integer[10];
        Arrays.sort(arr, new Comparator<Integer>() {
            @Override
            public int compare(Integer o1, Integer o2) {
                return o1 - o2;
            }
        });

这里面这个方法使用参数new Comparator<Integer>()的底层原理是什么?

回答:需要用到接口中的方法,例:

public interface StudentBuilder {
    Student build(String name,int age);
}//函数式接口
//调用接口的方法:StudentBuilder sb,其实是接口的实现类
private static void useStudentBuilder(StudentBuilder sb) {
        Student s = sb.build("林青霞", 30);
        System.out.println(s.getName() + "," + s.getAge());
    }
--------------------------------------
//在测试类中:
useStudentBUilder(new StudentBuider() {
    @Override
    Student build(String name, int age) {
        return new Student(name,age);
    }
});
---------------------------------------
//用Lambda表达式化简
useStudentBUilder((name, age) -> new Student(name,age));
//方法引用
useStudentBUilder(Student :: new);

来解释为什么可以使用方法引用:

  • 函数式接口

  • 两个方法的参数、返回值是相同的

    //构造方法
    public Student(String name, int age) {
            this.name = name;
            this.age = age;
        }
    //抽象函数的方法体
     Student build(String name, int age) {
            return new Student(name,age);
        }
    

    构造方法这里默认有返回值为:Student

  • 方法存在

  • 方法功能可以满足

不可变集合

什么是不可变集合

​ 是一个长度不可变,内容也无法修改的集合

使用场景

​ 如果某个数据不能被修改,把它防御性地拷贝到不可变集合中是个很好的实践。

​ 当集合对象被不可信的库调用时,不可变形式是安全的。

简单理解:

​ 不想让别人修改集合中的内容

比如说:

1,斗地主的54张牌,是不能添加,不能删除,不能修改的

2,斗地主的打牌规则:单张,对子,三张,顺子等,也是不能修改的

3,用代码获取的操作系统硬件信息,也是不能被修改的

不可变集合分类

  • 不可变的list集合
  • 不可变的set集合
  • 不可变的map集合

不可变的list集合

public class ImmutableDemo1 {
    public static void main(String[] args) {
        /*
            创建不可变的List集合
            "张三", "李四", "王五", "赵六"
        */

        //一旦创建完毕之后,是无法进行修改的,在下面的代码中,只能进行查询操作
        List<String> list = List.of("张三", "李四", "王五", "赵六");

        System.out.println(list.get(0));
        System.out.println(list.get(1));
        System.out.println(list.get(2));
        System.out.println(list.get(3));

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

        for (String s : list) {
            System.out.println(s);
        }

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


        Iterator<String> it = list.iterator();
        while(it.hasNext()){
            String s = it.next();
            System.out.println(s);
        }
        System.out.println("---------------------------");

        for (int i = 0; i < list.size(); i++) {
            String s = list.get(i);
            System.out.println(s);
        }
        System.out.println("---------------------------");

        //list.remove("李四");
        //list.add("aaa");
        list.set(0,"aaa");
    }
}

不可变的Set集合

细节:
当我们要获取一个不可变的Set集合时,里面的参数一定要保证唯一性,即元素不能重复

public class ImmutableDemo2 {
    public static void main(String[] args) {
        /*
           创建不可变的Set集合
           "张三", "李四", "王五", "赵六"


           细节:
                当我们要获取一个不可变的Set集合时,里面的参数一定要保证唯一性
        */

        //一旦创建完毕之后,是无法进行修改的,在下面的代码中,只能进行查询操作
        Set<String> set = Set.of("张三", "张三", "李四", "王五", "赵六");

        for (String s : set) {
            System.out.println(s);
        }

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

        Iterator<String> it = set.iterator();
        while(it.hasNext()){
            String s = it.next();
            System.out.println(s);
        }

        System.out.println("-----------------------");
        //set.remove("王五");
    }
}

不可变的Map集合

键值对个数小于等于10
  • 细节1:

​ 键是不能重复的

  • 细节2:

​ Map里面的of方法,参数是有上限的,最多只能传递20个参数,10个键值对

  • 细节3:

​ 如果我们要传递多个键值对对象,数量大于10个,在Map接口中还有一个方法

public class ImmutableDemo3 {
    public static void main(String[] args) {
       /*
        创建Map的不可变集合
            细节1:
                键是不能重复的
            细节2:
                Map里面的of方法,参数是有上限的,最多只能传递20个参数,10个键值对
            细节3:
                如果我们要传递多个键值对对象,数量大于10个,在Map接口中还有一个方法
        */

        //一旦创建完毕之后,是无法进行修改的,在下面的代码中,只能进行查询操作
        Map<String, String> map = Map.of("张三", "南京", "张三", "北京", "王五", "上海",
                "赵六", "广州", "孙七", "深圳", "周八", "杭州",
                "吴九", "宁波", "郑十", "苏州", "刘一", "无锡",
                "陈二", "嘉兴");

        Set<String> keys = map.keySet();
        for (String key : keys) {
            String value = map.get(key);
            System.out.println(key + "=" + value);
        }

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

        Set<Map.Entry<String, String>> entries = map.entrySet();
        for (Map.Entry<String, String> entry : entries) {
            String key = entry.getKey();
            String value = entry.getValue();
            System.out.println(key + "=" + value);
        }
        System.out.println("--------------------------");
    }
}
键值对个数大于10
public class ImmutableDemo4 {
    public static void main(String[] args) {

        /*
            创建Map的不可变集合,键值对的数量超过10个
        */

        //1.创建一个普通的Map集合
        HashMap<String, String> hm = new HashMap<>();
        hm.put("张三", "南京");
        hm.put("李四", "北京");
        hm.put("王五", "上海");
        hm.put("赵六", "北京");
        hm.put("孙七", "深圳");
        hm.put("周八", "杭州");
        hm.put("吴九", "宁波");
        hm.put("郑十", "苏州");
        hm.put("刘一", "无锡");
        hm.put("陈二", "嘉兴");
        hm.put("aaa", "111");

        //2.利用上面的数据来获取一个不可变的集合

        //获取到所有的键值对对象(Entry对象)
        Set<Map.Entry<String, String>> entries = hm.entrySet();
        //把entries变成一个数组
        Map.Entry[] arr1 = new Map.Entry[0];
        //toArray方法在底层会比较集合的长度跟数组的长度两者的大小
        //如果集合的长度 > 数组的长度 :数据在数组中放不下,此时会根据实际数据的个数,重新创建数组
        //如果集合的长度 <= 数组的长度:数据在数组中放的下,此时不会创建新的数组,而是直接用
        Map.Entry[] arr2 = entries.toArray(arr1);
        //不可变的map集合
        Map map = Map.ofEntries(arr2);
        map.put("bbb","222");


        //Map<Object, Object> map = Map.ofEntries(hm.entrySet().toArray(new Map.Entry[0]));

        Map<String, String> map = Map.copyOf(hm);
        map.put("bbb","222");
    }
}

entries.toArray()方法的形参可以是空,也可以是一个数组。

  • 形参为空:把Set集合entries中的数据返回一个Object类型的数组
  • 形参为数组:把集合中的数据放到这个指定的数组中,数组的长度默认设为零即可;
    • 如果集合的长度 > 数组的长度 :数据在数组中放不下,此时会根据实际数据的个数,重新创建数组
    • 如果集合的长度 <= 数组的长度:数据在数组中放的下,此时不会创建新的数组,而是直接用

因为Map.ofEntries方法中需要的参数是Map.Entry类型的集合,所以在上面的转换中需要的形参是Map.Entry[]

简略写法:

Map<Object, Object> map = Map.ofEntries(hm.entrySet().toArray(new Map.Entry[0]));
  • Map.Entry是一个数据类型,它的对象叫做键值对对象
  • entrySet方法获取所有的键值对,返回一个Set集合

注意:

在JDK10之后为了方便书写,Java为我们提供了另一个方法:copyOf()参数是Map集合

  • 当传递的是不可变集合就返回集合本身
  • 当传递的是可变集合,就会调用我们上面写的代码返回一个不可变Map集合

Stream流

体验Stream

  • 案例需求

    按照下面的要求完成集合的创建和遍历

    • 创建一个集合,存储多个字符串元素
    • 把集合中所有以"张"开头的元素存储到一个新的集合
    • 把"张"开头的集合中的长度为3的元素存储到一个新的集合
    • 遍历上一步得到的集合
  • 原始方式示例代码

    public class MyStream1 {
        public static void main(String[] args) {
            //集合的批量添加
            ArrayList<String> list1 = new ArrayList<>(List.of("张三丰","张无忌","张翠山","王二麻子","张良","谢广坤"));
            //list.add()
    
            //遍历list1把以张开头的元素添加到list2中。
            ArrayList<String> list2 = new ArrayList<>();
            for (String s : list1) {
                if(s.startsWith("张")){
                    list2.add(s);
                }
            }
            //遍历list2集合,把其中长度为3的元素,再添加到list3中。
            ArrayList<String> list3 = new ArrayList<>();
            for (String s : list2) {
                if(s.length() == 3){
                    list3.add(s);
                }
            }
            for (String s : list3) {
                System.out.println(s);
            }      
        }
    }
    
  • 使用Stream流示例代码

    public class StreamDemo {
        public static void main(String[] args) {
            //集合的批量添加
            ArrayList<String> list1 = new ArrayList<>(List.of("张三丰","张无忌","张翠山","王二麻子","张良","谢广坤"));
    
            //Stream流
            list1.stream().filter(s->s.startsWith("张"))
                    .filter(s->s.length() == 3)
                    .forEach(s-> System.out.println(s));
        }
    }
    
  • Stream流的好处

    • 直接阅读代码的字面意思即可完美展示无关逻辑方式的语义:获取流、过滤姓张、过滤长度为3、逐一打印
    • Stream流把真正的函数式编程风格引入到Java中
    • 代码简洁

Stream流的常见生成方式

  1. Stream流的思想

    简单来说,流就像一个流水线

  2. Stream流的三类方法

  • 获取Stream流

    • 创建一条流水线,并把数据放到流水线上准备进行操作
  • 中间方法

    • 流水线上的操作
    • 一次操作完毕之后,还可以继续进行其他操作
  • 终结方法

    • 一个Stream流只能有一个终结方法
    • 是流水线上的最后一个操作

  1. 生成Stream流的方式
  • Collection体系集合

    使用默认方法stream()生成流, default Stream<E> stream()

  • Map体系集合: 把Map转成Set集合,间接的生成流

    1. 使用keySet,valueSet方法,转换为Set集合,使用Set集合中的方法来获取流
    2. 使用entrySet方法,转换为Set集合
  • 数组

    通过Arrays中的静态方法stream生成流

  • 同种数据类型的多个数据

    通过Stream接口的静态方法of(T... values)生成流,数据的类型需要是一致的

    同时需要注意的是:这个方法还可以传入数组(我们说过可变参数在底层中就是数组),但是基本数据类型的数组传入的话,传入的是地址

  • 代码演示

    public class StreamDemo {
        public static void main(String[] args) {
            //Collection体系的集合可以使用默认方法stream()生成流
            List<String> list = new ArrayList<String>();
            Stream<String> listStream = list.stream();
    
            Set<String> set = new HashSet<String>();
            Stream<String> setStream = set.stream();
    
            //Map体系的集合间接的生成流
            Map<String,Integer> map = new HashMap<String, Integer>();
            Stream<String> keyStream = map.keySet().stream();
            Stream<Integer> valueStream = map.values().stream();
            Stream<Map.Entry<String, Integer>> entryStream = map.entrySet().stream();
    
            //数组可以通过Arrays中的静态方法stream生成流
            String[] strArray = {"hello","world","java"};
            Stream<String> strArrayStream = Arrays.stream(strArray);
          
          	//同种数据类型的多个数据可以通过Stream接口的静态方法of(T... values)生成流
            Stream<String> strArrayStream2 = Stream.of("hello", "world", "java");
            Stream<Integer> intStream = Stream.of(10, 20, 30);
        }
    }
    

Stream流中间操作方法

  • 概念

    中间操作的意思是,执行完此方法之后,Stream流依然可以继续执行其他操作

  • 常见方法

    方法名说明
    Stream filter(Predicate predicate)用于对流中的数据进行过滤
    Stream limit(long maxSize)返回此流中的元素组成的流,截取前指定参数个数的数据
    Stream skip(long n)跳过指定参数个数的数据,返回由该流的剩余元素组成的流
    static Stream concat(Stream a, Stream b)合并a和b两个流为一个流
    Stream distinct()返回由该流的不同元素(根据Object.equals(Object) )组成的流
    Stream map()进行数据转换

    注意:

    • 中间方法会返回一个新的Stream流,原来的Stream流只能使用一次,建议使用链式编程
    • 在进行Stream流的操作后并不会影响集合本身

    Stream<String> stream1 = stream.filter(s -> s.length == 3);这个时候再调用stream进行操作就会报错

  • filter代码演示

    public class MyStream3 {
        public static void main(String[] args) {
    //        Stream<T> filter(Predicate predicate):过滤
    //        Predicate接口中的方法	boolean test(T t):对给定的参数进行判断,返回一个布尔值
    
            ArrayList<String> list = new ArrayList<>();
            list.add("张三丰");
            list.add("张无忌");
            list.add("张翠山");
            list.add("王二麻子");
            list.add("张良");
            list.add("谢广坤");
    
            //filter方法获取流中的 每一个数据.
            //而test方法中的s,就依次表示流中的每一个数据.
            //我们只要在test方法中对s进行判断就可以了.
            //如果判断的结果为true,则当前的数据留下
            //如果判断的结果为false,则当前数据就不要.
    //        list.stream().filter(
    //                new Predicate<String>() {
    //                    @Override
    //                    public boolean test(String s) {
    //                        boolean result = s.startsWith("张");
    //                        return result;
    //                    }
    //                }
    //        ).forEach(s-> System.out.println(s));
    
            //因为Predicate接口中只有一个抽象方法test
            //所以我们可以使用lambda表达式来简化
            list.stream().filter(
                    (String s)->{
                        boolean result = s.startsWith("张");
                            return result;
                    }
            ).forEach(s-> System.out.println(s));
    
            list.stream().filter(s ->s.startsWith("张")).forEach(s-> System.out.println(s));
    
        }
    }
    

    注意:

    startswith()是String的方法,判断字符串的首字符是否符合放方法中的字符,如果是:返回true,不是:返回false

    Stream<T> filter(Predicate predicate)这个方法中,参数是Predicate接口的实现类,我们可以采用匿名内部类或者lamda表达式的方式

  • limit&skip代码演示

    public class StreamDemo02 {
        public static void main(String[] args) {
            //创建一个集合,存储多个字符串元素
            ArrayList<String> list = new ArrayList<String>();
    
            list.add("林青霞");
            list.add("张曼玉");
            list.add("王祖贤");
            list.add("柳岩");
            list.add("张敏");
            list.add("张无忌");
    
            //需求1:取前3个数据在控制台输出
            list.stream().limit(3).forEach(s-> System.out.println(s));
            System.out.println("--------");
    
            //需求2:跳过3个元素,把剩下的元素在控制台输出
            list.stream().skip(3).forEach(s-> System.out.println(s));
            System.out.println("--------");
    
            //需求3:跳过2个元素,把剩下的元素中前2个在控制台输出
            list.stream().skip(2).limit(2).forEach(s-> System.out.println(s));
        }
    }
    
  • concat&distinct代码演示

    public class StreamDemo03 {
        public static void main(String[] args) {
            //创建一个集合,存储多个字符串元素
            ArrayList<String> list = new ArrayList<String>();
    
            list.add("林青霞");
            list.add("张曼玉");
            list.add("王祖贤");
            list.add("柳岩");
            list.add("张敏");
            list.add("张无忌");
    
            //需求1:取前4个数据组成一个流
            Stream<String> s1 = list.stream().limit(4);
    
            //需求2:跳过2个数据组成一个流
            Stream<String> s2 = list.stream().skip(2);
    
            //需求3:合并需求1和需求2得到的流,并把结果在控制台输出
    //        Stream.concat(s1,s2).forEach(s-> System.out.println(s));
    
            //需求4:合并需求1和需求2得到的流,并把结果在控制台输出,要求字符串元素不能重复
            Stream.concat(s1,s2).distinct().forEach(s-> System.out.println(s));
        }
    }
    

    distinct方法在底层使用的是HashSet进行的去重操作,所以 在使用的时候需要注意hashcode和equels方法的重写

    concat方法合并的两个流建议类型要一致,不一致的话会进行类型的提升,导致不能使用子类中的方法

  • map

    //1.匿名内部类
    list.stream().map(new Function<String, Integer>() {
    	@Override
    	public Integer apply(String s) {
    		String[] arr = s.split( regex: "-");
    		String ageString = arr[1];
    		int age = Integer.parseInt(ageString);
    		return age;
    
    	}).forEach(s-> System.out.println(s));
    //2.链式编程
    list.stream()
    .map(s-> Integer.parseInt(s.split( regex: "-") [1]))
    .forEach(s-> System.out.println(s));
    

    map返回的类型需要自己进行定义,同时修改流的内容,把需要的类型的内容留下。

Stream流终结操作方法

  • 概念

    终结操作的意思是,执行完此方法之后,Stream流将不能再执行其他操作

  • 常见方法

    方法名说明
    void forEach(Consumer action)对此流的每个元素执行操作
    long count()返回此流中的元素数
  • 代码演示

    public class MyStream5 {
        public static void main(String[] args) {
            ArrayList<String> list = new ArrayList<>();
            list.add("张三丰");
            list.add("张无忌");
            list.add("张翠山");
            list.add("王二麻子");
            list.add("张良");
            list.add("谢广坤");
    
            //method1(list);
            
    //        long count():返回此流中的元素数
            long count = list.stream().count();
            System.out.println(count);
        }
    
        private static void method1(ArrayList<String> list) {
            //  void forEach(Consumer action):对此流的每个元素执行操作
            //  Consumer接口中的方法void accept(T t):对给定的参数执行此操作
            //在forEach方法的底层,会循环获取到流中的每一个数据.
            //并循环调用accept方法,并把每一个数据传递给accept方法
            //s就依次表示了流中的每一个数据.
            //所以,我们只要在accept方法中,写上处理的业务逻辑就可以了.
            list.stream().forEach(
                    new Consumer<String>() {
                        @Override
                        public void accept(String s) {
                            System.out.println(s);
                        }
                    }
            );
          
            System.out.println("====================");
            //lambda表达式的简化格式
            //是因为Consumer接口中,只有一个accept方法
            list.stream().forEach(
                    (String s)->{
                        System.out.println(s);
                    }
            );
            System.out.println("====================");
            //lambda表达式还是可以进一步简化的.
            list.stream().forEach(s->System.out.println(s));
        }
    }
    

Stream流的收集操作

  • 概念

    对数据使用Stream流的方式操作完毕后,可以把流中的数据收集到集合中

    收集方法也算在终结操作中

  • 常用方法

    方法名说明
    R collect(Collector collector)把结果收集到集合中
    R[] toArray(IntFunction<? super extends Object[]> intfunction)把结果收集到数组中
  • 工具类Collectors提供了具体的收集方式

    方法名说明
    public static Collector toList()把元素收集到List集合中
    public static Collector toSet()把元素收集到Set集合中
    public static Collector toMap(Function keyMapper,Function valueMapper)把元素收集到Map集合中

tomap方法较难,需要重点注意

  • 代码演示

    // toList和toSet方法演示 
    public class MyStream7 {
        public static void main(String[] args) {
            ArrayList<Integer> list1 = new ArrayList<>();
            for (int i = 1; i <= 10; i++) {
                list1.add(i);
            }
    
            list1.add(10);
            list1.add(10);
            list1.add(10);
            list1.add(10);
            list1.add(10);
    
            //filter负责过滤数据的.
            //collect负责收集数据.
                    //获取流中剩余的数据,但是他不负责创建容器,也不负责把数据添加到容器中.
            //Collectors.toList() : 在底层会创建一个List集合.并把所有的数据添加到List集合中.
            List<Integer> list = list1.stream().filter(number -> number % 2 == 0)
                    .collect(Collectors.toList());
    
            System.out.println(list);
    
        Set<Integer> set = list1.stream().filter(number -> number % 2 == 0)
                .collect(Collectors.toSet());
        System.out.println(set);
    }
    }
    /**
    Stream流的收集方法 toMap方法演示
    创建一个ArrayList集合,并添加以下字符串。字符串中前面是姓名,后面是年龄
    "zhangsan,23"
    "lisi,24"
    "wangwu,25"
    保留年龄大于等于24岁的人,并将结果收集到Map集合中,姓名为键,年龄为值
    */
    public class MyStream8 {
    	public static void main(String[] args) {
          	ArrayList<String> list = new ArrayList<>();
            list.add("zhangsan,23");
            list.add("lisi,24");
            list.add("wangwu,25");
    
            Map<String, Integer> map = list.stream().filter(
                    s -> {
                        String[] split = s.split(",");
                        int age = Integer.parseInt(split[1]);
                        return age >= 24;
                    }
    
             //   collect方法只能获取到流中剩余的每一个数据.
             //在底层不能创建容器,也不能把数据添加到容器当中
    
             //Collectors.toMap 创建一个map集合并将数据添加到集合当中
    
              // s 依次表示流中的每一个数据
    
              //第一个lambda表达式就是如何获取到Map中的键
              //第二个lambda表达式就是如何获取Map中的值
            ).collect(Collectors.toMap(
                    s -> s.split(",")[0],
                    s -> Integer.parseInt(s.split(",")[1]) ));
    
            System.out.println(map);
    	}
    }
    
  • toArray方法

    // toArray()-----收集流中的数据,放到数组中
    //Object[] arr1 = list.stream().toArray();
    
    //System.out.println(Arrays.toString(arr1));
    
    //IntFunction的泛型:具体类型的数组
    //apply的形参:流中数据的个数,要跟数组的长度保持一致
    //apply的返回值:具体类型的数组
    //方法体:就是创建数组
    
    //toArray方法的参数的作用:负责创建一个指定类型的数组
    //toArray方法的底层,会依次得到流里面的每一个数据,并把数据放到数组当中
    //toArray方法的返回值:是一个装着流里面所有数据的数组
    String[] arr = list. stream(). toArray(new IntFunction<String[ ]>() {
    	@Override
    	public String[] apply(int value) {
    	return new String[value];
    	}
    });
    
    System.out.println(Arrays.toString(arr)) ;
    String[] arr2 = list.stream().toArray(value -> new String[value]);
    
    System.out.println(Arrays.toString(arr2));
    

Stream流的总结

  1. Stream流的作用
    结合了Lambda表达式, 简化集合、数组的操作

  2. Stream的使用步骤

  • 获取Stream流对象
  • 使用中间方法处理数据
  • 使用终结方法处理数据
  1. 如何获取Stream流对象
  • 单列集合:Collection中的默认方法stream
  • 双列集合:不能直接获取
  • 数组:Arrays工具类型中的静态方法stream
  • 一堆零散的数据:Stream接口中的静态方法of
  1. 常见方法

    • 中间方法:filter, limit, skip, distinct, concat, map
    • 终结方法:forEach, count, collect

Stream流综合练习

  • 案例需求

    现在有两个ArrayList集合,分别存储6名男演员名称和6名女演员名称,要求完成如下的操作

    • 男演员只要名字为3个字的前三人
    • 女演员只要姓林的,并且不要第一个
    • 把过滤后的男演员姓名和女演员姓名合并到一起
    • 把上一步操作后的元素作为构造方法的参数创建演员对象,遍历数据

    演员类Actor已经提供,里面有一个成员变量,一个带参构造方法,以及成员变量对应的get/set方法

  • 代码实现

    演员类

    public class Actor {
        private String name;
    
        public Actor(String name) {
            this.name = name;
        }
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    }
    

    测试类

    public class StreamTest {
        public static void main(String[] args) {
            //创建集合
            ArrayList<String> manList = new ArrayList<String>();
            manList.add("周润发");
            manList.add("成龙");
            manList.add("刘德华");
            manList.add("吴京");
            manList.add("周星驰");
            manList.add("李连杰");
      
            ArrayList<String> womanList = new ArrayList<String>();
            womanList.add("林心如");
            womanList.add("张曼玉");
            womanList.add("林青霞");
            womanList.add("柳岩");
            womanList.add("林志玲");
            womanList.add("王祖贤");
      
            //男演员只要名字为3个字的前三人
            Stream<String> manStream = manList.stream().filter(s -> s.length() == 3).limit(3);
      
            //女演员只要姓林的,并且不要第一个
            Stream<String> womanStream = womanList.stream().filter(s -> s.startsWith("林")).skip(1);
      
            //把过滤后的男演员姓名和女演员姓名合并到一起
            Stream<String> stream = Stream.concat(manStream, womanStream);
      
          	// 将流中的数据封装成Actor对象之后打印
          	stream.forEach(name -> {
                Actor actor = new Actor(name);
                System.out.println(actor);
            }); 
        }
    }
    

方法引用

3.1方法引用

  • 什么是方法引用:把已经存在的方法当作函数式接口抽象方法的方法体

  • 方法引用的出现原因

    在使用Lambda表达式的时候,我们实际上传递进去的代码就是一种解决方案:拿参数做操作

    那么考虑一种情况:如果我们在Lambda中所指定的操作方案,已经有地方存在相同方案,那是否还有必要再写重复逻辑呢?答案肯定是没有必要

    那我们又是如何使用已经存在的方案的呢?

    这就是我们要讲解的方法引用,我们是通过方法引用来使用已经存在的方案

  • 代码演示

    public interface Printable {
        void printString(String s);
    }
    
    public class PrintableDemo {
        public static void main(String[] args) {
            //在主方法中调用usePrintable方法
    //        usePrintable((String s) -> {
    //            System.out.println(s);
    //        });
    	    //Lambda简化写法
            usePrintable(s -> System.out.println(s));
    
            //方法引用
            usePrintable(System.out::println);
    
        }
    
        private static void usePrintable(Printable p) {
            p.printString("爱生活爱Java");
        }
    }
    
    

3.2方法引用符和注意事项

  • 方法引用符

    :: 该符号为引用运算符,而它所在的表达式被称为方法引用

  • 推导与省略

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

使用引用方法的注意事项:

  • 需要有函数式接口(引用方法的地方必须是函数式接口)

    Array.sort(arr,new Comparator<Integer>());
    //这里的Comparator是一个函数式接口,可以使用方法引用替换
    
  • 被引用方法必须已经存在

  • 被引用方法的形参和返回值需要跟抽象方法保持一致

  • 被引用方法的功能要满足当前的需求

引用方法的分类

  • 引用静态方法
  • 引用成员方法
    • 其他类的成员方法
    • 本类的成员方法
    • 父类的成员方法
  • 引用构造方法

引用类的静态方法

​ 引用类方法,其实就是引用类的静态方法

  • 格式

    类名::静态方法

  • 范例

    Integer::parseInt

    Integer类的方法:public static int parseInt(String s) 将此String转换为int类型数据

  • 练习描述

    • 定义一个接口(Converter),里面定义一个抽象方法 int convert(String s);
    • 定义一个测试类(ConverterDemo),在测试类中提供两个方法
      • 一个方法是:useConverter(Converter c)
      • 一个方法是主方法,在主方法中调用useConverter方法
  • 代码演示

    public interface Converter {
        int convert(String s);
    }
    
    public class ConverterDemo {
        public static void main(String[] args) {
    
    		//Lambda写法
            useConverter(s -> Integer.parseInt(s));
    
            //引用类方法
            useConverter(Integer::parseInt);
    
        }
    
        private static void useConverter(Converter c) {
            int number = c.convert("666");
            System.out.println(number);
        }
    }
    
  • 使用说明

    Lambda表达式被类方法替代的时候,它的形式参数全部传递给静态方法作为参数

引用对象的实例方法

​ 引用对象的实例方法,其实就引用类中的成员方法

  • 格式

    • 其他对象::成员方法
    • this :: 成员方法(方法在本类中)[^1]
    • super :: 成员方法(方法在父类中)

如果本类是静态方法,(静态方法中没有this和super)就需要创建本类的对象再进行调用new Benlei :: 成员方法

  • 范例

    "HelloWorld"::toUpperCase

    String类中的方法:public String toUpperCase() 将此String所有字符转换为大写


  • 练习描述

    • 定义一个类(PrintString),里面定义一个方法

      public void printUpper(String s):把字符串参数变成大写的数据,然后在控制台输出

    • 定义一个接口(Printer),里面定义一个抽象方法

      void printUpperCase(String s)

    • 定义一个测试类(PrinterDemo),在测试类中提供两个方法

      • 一个方法是:usePrinter(Printer p)
      • 一个方法是主方法,在主方法中调用usePrinter方法
  • 代码演示

    public class PrintString {
        //把字符串参数变成大写的数据,然后在控制台输出
        public void printUpper(String s) {
            String result = s.toUpperCase();
            System.out.println(result);
        }
    }
    
    public interface Printer {
        void printUpperCase(String s);
    }
    
    public class PrinterDemo {
        public static void main(String[] args) {
    
    		//Lambda简化写法
            usePrinter(s -> System.out.println(s.toUpperCase()));
    
            //引用对象的实例方法
            PrintString ps = new PrintString();
            usePrinter(ps::printUpper);
    
        }
    
        private static void usePrinter(Printer p) {
            p.printUpperCase("HelloWorld");
        }
    }
    
    
  • 使用说明

    Lambda表达式被对象的实例方法替代的时候,它的形式参数全部传递给该方法作为参数

引用构造器

​ 引用构造器,其实就是引用构造方法

  • 格式

    类名::new

  • 作用

    实际上就是为了创建类的对象

  • 范例

    Student::new


  • 练习描述

      - 定义一个类(Student),里面有两个成员变量(name,age)
        并提供无参构造方法和带参构造方法,以及成员变量对应的get和set方法
      - 定义一个接口(StudentBuilder),里面定义一个抽象方法
        Student build(String name,int age);
      - 定义一个测试类(StudentDemo),在测试类中提供两个方法
        - 一个方法是:useStudentBuilder(StudentBuilder s)
        - 一个方法是主方法,在主方法中调用useStudentBuilder方法
    
    • 代码演示
    public class Student {
        private String name;
        private int age;
    
        public Student() {
        }
    
        public Student(String name, int age) {
            this.name = name;
            this.age = age;
        }
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        public int getAge() {
            return age;
        }
    
        public void setAge(int age) {
            this.age = age;
        }
    }
    
    public interface StudentBuilder {
        Student build(String name,int age);
    }//函数式接口
    
    public class StudentDemo {
        public static void main(String[] args) {
    
    		//Lambda简化写法
            useStudentBuilder((name,age) -> new Student(name,age));
    
            //引用构造器
            useStudentBuilder(Student::new);
    
        }
    
        private static void useStudentBuilder(StudentBuilder sb) {
            Student s = sb.build("林青霞", 30);
            System.out.println(s.getName() + "," + s.getAge());
        }
    }
    
  • 使用说明

    Lambda表达式被构造器替代的时候,它的形式参数全部传递给构造器作为参数

补充引用方法

使用类名引用成员方法

​ 引用类的实例方法,其实就是引用类中的成员方法

  • 格式

    类名::成员方法

  • 范例

    String::substring
    
    public String substring(int beginIndex,int endIndex) 
    
    从beginIndex开始到endIndex结束,截取字符串。返回一个子串,子串的长度为endIndex-beginIndex
    
    

方法的注意点:

方法引用的规则:

  1. 需要有函数式接口
  2. 被引用的方法必须已经存在
  3. 被引用方法的形参,需要跟抽象方法的第二个形参到最后一个形参保持一致,返回值需要保持一致。
  4. 被引用方法的功能需要满足当前的需求

抽象方法形参的详解:

  • 第一个参数: 表示被引用方法的调用者, 决定了可以引用哪些类中的方法在Stream流当中,第一个参数一般都表示流里面的每一个数据。假设流里面的数据是字符串,那么使用这种方式进行方法引用,只能引用String这个类中的方法

实际上是这个参数调用了引用的方法,所以只能使用跟第一个参数类型一致的类中的方法

  • 第二个参数到最后一个参数:跟被引用方法的形参保持一致,如果没有第二个参数, 说明被引用的方法需要是无参的成员方法

局限性:

  • 不能引用所有类中的成员方法。

  • 是跟抽象方法的第一个参数有关,这个参数是什么类型的,那么就只能引用这个类中的方法。|

  • 练习描述

    - 定义一个接口(MyString),里面定义一个抽象方法:
      String mySubString(String s,int x,int y);
    - 定义一个测试类(MyStringDemo),在测试类中提供两个方法
      - 一个方法是:useMyString(MyString my)
      - 一个方法是主方法,在主方法中调用useMyString方法
    
    
  • 代码演示

    public interface MyString {
        String mySubString(String s,int x,int y);
    }
    
    public class MyStringDemo {
        public static void main(String[] args) {
    		//Lambda简化写法
            useMyString((s,x,y) -> s.substring(x,y));
    
            //引用类的实例方法
            useMyString(String::substring);
    
        }
    
        private static void useMyString(MyString my) {
            String s = my.mySubString("HelloWorld", 2, 5);
            System.out.println(s);
        }
    }
    
  • 使用说明

    ​ Lambda表达式被类的实例方法替代的时候
    ​ 第一个参数作为调用者
    ​ 后面的参数全部传递给该方法作为参数

引用数组的构造方法

Java在底层其实是有数组的构造方法的

我们使用数组的构造方法可以更快的创建一个数组

格式:

数组类型[]::new

细节:

数组的类型需要跟流中的数据类型保持一致

例:

//1.创建集合并添加元素
ArrayList<Integer> list = new ArrayList<>();
... elements: 1, 2, 3, 4, 5);

Collections.addAll(list,
//2.收集到数组当中
---------------------//方法引用
Integer[] arr2 = list.stream().toArray(Integer[]: :new);
System.out.println(Arrays.toString(arr2));

-----------------------------------------
               //匿名内部类
/*Integer[] arr = list. stream().toArray(new IntFunction<Integer[]>() {
	@Override
	public Integer[] apply(int value) {
		return new Integer[value];
	}
}) ;* /

综合练习

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值