A股上市公司传智教育(股票代码 003032)旗下技术交流社区北京昌平校区

 找回密码
 加入黑马

QQ登录

只需一步,快速开始

    今天我们来学习java中的流式编程,流式编程是基于时间的,可以简化代码的编写。
    以下是今天的学习目标:
  • 能够理解流与集合相比的优点
  • 能够掌握常用的流操作
  • 能够通过4种方式使用方法引用
  • 能够使用类和数组的构造器引用

    以下是今天的详细笔记:
Stream流传统方式遍历集合进行过滤传统方式过滤集合中的元素, 要写很多次for循环, 代码重复冗余
传统方式:
[Java] 纯文本查看 复制代码
public class Demo01List {
    public static void main(String[] args) {
        //创建一个List集合,存储姓名
        List<String> list = new ArrayList<>();
        list.add("张无忌");
        list.add("周芷若");
        list.add("赵敏");
        list.add("张强");
        list.add("张三丰");

        // 第一次过滤: 只要以张开头的元素, 存储到一个新的集合中
        List<String> listA = new ArrayList<>();
        for(String s : list){
            if(s.startsWith("张")){  // boolean startsWith("字符串")    endsWith()
                listA.add(s);
            }
        }

        // 第二次过滤: 只要姓名长度为3的人, 存储到一个新集合中
        List<String> listB = new ArrayList<>();
        for (String s : listA) {
            if(s.length()==3){
                listB.add(s);
            }
        }

        // 遍历查看结果
        for (String s : listB) {
            System.out.println(s);
        }
    }
}

使用Stream流式思想遍历集合进行过滤Stream流式思想:
        JDK 8 出现, 是函数式编程中的一大特性
        关注做什么, 而不是怎么做
[Java] 纯文本查看 复制代码
public class Demo02Stream {
    public static void main(String[] args) {
        //创建一个List集合,存储姓名
        List<String> list = new ArrayList<>();
        list.add("张无忌");
        list.add("周芷若");
        list.add("赵敏");
        list.add("张强");
        list.add("张三丰");

        // 使用Stream流式操作
        list.stream()
			.filter(name -> name.startsWith("张"))         // 第一次过滤: 只要以张开头的元素
			.filter(name -> name.length()==3)              // 第二次过滤: 只要姓名长度为3的人
			.forEach(name -> System.out.println(name));    // 遍历查看结果
	}
}

流式思想概述
Stream流式思想处理数据的方式:
        让代码像流水线一样, 一步一步的对数据进行处理, 得到最终的结果
        流也可以看作是一个容器, 里面可以装很多元素, 形成元素流

流相比于集合的2个优点:
        1. Pipelining(管道特性)
                Stream流对象的一些方法调用完毕后, 会返回新的Stream流对象, 类型相同, 可链式调用, 类似于"管道"
        2. 内部迭代特性:
                集合遍历通过 Iterator 或者 增强for, 显式的在集合外部进行迭代, 这叫做外部迭代
                Stream提供了内部迭代的方法 forEach(), 可以直接调用遍历方法

使用Stream流的3个步骤:
        1. 获取数据源 (集合, 数组)
        2. 数据转换
        3. 获得结果
2种获取Stream流的方式
获取Stream流对象的2种方式:
        1. 利用Collection接口中的默认方法 default Stream<E> stream() 方法: 集合转Stream对象
        2. 利用Stream接口中的静态方法 static<T> Stream<T> of(T... values): 数组转Stream对象
       
java.util.Collection<E>接口:
        // 默认方法
        default Stream<E> stream(): 将"集合"转换为Stream对象
       
java.util.stream.Stream<T>接口: 管道接口, 泛型为流中元素的类型
        // 静态方法
        static<T> Stream<T> of(T... values): 将"数组"转换为Stream对象
[Java] 纯文本查看 复制代码
// 集合转换为Stream流对象
List<String> list = new ArrayList<>();
Stream<String> listStream = list.stream();                                // List集合的Stream

Set<String> set = new HashSet<>();
Stream<String> setStream = set.stream();                                  // Set集合的Stream

Map<String,String> map = new HashMap<>();
Stream<String> keyStream = map.keySet().stream();                         // 键的集合的Stream
Stream<String> valueStream = map.values().stream();                       // 值的集合的Stream
Stream<Map.Entry<String, String>> entryStream = map.entrySet().stream();  // 键值对的集合的Stream


// 数组转换为Stream流对象
Stream<Integer> stream = Stream.of(1, 2, 3, 4, 5);            

String[] array = {"a", "b", "c"};
Stream<String> arrayStream = Stream.of(array);

Stream API: 方法分类, forEach()遍历
延迟方法: (具有延迟执行的特性)
        返回值类型 是 Stream接口自身类型的方法, 支持链式调用
                filter(): 过滤
                map(): 映射/转换
                limit(): 截取
                skip(): 跳过
终结方法:
        返回值类型 不是 Stream接口自身类型的方法, 不支持链式调用
        forEach(): 遍历
        count(): 统计
       
