Bootstrap

[Java]函数式编程

Lambda函数

JDK8新增的语法形式, 使用Lambda函数替代某些匿名内部类对象,从而让程序代码更简洁,可读性更好。

基本使用

lambda表达式只能简化函数式接口的匿名内部类写法

// 1.定义抽象类
abstract class Animal {
    public abstract void crt();
}

public class LambdaDome {
    public staitc void main(String[] args) {
       // 2.使用匿名内部类, 创建抽象类的子类对象
       Animal a = new Animal() {
           @Override
           public void cry() {
               sout("猫喵喵的叫")
           }
       }

       // 3. 错误示范, 代码报错
       // lambda表达式并不能简写所有匿名内部类的写法
       Animal a = () -> {
            sout("猫喵喵的叫")
        }
        
       a.cry(); // 猫喵喵的叫
       
    }
}
// 1.定义函数式接口
// 定义: 有且只有一个抽象方法的接口称为函数式接口
// 注解: 约束函数式接口的注解, 不符合要求会报错
@FunctionalInterface
interface Swim {
    void swimming();
}

public class LambdaDome {
    public staitc void main(String[] args) {
       // 2.使用匿名内部类, 创建接口类的子类对象
       Swim a = new Swim() {
           @Override
           public void swimming() {
               sout("老师狗爬式游泳")
           }
       }

       // 3. 使用lambda表达式简化匿名内部类写法
       Swim a = () -> {
            sout("老师狗爬式游泳")
        }
        
       a.swimming(); // 老师狗爬式游泳
       
    }
}
  1. 什么是函数式接口? 有且仅有一个抽象方法的接口。
  2. 大部分函数式接口,上面都会有 @Functionallnterface 注解, 用于约束当前接口必须是函数式接口。

简化规则

用于进一步简化Lambda表达式的写法。

  1. 参数类型全部可以省略不写。
  1. 如果只有一个参数, “()”也可以省略,但多个参数不能省略
  2. 如果Lambda表达式中只有一行代码,大括号可以不写,同时要省略分号“;”
  3. 如果这行代码是return语句,也必须去掉return。
public class Test {
    public static void main(String[] args) {
        // 需求: 创建一个登录窗口,窗口上有一个登录按钮
        JFrame win = new JFrame("登录窗口");
        win.setSize(300, 200);
        win.setLocationRelativeTo(null); // 居中展示
        win.setDefaultCloseOperaion(JFrame.EXIT_ON_CLOSE);

        JPanel penel new JPanel();
        win.add(penel);

        JButton btn = new JButton("登录");
      
        // 2.匿名内部类: 快速创建对象, 传给方法
        // btn.addActionListener(new ActionListener{
        //       public void actionPerfirmed(ActionEvent e) {
        //          sout("按钮点击了")
        //       }
        // });

        // 3. 使用lambda表达式的规则简化语法
        btn.addActionListener(e -> sout("按钮点击了"));

        win.setVisible(true);
    }
}
public class Test {
    public static void main(String[] args) {
        // 需求: 完成数组排序, 加深匿名内部类的使用
        // 1. 准备数组,存放学生对象
        student[] students = new student[4];
        students[0] = new Student( name:"殷素素",age:35,height:171.5,sex:'女');
        students[1] = new Student( name:"杨幂",age:28,height: 168.5,sex:'女');
        students[2] = new student( name:"张无忌"age:25,height:181.5,sex:'男');
        students[3] = new student( name:"刘亦菲",age: 36,height: 168,sex:'女');

        // 2.使用数组的API, 对数组按照对象的年龄升序排序
        // Arrays.sort(students, new Comparator<Student>() {
        //     @Override
        //     public int compare(Student o1, Student o2) {
        //         // 按对象年龄升序排序
        //         return o1.getAge() - o2.getAge();
        //         // 按对象年龄降序排序
        //         return o2.getAge() - o1.getAge();
        //     }
        // });

         //3.使用lambda表达式的规则简化语法
         Arrays.sort(students, (o1,o2) -> o1.getAge() - o2.getAge());
        
        // 3.遍历数组中的对象并输出
        for (int i = 0; i<students.length; i++) {
            student s = students[i];
            sout(s);
        }
        
    }
}

方法引用

进一步简化Lambda表达式, 遇到可以简化场景时使用(IDEA会提示)

静态方法引用

类名::静态方法

如果某个Lambda表达式里只是调用一个静态方法, 并且 "->"前后参数的形式一致, 就可以使用静态方法引用

public class Student {
    private String name;
    private int age;
    private double height;
    private String sex;