注意:
        除了终结方法外, 其余方法均为延迟方法

                             ------------------------------------------------------
创建数据源 ->   filter()过滤 | map()映射 | limit()截取 | skip()跳过  -> forEach() | count()结果
                             ------------------------------------------------------  
  获取流                                                    转换                                                      聚合
                                                              延迟方法                                              终结方法
new ArrayList().filter(...).map(...).limit(...).skip(...).forEach(...);
Stream.of(1,2,3).filter(...).map(...).limit(...).skip(...).count();

java.util.stream.Stream<T>接口: 管道接口
        // 抽象方法
        void forEach(Consumer<? super T> action): 遍历流中的元素进行逐一消费. 并不保证元素的逐一消费动作在流中是被有序执行的






5分钟练习: 使用forEach()遍历集合需求:
定义一个字符串数组: {"张三", "李四", "王五", "赵六", "田七"}
将数组转换为Stream对象, 并使用forEach()方法遍历数组, 打印每个元素到控制台
[Java] 纯文本查看 复制代码
public class Test {
    public static void main(String[] args) {
        String[] arr = {"张三", "李四", "王五", "赵六", "田七"};
        
        // 将数组转换为流对象
        Stream<String> stream = Stream.of(arr);
        // 遍历流
        stream.forEach(new Consumer<String>() {
            @Override
            public void accept(String s) {
                System.out.println(s);
            }
        });
        
        // 使用链式调用+Lambda表达式的标准格式
        Stream.of("张三", "李四", "王五", "赵六", "田七")
                .forEach((String name)-> {
                    System.out.println(name);
                });
        
        // Lambda的省略格式
        Stream.of("张三", "李四", "王五", "赵六", "田七")
                .forEach(name-> System.out.println(name));
    }
}

Stream API: filter()过滤
java.lang.String类:
        boolean startsWith(String prefix): 判断当前字符串是否以参数字符串开头

java.util.stream.Stream<T>接口: 管道接口
        // 抽象方法
        Stream<T> filter(Predicate<? super T> predicate): 过滤符合条件的结果. 返回过滤后的流
       
        boolean test(String name)
5分钟练习: 使用filter()过滤数组
需求:
定义字符串数组: {"张三丰", "张翠山", "赵敏", "周芷若", "张无忌"}
将数组转换为Stream对象, 并过滤出姓张的, 并将元素打印出来
代码:
[Java] 纯文本查看 复制代码
public class Test {
    public static void main(String[] args) {
        // 普通方式
        String[] arr = {"张三丰", "张翠山", "赵敏", "周芷若", "张无忌"};
        Stream<String> stream1 = Stream.of(arr);
        Stream<String> stream2 = stream1.filter(new Predicate<String>() {
            @Override
            public boolean test(String name) {
                return name.startsWith("张");
            }
        });
        stream2.forEach(new Consumer<String>() {
            @Override
            public void accept(String name) {
                System.out.println(name);
            }
        });

        // 链式调用+Lambda表达式
        Stream.of("张三丰", "张翠山", "赵敏", "周芷若", "张无忌")
                .filter(name->name.startsWith("张"))
                .forEach(name-> System.out.println(name));
    }
}

Stream流的特点: 只能使用一次
每次调用延迟方法返回的Stream流对象, 都是经过处理后返回的新的Stream流对象
之前的Stream流在调用方法后, 已经使用过并关闭了, 不能再次使用, 否则会抛出异常:
        java.lang.IllegalStateException: stream has already been operated upon or closed
Stream API: map()映射转换
java.util.stream.Stream<T>接口: 管道接口
        // 抽象方法
        <R> Stream<R> map(Function<T, R> mapper): 将当前流中的T类型的元素, 转换R类型元素, 放入新流并返回
        R apply(T t)
5分钟练习: 使用map()转换数组元素
需求:
定义字符串数组: {"1", "2", "3", "4"}
将数组转换为Stream流, 使用map()方法将流中的字符串都转换为int, 遍历输出转换后的结果
代码:
[Java] 纯文本查看 复制代码
public class Test {
    public static void main(String[] args) {
        String[] arr = {"1", "2", "3", "4"};
        Stream<String> stream1 = Stream.of(arr);
        Stream<Integer> stream2 = stream1.map(new Function<String, Integer>() {
            @Override
            public Integer apply(String s) {
                return Integer.parseInt(s);
            }
        });
        stream2.forEach(i-> System.out.println(i));

        // 链式调用+Lambda表达式
        Stream.of("1", "2", "3", "4")
                .map(s -> Integer.parseInt(s))
                .forEach(i-> System.out.println(i));
    }
}

Stream API: count()统计流中元素个数
java.util.stream.Stream<T>接口: 管道接口
        // 抽象方法
        long count(): 获取流中的元素个数 (终结方法)