    // 提供静态方法
    public static int compareByAge(Student s1, Student s2) {
        return s1.getAge() - s2.getAge()
    }
}

public class Test {
    public static void main(String[] args) {
        // 1. 准备数组,存放学生对象
        student[] students = new student[4];
        students[0] = new Student( name:"殷素素”,age:35,height:171.5,sex:'女');
        students[1] = new Student( name:"杨幂",age:28,height: 168.5,sex:'女');
        students[2] = new student( name:"张无忌"age:25,height:181.5,sex:'男');
        students[3] = new student( name:"刘亦菲",age: 36,height: 168,sex:'女');

        // 2.使用数组的API, 对数组按照对象的年龄升序排序
        // Arrays.sort(students, (o1, o2) -> o1.getAge() - o2.getAge());

        // 3.使用lambda表达式调用静态方法
        // Arrays.sort(students, (o1, o2) -> Student.compareByAge(o1, o2));

        // 3.使用静态方法引用进一步简化
        Arrays.sort(students, Student::compareByAge);
        
        // 4.遍历数组中的对象并输出
        for (int i = 0; i<students.length; i++) {
            student s = students[i];
            sout(s);
        }
        
    }
}

实例方法引用

对象名::实例方法

如果某个Lambda表达式里只是调用一个实例方法, 并且"->"前后参数的形式一致, 就可以使用实例方法引用

public class Student {
    private String name;
    private int age;
    private double height;
    private String sex;

    // 提供实例方法
    public int compareByAge(Student s1, Student s2) {
        return s1.getAge() - s2.getAge()
    }
}

public class Test {
    public static void main(String[] args) {
        // 1. 准备数组,存放学生对象
        student[] students = new student[4];
        students[0] = new Student( name:"殷素素”,age:35,height:171.5,sex:'女');
        students[1] = new Student( name:"杨幂",age:28,height: 168.5,sex:'女');
        students[2] = new student( name:"张无忌"age:25,height:181.5,sex:'男');
        students[3] = new student( name:"刘亦菲",age: 36,height: 168,sex:'女');

        // 2.使用数组的API, 对数组按照对象的年龄升序排序
        // Arrays.sort(students, (o1, o2) -> o1.getAge() - o2.getAge());

        // 3.使用lambda表达式调用实例方法
        Student s1 = new Student();
        // Arrays.sort(students, (o1, o2) -> s1.compareByAge(o1,o2));

        // 3.使用实例方法引用进一步简化
        Arrays.sort(students, s1::compareByAge);
        
        // 4.遍历数组中的对象并输出
        for (int i = 0; i<students.length; i++) {
            student s = students[i];
            sout(s);
        }
        
    }
}

特定类型的方法引用

特定类的名称::方法

如果某个Lambda表达式里只是调用一个特定类型的实例方法, 并且前面参数列表中的第一个参数作为方法的主调, 后面的所有参数都是作为该实例方法的入参, 此时可以使用特定类型的方法引用

public class Demo {
    public static void main(String[] args) {
        // 需求:有一个字符申数组,里面有一些人的名字都是,请按照名字的首字母升序排序
         String[] names = {"Tom", "Jerry", "Bobi", "曹操", "Mike", "angela", "Dlei", "Jack", "Rose", "Andy", "caocao"};

        // 1.对数组排序: Arrays.sort(数组, 比较器对象)
        Arrays.sort(names);  // 默认就是按照首字母的编号升序排序
        System.out.println(Arrays.toString(names)); // [Andy, Bobi, Jack, Jerry, Mike, Rose, Tom, angela, caocao, 曹操, Dlei]
        
        // 2.需要忽略首字母的大小写进行升序排序(java官网默认是搞不定的,需要自己指定比较规则)
        Arrays.sort(names, new Comparator<String>() {
            @Override
            public int compare(String o1, String o2) {
                return o1.compareToIgnoreCase(o2); // java提供了字符串按照首字母忽略大小写比较的方法
            }
        });
        System.out.println(Arrays.toString(names)); // [Andy, angela, Bobi, caocao, Dlei, Jack, Jerry, Mike, Rose, Tom, 曹操]

        // 3.使用特定类型的方法引用简化代码
        Arrays.sort(names, String::compareToIgnoreCase);
        System.out.println(Arrays.toString(names)); // [Andy, angela, Bobi, caocao, Dlei, Jack, Jerry, Mike, Rose, Tom, 曹操]
        
    }
}

构造器引用

类名::new

如果某个Lambda表达式里只是在创建对象, 并且"->"前后参数情况一致, 就可以使用构造器引用

// 汽车类
class Car {
    private String name;
}

// 函数式接口
@FunctionalInterface
interface CarFactory {
    Car getCar(String name);
}

public class Demo {
    public static void main(String[] args) {
        // 1.创建接口的匿名内部类对象
        CarFactory cf = new CarFactory() {
            @Override
            public Car getCar(String name) {
                return new Car(name);
            }
        }

        // 2.使用lambda表达式简写
        CarFactory cf = name -> new Car(name);

        // 3.构造器引用进一步简化
        CarFactory cf = Car::new;
        
        Car c1 = cf.getCar("奔驰");
        System.out.println(c1); // Car(name=奔驰)
    }
}

Stream流

认识流

Jdk8开始新增的一套API(iava.util.stream.*),可以用于操作集合或者数组的数据