5分钟练习: 使用count()统计个数
需求:
创建一个ArrayList集合, 存储整数: 1,2,3,4,5,6,7
将集合转换为Stream流对象, 调用count()获取流中元素的个数, 打印到控制台
代码:
[Java] 纯文本查看 复制代码
public class Test {
    public static void main(String[] args) {
        ArrayList<Integer> list = new ArrayList<>();
        list.add(1);
        list.add(2);
        list.add(3);
        list.add(4);
        list.add(5);
        list.add(6);
        list.add(7);
        long count = list.stream().count();
        System.out.println(count);

        // 利用JDK9提供的快速创建集合方法
        long count2 = List.of(1, 2, 3, 4, 5, 6, 7)
                .stream()
                .count();
        System.out.println(count2);
    }
}

Stream API: limit()获取前n个(只要前n个)
java.util.stream.Stream<T>接口: 管道接口
        // 抽象方法
        Stream<T> limit(long maxSize): 从流中获取前maxSize个. 如果maxSize大于等于元素个数, 则返回所有元素的流
5分钟练习: 使用limit()获取数组中前3个元素
需求:
创建字符串数组: {"美羊羊", "喜洋洋", "懒洋洋", "灰太狼", "红太狼"}
将数组转换为Stream流, 从流中获取前3个元素, 将截取后的流遍历打印输出到控制台
代码:
[Java] 纯文本查看 复制代码
public class Test {
    public static void main(String[] args) {
        String[] arr = {"美羊羊", "喜洋洋", "懒洋洋", "灰太狼", "红太狼"};
//        Stream.of(arr)
//                .limit(3)
//                .forEach(name-> System.out.println(name));

        Stream.of("美羊羊", "喜洋洋", "懒洋洋", "灰太狼", "红太狼")
                .limit(300)  // 超出实际大小, 有多少返回多少
                .forEach(name-> System.out.println(name));
    }
}

Stream API: skip()跳过前n个(不要前n个)
java.util.stream.Stream<T>接口: 管道接口
        // 抽象方法
        Stream<T> skip(long n): 从流中跳过n个元素, 获取后面的元素. 如果n大于等于元素个数, 则全都跳过
5分钟练习: 使用skip()跳过数组中的元素
需求:
创建字符串数组: {"美羊羊", "喜洋洋", "懒洋洋", "灰太狼", "红太狼"}
将数组转换为Stream流, 从流中跳过前3个元素, 遍历打印输出到控制台
代码:
[Java] 纯文本查看 复制代码
public class Test {
    public static void main(String[] args) {
        String[] arr = {"美羊羊", "喜洋洋", "懒洋洋", "灰太狼", "红太狼"};
        Stream.of(arr)
                .skip(3)
                .forEach(name-> System.out.println(name));

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

        Stream.of("美羊羊", "喜洋洋", "懒洋洋", "灰太狼", "红太狼")
                .skip(3)
                .forEach(name-> System.out.println(name));
    }
}

Stream API: 静态方法concat()合并两个流
java.util.stream.Stream<T>接口: 管道接口
        // 静态方法
        static <T> Stream<T> concat(Stream<? extends T> a, Stream<? extends T> b): 合并两个流的元素, 变成一个新的流. 两个流中的元素类型必须相同, 或有共同的父类
5分钟练习: 使用concat()方法合并两个流, 并遍历输出
需求:
创建2个字符串数组:
        {"张三丰", "张翠山", "赵敏", "周芷若", "张无忌"}
        {"美羊羊", "喜洋洋", "懒洋洋", "灰太狼", "红太狼"}
将2个数组分别转换为Stream流对象, 并使用Stream的静态方法concat()将2个流拼接为新的流, 遍历打印新流的元素
代码:
[Java] 纯文本查看 复制代码
public class Test {
    public static void main(String[] args) {
        String[] arr1 = {"张三丰", "张翠山", "赵敏", "周芷若", "张无忌"};
        String[] arr2 = {"美羊羊", "喜洋洋", "懒洋洋", "灰太狼", "红太狼"};
        Stream<String> stream1 = Stream.of(arr1);
        Stream<String> stream2 = Stream.of(arr2);
        Stream<String> concat = Stream.concat(stream1, stream2);
        concat.forEach(name-> System.out.println(name));

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

        // 简化
        Stream.concat(
                Stream.of("张三丰", "张翠山", "赵敏", "周芷若", "张无忌"),
                Stream.of("美羊羊", "喜洋洋", "懒洋洋", "灰太狼", "红太狼")
            ).forEach(name-> System.out.println(name));
    }
}

练习: 集合元素处理-传统方式
需求:
现在有两个ArrayList集合存储队伍当中的多个成员姓名,要求使用传统的for循环(或增强for循环)依次进行以下若干操作步骤:
1. 第一个队伍只要名字为3个字的成员姓名;存储到一个新集合中。
2. 第一个队伍筛选之后只要前3个人;存储到一个新集合中。

3. 第二个队伍只要姓张的成员姓名;存储到一个新集合中。
4. 第二个队伍筛选之后不要前2个人;存储到一个新集合中。

5. 将两个队伍合并为一个队伍;存储到一个新集合中。
6. 根据姓名创建Person对象;存储到一个新集合中。
7. 打印整个队伍的Person对象信息
[Java] 纯文本查看 复制代码
public static void main(String[] args) {
    //第一支队伍
    ArrayList<String> one = new ArrayList<>();

    one.add("迪丽热巴");
    one.add("宋远桥");
    one.add("苏星河");
    one.add("石破天");
    one.add("石中玉");
    one.add("老子");
    one.add("庄子");
    one.add("洪七公");

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

}
    
public class Person {
    
    private String name;

    public Person() {}

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

    @Override
    public String toString() {
        return "Person{name='" + name + "'}";
    }

    public String getName() {
        return name;
    }

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

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

        //第二支队伍
        ArrayList<String> two = new ArrayList<>();
        two.add("古力娜扎");
        two.add("张无忌");
        two.add("赵丽颖");
        two.add("张三丰");
        two.add("尼古拉斯赵四");
        two.add("张天爱");
        two.add("张二狗");
        //3. 第二个队伍只要姓张的成员姓名;存储到一个新集合中。
        ArrayList<String> two1 = new ArrayList<>();
        for (String name : two) {
            if(name.startsWith("张")){
                two1.add(name);
            }
        }
        //4. 第二个队伍筛选之后不要前2个人;存储到一个新集合中。
        ArrayList<String> two2 = new ArrayList<>();
        for (int i = 2; i <two1.size() ; i++) {
            two2.add(two1.get(i)); //i 不包含0 1
        }

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

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

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

练习: 集合元素处理-Stream流方式5分钟练习: 使用Stream流方式处理集合元素
需求:
现在有两个ArrayList集合存储队伍当中的多个成员姓名,要求使用"Stream流方式"依次进行以下若干操作步骤:
1. 第一个队伍只要名字为3个字的成员姓名;存储到一个新集合中。
2. 第一个队伍筛选之后只要前3个人;存储到一个新集合中。

3. 第二个队伍只要姓张的成员姓名;存储到一个新集合中。
4. 第二个队伍筛选之后不要前2个人;存储到一个新集合中。

5. 将两个队伍合并为一个队伍;存储到一个新集合中。
6. 根据姓名创建Person对象;存储到一个新集合中。
7. 打印整个队伍的Person对象信息
[Java] 纯文本查看 复制代码
public static void main(String[] args) {
    //第一支队伍
    ArrayList<String> one = new ArrayList<>();

    one.add("迪丽热巴");
    one.add("宋远桥");
    one.add("苏星河");
    one.add("石破天");
    one.add("石中玉");
    one.add("老子");
    one.add("庄子");
    one.add("洪七公");

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

}
    
public class Person {
    
    private String name;

    public Person() {}

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

    @Override
    public String toString() {
        return "Person{name='" + name + "'}";
    }

    public String getName() {
        return name;
    }

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

代码:
[Java] 纯文本查看 复制代码
public class Test {
    public static void main(String[] args) {
        //第一支队伍
        ArrayList<String> one = new ArrayList<>();

        one.add("迪丽热巴");
        one.add("宋远桥");
        one.add("苏星河");
        one.add("石破天");
        one.add("石中玉");
        one.add("老子");
        one.add("庄子");
        one.add("洪七公");

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

        // 使用Stream流处理
        Stream.concat(
                one.stream().filter(name->name.length()==3).limit(3),
                two.stream().filter(name->name.startsWith("张")).skip(2)
                ).map(name->new Person(name))
                .forEach(person -> System.out.println(person));
    }
}

方法引用方法引用基本介绍
方法引用: Method Reference
        如果Lambda表达式仅仅是调用一个已经存在的方法, 那就可以通过方法引用来替代Lambda表达式
        作用: 简化Lambda表达式
        :: 方法引用运算符, 它所在的表达式被称为方法引用

Lambda表达式写法:
        (String s) -> System.out.println(s)   
    参数传递给System.out.println()方法去打印
方法引用写法:     
        System.out::println
        用System.out.println()方法中代码, 来作为Lambda中重写方法的实现方式

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


方法引用能简化以下场景: (方法名后不要写小括号)
        1. 通过对象名引用成员方法       对象名::成员方法名  System.out::println
        2. 通过类名引用静态方法         类名::静态方法名   i -> Math.abs(i)   Math::abs
        3. 通过super引用父类成员方法    super::父类方法名  ()->super.eat();   super::eat
        4. 通过this引用本类成员方法     this::本类方法名   ()->this.eat();    this::eat
        5. 引用某个类的构造方法         类名::new          name->new Person(name)  Person::new
        6. 引用创建数组的方法           数据类型[]::new    length->new int[length];  int[]::new
[Java] 纯文本查看 复制代码
@FunctionalInterface
public interface Printable {
    //定义字符串的抽象方法
    void print(String s);
}

public class Demo01Printable {

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