  1. 先得到集合或者数组的Stream流。
  2. 然后调用Stream流的方法对数据进行处理。
  3. 获取处理的结果。

示例代码

public class Dome {
    public static void main(String[] args) {
        // 1.认识stream流的使用
        // 需求: 把集合中所有以“张”开头,且是3个字的元素存储到一个新的集合
        ArrayList<String> list = new ArrayList<>();
        list.add("张无忌");
        list.add("周芷若");
        list.add("赵敏");
        list.add("张强");
        list.add("张三丰");

        // 2.使用传统方式完成需求
        List<String> newList = new ArrayList<>();
        for (String l : list) {
            if (l.startsWith("张") && l.length() == 3) {
                newList.add(l);
            }
        }
        // [张无忌, 张三丰]
        System.out.println(newList);

        // 3.使用stream流完成需求
        List<String> newList2 = list.stream()
                .filter(l -> l.startsWith("张"))
                .filter(l -> l.length() == 3)
                .collect(Collectors.toList());
        // [张无忌, 张三丰]
        System.out.println(newList2);
    }
}
  1. Stream流大量的结合了Lambda的语法风格来编程,功能强大,性能高效,代码简洁,支持链式编程

获取流

1.获取集合的Stream流

public class Dome2 {
    public static void main(String[] args) {
        // 获取Stream流
        // 1.获取Collection集合流
        // 使用Collection接口提供的stream()方法获取流
        Collection<String> list = new ArrayList<>();
        Stream<String> s1 = list.stream();

        // 2.获取Map集合流
        Map<String, Integer> map = new HashMap<>();
        // 获取键流
        Stream<String> s2 = map.keySet().stream();
        // 获取值流
        Stream<Integer> s3 = map.values().stream();
        // 获取键值对流
        Stream<Map.Entry<String, Integer>> s4 = map.entrySet().stream();
    }
}
2.获取数组的Stream流

public class Dome2 {
    public static void main(String[] args) {
        // 获取Stream流
         String[] arr = {"a", "b", "c", "d"};
        // 3.获取数组流
        // 使用Stream类中的静态方法of()
        Stream<String> s5 = Stream.of(arr);
        // 使用Arrays类中的静态方法stream()
        Stream<String> s6 = Arrays.stream(arr);
    }
}

处理流

中间方法指的是调用完成后会返回新的Stream流,可以继续使用(支持链式编程),

public class Dome3 {
    public static void main(String[] args) {
        // 使用stream流的中间方法处理数据
        ArrayList<String> list = new ArrayList<>();
        list.add("张无忌");
        list.add("周芷若");
        list.add("赵敏");
        list.add("张强");
        list.add("张三丰");

        // 1.过滤方法
        list.stream()
                .filter(s -> s.startsWith("张") && s.length() == 3)
                .forEach(System.out::println); // 张无忌, 张三丰

        // 2.排序方法
        List<Double> scores = new ArrayList<>();
        scores.add(99.9);
        scores.add(66.1);
        scores.add(88.7);
        scores.add(66.1);
        scores.add(72.3);
        scores.add(88.7);
        // 默认是升序排序  66.1 72.3 88.7 99.9
        scores.stream()
                .sorted()
                .forEach(System.out::println);
        // 指定排序规则(降序) 99.9 88.7 72.3 66.1
        scores.stream()
                .sorted((o1, o2) -> Double.compare(o2, o1))
                .forEach(System.out::println);

        // 3.截取方法,
        // 只要前3名 99.9 88.7 72.3
        scores.stream()
                .sorted((o1, o2) -> Double.compare(o2, o1))
                .limit(3)
                .forEach(System.out::println);
        // 跳过前2名 72.3 66.1
        scores.stream()
                .sorted((o1, o2) -> Double.compare(o2, o1))
                .skip(2)
                .forEach(System.out::println);

        // 4.去重方法 99.9 66.1 88.7 72.3
        // 如果自定义对象需要去重, 该对象必须重写hashCode和equals方法
        scores.stream()
                .distinct()
                .forEach(System.out::println);

        // 5.映射/加工方法: 把流里面的元素进行加工, 得到新的集合
        // 成绩是:99.9分 成绩是:66.1分 成绩是:88.7分 成绩是:66.1分 成绩是:72.3分 成绩是:88.7分
        scores.stream()
                .map(s -> "成绩是:" + s + "分")
                .forEach(System.out::println);

        // 6.合并流
        Stream<String> s1 = Stream.of("张无忌", "赵敏", "张三丰");
        Stream<Integer> s2 = Stream.of(11, 22, 31);
        Stream<Object> s3 = Stream.concat(s1, s2);
        System.out.println(s3.count()); // 6
    }
}

终结流

使用Stream是为了方便的操作集合和数组, 操作完成后把结果收集到数组或集合中, 才是最终的目的