    public static void main(String[] args) {
        // 匿名内部类方式
        printString(new Printable(){
            @Override
            public void print(String s){
                System.out.println(s);
            }
        });
        
        //Lambda方式
        printString((s)->{
            System.out.println(s);  // 只是调用了一个已经存在的System.out.println()
        });

		// 方法引用
        printString(System.out::println);
    }
}

方法引用: 通过对象名引用成员方法
通过对象名引用成员方法
        对象名::成员方法名
       
适用场景:
        当Lambda表达式中, 仅仅是"通过某个对象调用已有的方法"时, 就可以用这种方式简化
[Java] 纯文本查看 复制代码
// 定义函数式接口: 模拟Java中已经提供的函数式接口
@FunctionalInterface
public interface Printable {
	// 定义功能: 打印s. 功能怎么实现先不管
    void print(String s);
}

// 定义一个类: 模拟Java中已经定义好的类和方法
public class MethodRefObject {
    // 将字符串变大写再打印出来
    public void printUpperCaseString(String str){
        System.out.println(str.toUpperCase());
    }
}

public class Test {
    
    // 定义方法用于打印Hello, 参数为 打印字符串的方式
    public static void printString(Printable lambda){
        lambda.print("Hello");
    }

    public static void main(String[] args) {
		// 普通Lambda方式
        printString((s)->{
  			// 创建对象, 调用已经存在方法printUpperCase
            MethodRefObject obj  = new MethodRefObject();
            obj.printUpperCaseString(s);
        });
        
        // 使用方法引用简化
        MethodRefObject obj = new MethodRefObject();
        printString(obj::printUpperCaseString);  
        // 直接引用obj的printUpperCaseString方法作为抽象方法print的实现
    }
}

5分钟练习: 简化对象调用方法
[Java] 纯文本查看 复制代码
//需求:
//已知如下代码, 请在Test类中补全main()方法, 分别使用Lambda和方法引用调用printString方法
// 定义函数式接口: 模拟Java中已经提供的函数式接口
@FunctionalInterface
public interface Printable {
	// 定义功能: 打印s. 功能怎么实现先不管
    void print(String s);
}

// 定义一个类: 模拟Java中已经定义好的类和方法
public class MethodRefObject {
    // 将字符串变大写再打印出来
    public void printUpperCaseString(String str){
        System.out.println(str.toUpperCase());
    }
}

public class Test {
    
    // 定义方法用于打印Hello, 参数为 打印字符串的方式
    public static void printString(Printable lambda){
        lambda.print("Hello");
    }

    public static void main(String[] args) {
		// 普通Lambda方式调用printString方法: 创建MethodRefObject对象, 调用已经存在方法printUpperCase

        
        // 方法引用方式调用printString方法: 创建MethodRefObject对象, 直接引用obj的printUpperCaseString方法

    }
}

代码:
[Java] 纯文本查看 复制代码
public class Test {

    // 定义方法用于打印Hello, 参数为 打印字符串的方式
    public static void printString(Printable lambda){
        lambda.print("Hello");
    }

    public static void main(String[] args) {
        // 匿名内部类方式
        printString(new Printable() {
            @Override
            public void print(String s) {
                MethodRefObject obj = new MethodRefObject();
                obj.printUpperCaseString(s);
            }
        });

        // 普通Lambda方式调用printString方法: 创建MethodRefObject对象, 调用已经存在方法printUpperCase
        printString(s->{
            // 利用MethodRefObject中的printUpperCaseString转换为大写打印
            MethodRefObject obj = new MethodRefObject();
            obj.printUpperCaseString(s);
        });

        // 方法引用方式调用printString方法: 创建MethodRefObject对象, 直接引用obj的printUpperCaseString方法
        MethodRefObject obj = new MethodRefObject();
        printString(obj::printUpperCaseString);

        // 一行代码实现
        printString(new MethodRefObject()::printUpperCaseString);
    }
}

方法引用: 通过类名引用静态方法通过类名引用静态方法
        类名::静态方法名  Math.abs(1)   Math::abs
       
适用场景:
        当Lambda表达式中, 仅仅是"通过某个类名调用已有的静态方法"时, 就可以用这种方式简化
[Java] 纯文本查看 复制代码
// 定义函数式接口: 模拟Java中已经提供的函数式接口
@FunctionalInterface
public interface Calcable {
    // 计算整数的绝对值并返回
    int calcAbs(int number);
}

public class Test {
    
    //定义方法用来计算一个数的结果. 参数: 被计算的整数 和 计算的方式
    public static int method(int number, Calcable c){
       return c.calcAbs(number);
    }

    public static void main(String[] args) {
        // Lambda方式
        int number = method(
            -10,
            (n)->{
                return Math.abs(n);  //对参数进行绝对值得计算并返回结果
            });
        System.out.println(number);

        // 方法引用
        int number2 = method(-10, Math::abs); //直接引用Math类中的abs方法作为calcAbs抽象方法的实现
        System.out.println(number2);
    }
}

5分钟练习: 引用静态方法
[Java] 纯文本查看 复制代码
//需求:
//已知如下代码, 请在Test类中的main()方法中, 分别使用Lambda和方法引用调用method方法
// 定义函数式接口: 模拟Java中已经提供的函数式接口
@FunctionalInterface
public interface Calcable {
    // 计算整数的绝对值并返回
    int calcAbs(int number);
}

public class Test {
    
    //定义方法用来计算一个数的结果. 参数: 被计算的整数 和 计算的方式
    public static int method(int number, Calcable c){
       return c.calcAbs(number);
    }

    public static void main(String[] args) {
        // Lambda方式调用method方法: 传递-10和Lambda, 调用Math.abs()方法计算出结果, 并返回
        

        // 方法引用方式调用method方法: 传递-10, 直接引用Math类中的abs方法
        
    }
}

[Java] 纯文本查看 复制代码
public class Test {

    //定义方法用来计算一个数的结果. 参数: 被计算的整数 和 计算的方式
    public static int method(int number, Calcable c){
        return c.calcAbs(number);
    }

    public static void main(String[] args) {
        // Lambda方式调用method方法: 传递-10和Lambda, 调用Math.abs()方法计算出结果, 并返回
        int result1 = method(-10, (number)->{return Math.abs(number);});
        System.out.println(result1);  // 10

        // 方法引用方式调用method方法: 传递-10, 直接引用Math类中的abs方法
        int result2 = method(-10, Math::abs);
        System.out.println(result2);  // 10
    }
}

方法引用: 通过super引用父类成员方法通过super引用父类成员方法
        super::父类方法名
       
适用场景
        当Lambda表达式中, 仅仅是"调用父类某个已有的方法"时, 就可以用这种方式简化
[Java] 纯文本查看 复制代码
// 定义见面打招呼的函数式接口: 模拟Java中已经提供的函数式接口
@FunctionalInterface
public interface Greetable {
    // 见面打招呼的方法
    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!");
    }

    //定义方法用来见面打招呼. 参数是见面打招呼的方式
    public void method(Greetable g){
        g.greet();
    }

    // 因为要使用super, 所以定义了一个成员方法
    public void show(){
        // Lambda方式: 创建父类对象, 用父类对象调用方法
        method(()->{
            Human h = new Human();   // 创建父类Human对象
            h.sayHello();            // 调用父类的sayHello方法
        });

        // Lambda方式: 直接使用super调用父类方法, 省去创建父类对象的麻烦
        method(()->{
            super.sayHello();        // 直接使用super调用父类的sayHello方法
        });

		// 方法引用方式: 直接引用父类的sayHello方法作为greet抽象方法的实现
		method(super::sayHello);    // 其实就是 对象名::成员方法名 只不过对象用super代表
    }

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

5分钟练习: 引用父类方法
[Java] 纯文本查看 复制代码
//需求:
//在Man类中的show()方法中, 补全三种方式调用method方法的代码:
// 定义见面打招呼的函数式接口: 模拟Java中已经提供的函数式接口
@FunctionalInterface
public interface Greetable {
    // 见面打招呼的方法
    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!");
    }

    //定义方法用来见面打招呼. 参数是见面打招呼的方式
    public void method(Greetable g){
        g.greet();
    }

    // 因为要使用super, 所以定义这个非静态方法
    public void show(){
        // Lambda方式调用method方法: 创建父类对象, 用父类对象调用方法
        

        // Lambda方式调用method方法: 直接使用super调用父类方法, 省去创建父类对象的麻烦
        

		// 方法引用方式调用method方法: 直接引用父类的sayHello方法作为greet抽象方法的实现
		
    }

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

代码:
[Java] 纯文本查看 复制代码
public class Man extends Human {

    // 子类重写了父类sayHello的方法
    @Override
    public void sayHello() {
        System.out.println("Hello 我是Man!");
    }

    //定义方法用来见面打招呼. 参数是见面打招呼的方式
    public void method(Greetable g){
        g.greet();
    }

    // 因为要使用super, 所以定义这个非静态方法
    public void show(){
        // Lambda方式调用method方法: 创建父类对象, 用父类对象调用方法
        method(()->{
            Human human = new Human();
            human.sayHello();    // Hello 我是Human!
        });

        // Lambda方式调用method方法: 直接使用super调用父类方法, 省去创建父类对象的麻烦
        method(()->{
            super.sayHello();     // Hello 我是Human!
        });

        // 方法引用方式调用method方法: 直接引用父类的sayHello方法作为greet抽象方法的实现
        method(super::sayHello);  // Hello 我是Human!
    }

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

方法引用: 通过this引用本类成员方法
通过this引用本类成员方法
        this::本类方法名
       
适用场景:
        当Lambda表达式中, 仅仅是"调用本类中某个已有的方法"时, 就可以用这种方式简化
[Java] 纯文本查看 复制代码
// 定义一个表示富有的函数式接口, 模拟Java中提供的函数式接口
@FunctionalInterface
public interface Richable {
    //定义一个想买什么就买什么的方法
    void buy();
}

// Husband类, 表示一个要买房结婚丈夫
public class Husband {
    