  1. 终结流

  • 补充: Optional容器中的元素需要通过get()方法获取出来

  1. 收集流

代码示例

public class Teacher {
    private String name;
    private int age;
    private double salary;

    //... 省略构造器和get/set方法 ...

    @Override
    public String toString() {
        return "Teacher{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", salary=" + salary +
                '}' + '\n';
    }
}
public class Dome4 {
    public static void main(String[] args) {
        // 掌握终结Stream流的方法
        List<Teacher> list = List.of(
                new Teacher("张三", 18, 5000),
                new Teacher("李四", 28, 6000),
                new Teacher("王五", 38, 7000),
                new Teacher("赵六", 48, 8000)
        );
        // 1.遍历终结
        // Teacher{name='王五', age=38, salary=7000.0} Teacher{name='赵六', age=48, salary=8000.0}
        list.stream().filter(t -> t.getSalary() > 6000).forEach(System.out::println);

        // 2.获取结果数量
        long count = list.stream().filter(t -> t.getSalary() > 6000).count();
        System.out.println(count); // 2

        // 3.获取最大值 (根据工资)
        Teacher max = list.stream().max(Comparator.comparing(Teacher::getSalary)).get();
        System.out.println(max); // Teacher{name='赵六', age=48, salary=8000.0}

        // 4.获取最小值 (根据年龄)
        Teacher min = list.stream().min(Comparator.comparing(Teacher::getAge)).get();
        System.out.println(min); // Teacher{name='张三', age=18, salary=5000.0}

    }
}
public class Dome5 {
    public static void main(String[] args) {
        // 掌握收集Stream流的方法
        List<Teacher> list = List.of(
                new Teacher("张三", 18, 5000),
                new Teacher("李四", 28, 6000),
                new Teacher("王五", 38, 7000),
                new Teacher("赵六", 48, 8000)
        );

        // 1. 把流收集到list集合中
        List<Teacher> list1 = list.stream().
                filter(s -> s.getName().startsWith("张")).
                collect(Collectors.toList());
        System.out.println(list1); // [Teacher{name='张三', age=18, salary=5000.0}]

        // 2. 把流收集到set集合中
        Set<Teacher> list2 = list.stream().
                filter(s -> s.getName().startsWith("张"))
                .collect(Collectors.toSet());
        System.out.println(list2);// [Teacher{name='张三', age=18, salary=5000.0}]

        // 3. 把流收集到数组中
        Object[] list3 = list.stream()
                .filter(s -> s.getName().startsWith("张"))
                .toArray();
        System.out.println(Arrays.toString(list3)); // [Teacher{name='张三', age=18, salary=5000.0}]

        // 4. 把流收集到Map集合中: 键是老师的名字, 值是老师的薪水
        // 4.1完整写法
//        Map<String, Double> map = list.stream().collect(Collectors.toMap(new Function<Teacher, String>() {
//            @Override
//            public String apply(Teacher teacher) {
//                return teacher.getName();
//            }
//        }, new Function<Teacher, Double>() {
//            @Override
//            public Double apply(Teacher teacher) {
//                return teacher.getSalary();
//            }
//        }));
        
        // 4.2lambda简写
//        Map<String, Double> map = list.stream().
//                collect(Collectors.toMap(teacher -> teacher.getName(), teacher -> teacher.getSalary()));
        
        // 4.3方法引用简写
        Map<String, Double> map = list.stream().
                collect(Collectors.toMap(Teacher::getName, Teacher::getSalary));

        System.out.println(map); // {李四=6000.0, 张三=5000.0, 王五=7000.0, 赵六=8000.0}
    }
}

;