    // 本类中已经存在的方法: 买房子的方法
    public void buyHouse(){
        System.out.println("北京二环内买一套四合院!");
    }

    //定义结婚的方法, 参数: 传递Richable接口
    public void marry(Richable r){
        r.buy();
    }

    //定义非常高兴的方法: 因为要调用this, 所以定义这个非静态方法
    public void soHappy(){
        // Lambda方式: 使用this调用本类已经存在的方法
        marry(()->{
            this.buyHouse();
        });

        // 方法引用方式: 直接通过this引用本类中的buyHouse方法
        marry(this::buyHouse);   // 其实也是 对象名::成员方法名  只不过对象名用this表示
    }

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

5分钟练习: 引用本类方法
[HTML] 纯文本查看 复制代码
//需求:
//已知如下代码, 请在Husband类中的main()方法中, 分别使用Lambda和方法引用调用marry()方法
// 定义一个表示富有的函数式接口, 模拟Java中提供的函数式接口
@FunctionalInterface
public interface Richable {
    //定义一个想买什么就买什么的方法
    void buy();
}

// Husband类, 表示一个要买房结婚丈夫
public class Husband {
    
    // 本类中已经存在的方法: 买房子的方法
    public void buyHouse(){
        System.out.println("北京二环内买一套四合院!");
    }

    //定义结婚的方法, 参数: 传递Richable接口
    public void marry(Richable r){
        r.buy();
    }

    //定义非常高兴的方法: 因为要调用this, 所以定义这个非静态方法
    public void soHappy(){
        // Lambda方式调用marry方法: 使用this调用本类已经存在的方法

        // 方法引用方式调用marry方法: 直接通过this引用本类中的buyHouse方法
        
    }

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

代码:
[Java] 纯文本查看 复制代码
public class Husband {

    // 本类中已经存在的方法: 买房子的方法
    public void buyHouse(){
        System.out.println("北京二环内买一套四合院!");
    }

    //定义结婚的方法, 参数: 传递Richable接口
    public void marry(Richable r){
        r.buy();
    }

    //定义非常高兴的方法: 因为要调用this, 所以定义这个非静态方法
    public void soHappy(){
        // Lambda方式调用marry方法: 使用this调用本类已经存在的方法
        marry(()->{
            this.buyHouse();
        });

        // 方法引用方式调用marry方法: 直接通过this引用本类中的buyHouse方法
        marry(this::buyHouse);
    }

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

方法引用: 类的构造方法引用引用某个类的构造方法
        类名::new

使用场景
        当Lambda表达式中, 仅仅是"调用某个类的构造方法来创建一个对象"时, 就可以用这种方式简化
[Java] 纯文本查看 复制代码
// Person类
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;
    }
}

// 定义用于创建Person对象的函数式接口, 模拟Java中已经提供的函数式接口
@FunctionalInterface
public interface PersonBuilder {  // Function转换
    // 根据姓名创建Person对象, 怎么创建需要Lambda来实现
    Person builderPerson(String name);
}

// 测试类
public class Test {
    
    // 定义方法, 用于根据传入的姓名创建一个Person对象并打印名称
    // 参数: 姓名 和 创建Person对象的方式
    public static void printName(String name, PersonBuilder pb){
        Person person = pb.builderPerson(name);
        System.out.println(person.getName());
    }

    public static void main(String[] args) {
        // Lambda方式: 传递姓名和创建对象的方式
        printName("迪丽热巴",(String name)->{
            return new Person(name);  // 仅仅调用了Person类的构造方法
        });

        // 方法引用方式: 直接引用Person类的构造方法
        printName("古力娜扎",Person::new); 
    }
}

5分钟练习: 引用类的构造方法
[Java] 纯文本查看 复制代码
//需求:
//已知如下代码, 请补全Test类中main方法的代码, 分别使用Lambda和方法引用调用printName方法:
// Person类
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;
    }
}

// 定义用于创建Person对象的函数式接口, 模拟Java中已经提供的函数式接口
@FunctionalInterface
public interface PersonBuilder {
    // 根据姓名创建Person对象, 怎么创建需要Lambda来实现
    Person builderPerson(String name);
}

// 测试类
public class Test {
    
    // 定义方法, 用于根据传入的姓名创建一个Person对象并打印名称
    // 参数: 姓名 和 创建Person对象的方式
    public static void printName(String name,PersonBuilder pb){
        Person person = pb.builderPerson(name);
        System.out.println(person.getName());
    }

    public static void main(String[] args) {
        // Lambda方式调用printName方法: 传递"迪丽热巴"和创建Person对象的方式
        

        // 方法引用方式调用printName方法: 传递"迪丽热巴", 直接引用Person类的构造方法
         
    }
}

代码:
[Java] 纯文本查看 复制代码
// 测试类
public class Test {

    // 定义方法, 用于根据传入的姓名创建一个Person对象并打印名称
    // 参数: 姓名 和 创建Person对象的方式
    public static void printName(String name,PersonBuilder pb){
        Person person = pb.builderPerson(name);
        System.out.println(person.getName());
    }

    public static void main(String[] args) {
        // Lambda方式调用printName方法: 传递"迪丽热巴"和创建Person对象的方式
        printName("古力娜扎", name -> {
            return new Person(name);
        });

        // 方法引用方式调用printName方法: 传递"迪丽热巴", 直接引用Person类的构造方法
        printName("迪丽热巴", Person::new);
    }
}

方法引用: 数组的构造方法引用
引用创建数组的方法
        数据类型[]::new

使用场景
        当Lambda表达式中, 仅仅是"创建一个数组对象"时, 就可以用这种方式简化
[Java] 纯文本查看 复制代码
// 定义创建数组的函数式接口, 模拟Java中已经提供的函数式接口
@FunctionalInterface
public interface ArrayBuilder {
    // 根据指定length长度, 创建一个int[]数组. 怎么创建需要我们传递Lambda实现
    int[] builderArray(int length);
}

// 测试类
public class Test {
    
    // 定义方法用于创建数组
    // 参数: 数组的长度 和 创建数组的方式
    public static int[] createArray(int length, ArrayBuilder ab){
        return ab.builderArray(length);
    }

    public static void main(String[] args) {
        // Lambda方式: 传递长度10, 以及创建数组的Lambda表达式
        int[] arr1 = createArray(
            10, 
            (len)->{
                return new int[len];
            });
        System.out.println(arr1.length); //10

        // 方法引用方式: 传递长度10, 和数组的构造方法
        int[] arr2 = createArray(10, int[]::new);
        System.out.println(arr2.length); //10
    }
}

5分钟练习: 引用数组的构造方法
[Java] 纯文本查看 复制代码
//需求:
//已知如下代码, 请补全Test类中main()方法的代码, 分别用Lambda和方法引用方式, 调用createArray方法
// 定义创建数组的函数式接口, 模拟Java中已经提供的函数式接口
@FunctionalInterface
public interface ArrayBuilder {
    // 根据指定length长度, 创建一个int[]数组. 怎么创建需要我们传递Lambda实现
    int[] builderArray(int length);
}

// 测试类
public class Test {
    
    // 定义方法用于创建数组
    // 参数: 数组的长度 和 创建数组的方式
    public static int[] createArray(int length, ArrayBuilder ab){
        return ab.builderArray(length);
    }

    public static void main(String[] args) {
        // Lambda方式调用createArray: 传递长度10, 以及创建数组的Lambda表达式


        // 方法引用方式调用createArray: 传递长度10, 和数组的构造方法

    }
}

代码:
[Java] 纯文本查看 复制代码
public class Test {

    // 定义方法用于创建数组
    // 参数: 数组的长度 和 创建数组的方式
    public static int[] createArray(int length, ArrayBuilder ab){
        return ab.builderArray(length);
    }

    public static void main(String[] args) {
        // Lambda方式调用createArray: 传递长度10, 以及创建数组的Lambda表达式
        int[] arr1 = createArray(10, length -> new int[length]);
        System.out.println(arr1.length);

        // 方法引用方式调用createArray: 传递长度10, 和数组的构造方法
        int[] arr2 = createArray(10, int[]::new);
        System.out.println(arr2.length);
    }
}

今日API
java.util.Collection<E>接口:
        // 默认方法
        default Stream<E> stream(): 将"集合"转换为Stream对象
       
java.util.stream.Stream<T>接口: 管道接口, 泛型为流中元素的类型
        // 静态方法
        static<T> Stream<T> of(T... values): 将"数组"转换为Stream对象
        static <T> Stream<T> concat(Stream<? extends T> a, Stream<? extends T> b): 合并两个流为新流
        // 抽象方法
        void forEach(Consumer<? super T> action): 遍历流中的元素进行逐一消费 (终结方法)
        long count(): 获取流中的元素个数 (终结方法)
        Stream<T> filter(Predicate<? super T> predicate): 过滤符合条件的结果. 返回过滤后的流
        <R> Stream<R> map(Function<? super T, ? extends R> mapper): 将T元素转换为R元素, 返回新的流
    Stream<T> limit(long maxSize): 从流中获取前maxSize个
    Stream<T> skip(long n): 从流中跳过n个元素, 获取后面的元素


方法引用能简化以下场景: (方法名后不要写小括号)
        1. 通过对象名引用成员方法       对象名::成员方法名
        2. 通过类名引用静态方法         类名::静态方法名
        3. 通过super引用父类成员方法    super::父类方法名
        4. 通过this引用本类成员方法     this::本类方法名
        5. 引用某个类的构造方法         类名::new
        6. 引用创建数组的方法           数据类型[]::new





























3 个回复

倒序浏览
虽然说要放假了,但是也不能提前传上来啊,这么兴奋啊~~~
回复 使用道具 举报
6666666666666666666666
回复 使用道具 举报
6666666666666666666666
回复 使用道具 举报
您需要登录后才可以回帖 登录 | 加入黑马