函数式接口

1. Lambda表达式

函数式接口是Java中的一个特殊接口,它只定义了一个抽象方法。函数式接口可以用作Lambda表达式或方法引用的目标类型。

Java 8引入了函数式接口的概念,以支持函数式编程。函数式接口提供了一种简洁的方式来定义函数对象,使得可以将函数作为参数传递、将函数作为返回值返回,以及使用Lambda表达式进行函数式编程。

在Java标准库中,有一些内置的函数式接口,如RunnableComparatorConsumerFunction等。此外,您也可以自定义自己的函数式接口。

package com.wz.Functional01;

//这里有可打印的接口
public interface Printable {
    //提供了一个打印信息的接口方法
    void print(String msg);
}
package com.wz.Functional01;

public class PrintableTest {
    public static void main(String[] args) {
        Printable p = new Printable() {
            @Override
            public void print(String msg) {
                System.out.println(msg);
            }
        };
        p.print("hello world");s
    }
}

函数式接口

构建函数式接口对象,目的式为了调用这个函数式接口中定义的唯一一个接口方法

我们可以直接告诉对象在调用方法时要执行的代码

JDK8提供的lambda表达式

lambda表达式的书写规则

  1. 保留接口方法的参数列表以及后边的方法体
  2. 在参数列表与方法体之间加一个 ->
Printable p = (String msg)  -> {
            System.out.println(msg);
        };

lambda表达式在编写的时候可以省略,省略规则如下:

  1. 因为函数式接口中的参数列表是定死的,因此,参数列表的类型是可以推导,参数列表的类型可以省略
Printable p =(msg)->{
            System.out.println(msg);
        };
		p.print("hello world");
  1. 如果参数列表只有一个参数,()是可以省略的
Printable p = msg->{
            System.out.println(msg);
        };
		p.print("hello world");
  1. 如果方法体的实现只有一行代码,那么{}是可以省略的,这行代码的分号也是可以省略的

如果这条语句是return语句,return也可以不要。

Printable p = msg-> System.out.println(msg);
p.print("hello world");

方法引用的语法

Printable p = System.out::println;

2.延迟执行

在Java中,lambda表达式是一种延迟执行的机制。它允许您将代码块作为参数传递给方法,并在需要时执行该代码块。

当您使用lambda表达式时,它并不立即执行。相反,它会被封装为一个函数式接口的实例,该接口定义了一个抽象方法,lambda表达式会在调用该方法时执行。

package com.wz.lazy;

import java.util.Scanner;

public class MsgBuilderTest {
    public static void main(String[] args) {
//        String s = build("a","b","c");
//        System.out.println(s);
//        System.out.println("-----------------);
//        MsgBuilder msgBuilder = new MsgBuilder(){
//            @Override
//            public String buildMsg(String... infos) {
//                return build(infos);
//            }
//        };
        MsgBuilder msgBuilder = infos -> build(infos);
        System.out.println("输入是否延迟组装字符串:Y/N");
        Scanner scanner = new Scanner(System.in);
        String lazy = scanner.next();
        MsgBuilder builder = infos -> {
            if ("Y".equals(lazy)){
                return build(infos);
            }
            else return null;
        };
        String s = builder.buildMsg("a", "b", "c");
        System.out.println(s);
    }
    private static String build(String... infos){
        StringBuilder builder = new StringBuilder();
        //不定长自变量在使用的时候当作数组使用
        for (String info:infos){
            builder.append(info);
        }
        return builder.toString();
    }

}

main 方法中,我们首先创建了一个 MsgBuilder 接口的实例 msgBuilder,使用 lambda 表达式实现了 buildMsg 方法。

然后,我们通过输入来决定是否延迟组装字符串。使用 Scanner 获取用户输入的字符串,存储在变量 lazy 中。

接下来,我们创建了一个新的 MsgBuilder 实例 builder,使用 lambda 表达式实现了 buildMsg 方法。在 lambda 表达式中,如果 lazy 的值为 "Y",则调用 build 方法组装字符串,否则返回 null

最后,我们调用 builder.buildMsg("a", "b", "c") 来组装字符串,并将结果存储在变量 s 中,并打印出来。

根据用户输入的值,如果输入为 "Y",则会延迟组装字符串,并打印出组装后的结果;如果输入为其他值,则不会组装字符串,打印结果为 null

Consumer接口

Consumer 是 Java 8 中的一个函数式接口,它定义了一个接受一个参数并且没有返回值的操作。

该接口中有一个抽象方法 accept(T t),用于对给定的参数执行特定的操作。

Consumer 接口通常与 forEach 方法一起使用,用于对集合中的每个元素执行某种操作。

package com.wz.Consumer01;

import java.util.function.Consumer;

public class ConsumerDemo {
    public static void main(String[] args) {
//        Consumer<String> c = new Consumer<String>(){
//            @Override
//            public void accept(String s) {
//                System.out.println(s);
//            }
//        };
//        Consumer<String> c = (String s) ->{
//                System.out.println(s);
//        };
        Consumer<String> c = s ->  System.out.println(s);
        c.accept("hello World");
    }
}

首先,我们创建了一个 Consumer 实例 c,该实例接受一个字符串参数,并将其打印到控制台。

然后,我们调用 c.accept("hello World"),将字符串 "hello World" 作为参数传递给 accept 方法,从而触发 Consumer 实例的操作。

当运行这段代码时,将会在控制台输出 "hello World"。

与之前的示例代码相比,这个示例使用了 Lambda 表达式来实现 Consumer 接口的抽象方法 accept。Lambda 表达式使代码更加简洁和易读。

package com.wz.Consumer02;

import java.util.Arrays;
import java.util.List;

public class ConsumerDemo01 {
    public static void main(String[] args) {
        List<Integer> integers = Arrays.asList(1,2,3,4,5);
//        integers.forEach(new Consumer<Integer>() {
//            @Override
//            public void accept(Integer integer) {
//                System.out.println(integer);
//            }
//        });
//        integers.forEach((Integer integer) -> {
//                System.out.println(integer);
//        });
        integers.forEach(integer -> System.out.println(integer));
    }
}

BiConsumer接口

BiConsumer 是一个函数式接口,它接受两个输入参数并且不返回任何结果。它定义了一个抽象方法 accept,用于对给定的两个参数进行操作。

BiConsumer 接口常用于需要对两个参数进行操作的场景,例如遍历集合中的元素并执行某些操作,或者对两个对象进行比较等。

BiConsumer 接口中的方法包括:

  • accept(T t, U u):对给定的两个参数执行操作。
  • andThen(BiConsumer<? super T, ? super U> after):返回一个组合了当前 BiConsumer 和另一个 BiConsumer 的新的 BiConsumer,先执行当前 BiConsumer,然后再执行另一个 BiConsumer
package com.wz.BiConsumer01;

import java.util.HashMap;
import java.util.Map;
import java.util.function.BiConsumer;

public class test01 {
    public static void main(String[] args) {
//        BiConsumer<String,Integer> consumer = new BiConsumer<String, Integer>(){
//            @Override
//            public void accept(String s, Integer integer) {
//                System.out.println(s+"=>"+Integer);
//            }
//        };
//        BiConsumer<String,Integer> consumer = ( s,  integer) -> System.out.println(s+"=>"+integer);
//        consumer.accept("张三",18);
        HashMap<String,Integer> map = new HashMap<>();
        map.put("张三",20);
        map.put("李四",21);
        map.put("王五",22);
        map.put("赵六",23);
//        for(Map.Entry<String, Integer> entry: map.entrySet()){
//            String key = entry.getKey();
//            Integer value = entry.getValue();
//            consumer.accept(key, value);
//        }
//        map.forEach(new BiConsumer<String, Integer>() {
//            @Override
//            public void accept(String key, Integer value) {
//                System.out.println(key + "=>" + value);
//            }
//        });
        map.forEach(( key,  value) -> System.out.println(key + "=>" + value));
    }
}

首先,我们创建了一个 HashMap 实例 map,并向其中添加了一些键值对。

然后,我们使用 forEach 方法和一个 Lambda 表达式来遍历 map 中的每个键值对,并将键和值拼接后打印到控制台。

当运行这段代码时,将会在控制台输出每个键值对拼接后的结果。

Predicate接口 (条件)

Predicate 接口是 Java 8 中的一个函数式接口,用于表示一个条件(或谓词)。它定义了一个抽象方法 test,接受一个参数并返回一个布尔值,表示该参数是否满足条件。

Predicate 接口常用于在集合操作、流操作和函数式编程中进行条件判断。通过使用 Predicate 接口,我们可以定义和组合不同的条件,从而实现更灵活和可复用的代码。

Predicate 接口中常用的方法包括:

  • test(T t):对给定的参数进行条件判断,返回一个布尔值。
  • and(Predicate<? super T> other):返回一个新的 Predicate,表示当前条件与另一个条件的逻辑与操作。
  • or(Predicate<? super T> other):返回一个新的 Predicate,表示当前条件与另一个条件的逻辑或操作。
  • negate():返回一个新的 Predicate,表示当前条件的取反。
import java.util.function.Predicate;

public class Test01 {
    public static void main(String[] args) {
        int a = 10;
        int b = 20;
//        Predicate<Integer> p1 = new Predicate<Integer>() {
//            @Override
//            public boolean test(Integer integer) {
//                return integer>a;
//            }
//        };
//        Predicate<Integer> p2 = new Predicate<Integer>() {
//            @Override
//            public boolean test(Integer integer) {
//                return integer<b;
//            }
//        };
        Predicate<Integer> p1 = integer -> integer > a;
        Predicate<Integer> p2 = integer -> integer < b;

        boolean result1 = p1.and(p2).test(15);//相当于&&
        boolean result2 = p1.negate().test(5);//相当于 !
        boolean result3 = p1.or(p2).test(100);//相当于 ||
        System.out.println(result1);
        System.out.println(result2);
        System.out.println(result3);
    }
}

首先,我们定义了两个整数变量 ab,并分别赋值为 10 和 20。

然后,我们创建了两个 Predicate 实例 p1p2,它们分别表示一个整数是否大于 a 和小于 b

接下来,我们使用 and 方法将 p1p2 进行逻辑与操作,得到一个新的 Predicate 实例,并调用其 test 方法来判断给定的整数是否同时满足 p1p2 的条件。

类似地,我们还使用 negate 方法对 p1 进行取反操作,并调用其 test 方法来判断给定的整数是否不满足 p1 的条件。

最后,我们使用 or 方法将 p1p2 进行逻辑或操作,得到一个新的 Predicate 实例,并调用其 test 方法来判断给定的整数是否满足 p1p2 的条件。

当运行这段代码时,将会在控制台输出三个布尔值,分别表示给定的整数是否满足相应的条件。

应用:

package com.wz.Predicate03;

import java.util.ArrayList;
import java.util.Random;
import java.util.function.Predicate;

public class Test01 {
    public static void main(String[] args) {
        Random random = new Random();
        ArrayList<Student> students = new ArrayList<>();
        for (int i = 0; i<10 ; i++){
            Student stu = new Student();
            stu.setName("user"+i);
            int num= random.nextInt(10);
            stu.setSex(num % 3 == 0?"男":num%3 == 1?"女":"其他");
            stu.setAge(random.nextInt(20)+10);
            System.out.println(stu);
            students.add(stu);
        }
        System.out.println("---------------------");
//        Predicate<Student> p1 = new Predicate<Student>(){
//            @Override
//            public boolean test(Student student) {
//                return "男".equals(student.getSex());
//            }
//        };
//        Predicate<Student> p2 = new Predicate<Student>(){
//            @Override
//            public boolean test(Student student) {
//                return student.getAge()>20;
//            }
//        };
        Predicate<Student> p1 =  student -> "男".equals(student.getSex());
        Predicate<Student> p2 =  student -> student.getAge()>20;
        Predicate<Student> p3 = p1.and(p2);
//        students.forEach(new Consumer<Student>() {
//            @Override
//            public void accept(Student student) {
//                if (p3.test(student)){
//                    System.out.println(student);
//                }
//            }
//        });
        students.forEach( student->{
                if (p3.test(student)){
                    System.out.println(student);
                }
        });

    }
}

这是一个使用 Predicate 接口的示例代码。

代码中创建了一个 Student 类,包含姓名、性别和年龄属性。然后使用 Random 类生成了一些随机的学生数据,并将它们添加到一个 ArrayList 中。

接下来,定义了三个 Predicate 实例 p1p2p3,分别表示性别为男、年龄大于20和性别为男且年龄大于20的条件。

最后,使用 forEach 方法遍历学生列表,并使用 test 方法判断学生是否满足 p3 的条件。如果满足条件,则打印该学生的信息。

这段代码的功能是筛选出性别为男且年龄大于20的学生,并将其打印出来。

Function接口

Function 接口包含一个抽象方法 apply(T t),该方法接收一个类型为 T 的参数,并返回类型为 R 的结果。

使用 Function 接口可以方便地进行数据转换和处理。常见的用法包括将一个对象转换为另一个对象、对对象进行操作并返回结果等。

package com.wz.Function01;

import java.util.function.Function;

public class FunctionDemo {
    public static void main(String[] args) {
//        Function<String, Integer> f = new Function<String,Integer>(){
//            @Override
//            public Integer apply(String s) {
//                return Integer.parseInt(s);
//            }
//        };
        Function<String, Integer> f =  s -> Integer.parseInt(s);
        Integer num = f.apply("10");
        System.out.println(num);
        System.out.println("------------");

//        Function<Student, Integer> f2 = new Function<Student, Integer>(){
//            @Override
//            public Integer apply(Student student) {
//                return student.getAge();
//            }
//        };
        Function<Student, Integer> f2 = student -> student.getAge();
        Student s = new Student();
        s.setName("张三");
        s.setSex("男");
        s.setAge(18);
        Integer integer = f2.apply(s);
        System.out.println(integer);
    }
}

这段代码演示了如何使用 Function 接口进行数据转换和处理。

在代码中,首先定义了一个 Function 实例 f,它将字符串转换为整数。使用 lambda 表达式的方式定义了 f,并重写了 apply 方法,将字符串转换为整数。

然后使用 f.apply("10") 将字符串 "10" 转换为整数,并将结果赋值给 num 变量。最后打印出 num 的值。

接下来,定义了另一个 Function 实例 f2,它接收一个 Student 对象,并返回该学生对象的年龄。同样使用 lambda 表达式的方式定义了 f2,并重写了 apply 方法,获取学生对象的年龄。

创建一个 Student 对象 s,设置其姓名、性别和年龄。然后使用 f2.apply(s) 获取学生对象 s 的年龄,并将结果赋值给 integer 变量。最后打印出 integer 的值。

IO练习题

  1. 拷贝一张图片,从一个目录到另外一个目录下
package com.wz.work;

import java.io.*;

public class test01 {
    /**
     * 1. 拷贝一张图片,从一个目录到另外一个目录
     */
    public static void main(String[] args) {
        String source = "D:\\io\\IO流理解图.png";
        String dest = "D:\\io\\io\\IO流理解图.png";
        copy(new File(source),new File(dest));
    }
    public static void copy(File source,File dest){
        File parentFile = dest.getParentFile();
        if (!parentFile.exists()){
            parentFile.mkdirs();
        }
        try(InputStream in = new FileInputStream(source);
            OutputStream os = new FileOutputStream(dest)){
            byte[] buffer = new byte[2048];
            int len;
            while ((len=in.read(buffer))!=-1){
                os.write(buffer,0,len);
            }
        }catch (IOException e){
            e.printStackTrace();
        }
    }
}

代码使用 InputStream 从源文件中读取数据,然后使用 OutputStream 将数据写入目标文件中,实现文件的拷贝。

main 方法中,指定了源文件的路径和目标文件的路径,并调用了 copy 方法来执行拷贝操作。

copy 方法中,首先通过 dest.getParentFile() 获取目标文件的父目录,并使用 mkdirs() 方法创建父目录(如果不存在)。接下来,通过 InputStream 从源文件中读取数据,使用 OutputStream 将数据写入目标文件中,直到读取到文件末尾(read() 方法返回 -1)。

需要注意的是,在代码中使用了 try-with-resources 语句来自动关闭流资源,这样可以确保资源在使用完毕后被正确关闭,避免资源泄漏。


  1. 将一个文件的内容按行倒序保存

注意:Reader or Writer完成之后要手动关闭资源

package com.wz.work01;

import java.io.*;
import java.util.ArrayList;

public class Test01 {
    /**
     *将一个文件的内容按行倒序保存
     */
    public static void main(String[] args) {

        String path="d:/io/data.txt";
        //构建一个集合,来存储读取的每一行的数据
        ArrayList<String> lines = new ArrayList<>();
        try(BufferedReader reader = new BufferedReader(new FileReader(path))){
            String line;
            while ((line=reader.readLine())!=null){
                lines.add(line);
            }
            reader.close();
            BufferedWriter writer = new BufferedWriter(new FileWriter(path));
            for (int i = lines.size()-1; i >=0 ; i--) {
                writer.write(lines.get(i));
                writer.newLine();
            }
            writer.flush();
            writer.close();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

首先使用 BufferedReader 读取文件的每一行,并将其存储在 lines 集合中。然后,使用 BufferedWriterlines 集合中的内容按照倒序写入文件。最后,通过调用 flush() 方法刷新缓冲区,并调用 close() 方法关闭 BufferedWriter


某老师使用文件记录了5位学生成绩:

张华,60

李刚,70

龙华,75

金凤,72

李飞, 80

后来该老师发现将李刚和李飞的成绩录反了,请使用IO流相关知识将其修正过来

package com.wz.work02;

public class Student {
    private String name;
    private double score;

    public Student() {
    }

    public Student(String name, double score) {
        this.name = name;
        this.score = score;
    }

    public String getName() {
        return name;
    }

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

    public double getScore() {
        return score;
    }

    public void setScore(double score) {
        this.score = score;
    }

    @Override
    public String toString() {
        return name + ","+ score ;
    }
}
package com.wz.work02;

import java.io.*;
import java.util.ArrayList;

public class Test01 {
    /**
     * 某老师使用文件记录了5位学生成绩:
     * 张华,60
     * 李刚,70
     * 龙华,75
     * 金凤,72
     * 李飞, 80
     * 后来该老师发现将李刚和李飞的成绩录反了,请使用IO流相关知识将其修正过来
     */
    public static void main(String[] args) {
        String path = "d:/io/work.txt";
        update(path);
    }
    public static void update(String path){
        ArrayList<Student> stuList = new ArrayList<>();
            try(BufferedReader reader = new BufferedReader(new FileReader(path))){
                Student ligang = null;
                Student lifei = null;
                String line;
                while((line=reader.readLine())!=null){
                    String[] arr = line.split(",");
                    Student stu = new Student();
                    stu.setName(arr[0]);
                    stu.setScore(Integer.parseInt(arr[1]));
                    if ("李刚".equals(stu.getName())){
                        ligang=stu;
                    }else if ("李飞".equals(stu.getName())){
                        lifei=stu;
                    }
                    stuList.add(stu);
                }
                reader.close();
                double score1 = ligang.getScore();
                double score2 = lifei.getScore();
                ligang.setScore(score2);
                lifei.setScore(score1);

                BufferedWriter writer = new BufferedWriter(new FileWriter(path));
                for (Student stu : stuList) {
                    writer.write(stu.toString());
                    writer.newLine();
                }
                writer.flush();
                writer.close();
            } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

首先,定义了一个名为 Student 的类,该类包含学生的姓名和成绩属性,并提供了相应的 getter 和 setter 方法。

update 方法中,首先创建了一个 ArrayList 对象 stuList,用于存储学生对象。

然后,使用 BufferedReader 类从指定的文件路径 path 中读取文件内容。在读取过程中,每次读取一行数据,并使用逗号分隔符将姓名和成绩分割为数组 arr。接着,创建一个 Student 对象 stu,将姓名和成绩分别设置为数组中对应的值,并将该对象添加到 stuList 中。

同时,还定义了两个 Student 对象 liganglifei,初始值为 null

接下来,遍历 stuList 中的每个学生对象,如果学生姓名为"李刚",则将该学生对象赋值给 ligang;如果学生姓名为"李飞",则将该学生对象赋值给 lifei

完成遍历后,关闭 BufferedReader

然后,将 liganglifei 对象的成绩进行交换,通过临时变量 score1score2 进行交换操作。

最后,使用 BufferedWriter 类将修改后的学生信息重新写入到文件中。遍历 stuList,将每个学生对象的姓名和成绩以字符串的形式写入文件,并在每个学生信息之间添加换行符。

写入完成后,关闭 BufferedWriter


  1. 定义一个用户类,包含账号和密码以及对应的getter/setter方法,然后实现将5个用户信息存储在文件中,要求使用序列化完成。
package com.wz.work03;

import java.io.Serializable;

public class User implements Serializable {
    private String username;
    private String password;

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

}
package com.wz.work03;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.util.ArrayList;
import java.util.List;

public class Test01 {
    /**
     * 定义一个用户类,包含账号和密码以及对应的getter/setter方法,然后实现将5个用户信息存储在文件中,要求使用序列化完成。
     */
    public static void main(String[] args) {
        String path = "d:/io/user.obj";
        ArrayList<User> userList = new ArrayList<>();
        for(int i=0; i<5; i++){
            User user = new User();
            user.setUsername("user" + i);
            user.setPassword("123456");
            userList.add(user);
        }
        saveUsers(userList, path);
    }
    private static void saveUsers(List<User> userList,String path){
        File file = new File(path);
        File parentFile = file.getParentFile();
        if (!parentFile.exists()){
            parentFile.mkdirs();
        }
        try {
            try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(path))) {
                oos.writeObject(userList);
                oos.flush();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

首先,定义了一个名为 User 的用户类,该类包含账号和密码属性,并提供了相应的 getter 和 setter 方法。

main 方法中,首先创建一个 ArrayList 对象 userList,用于存储用户对象。

然后,使用循环生成5个用户对象,并将每个用户对象的账号设置为"user"加上循环变量的值,密码设置为固定值"123456",然后将用户对象添加到 userList 中。

接着,调用 saveUsers 方法,将 userList 和文件路径 path 作为参数传递进去。

saveUsers 方法中,首先创建一个 File 对象 file,表示要保存用户信息的文件。然后,通过 file 获取父目录的 File 对象 parentFile

接下来,判断 parentFile 是否存在,如果不存在,则使用 mkdirs 方法创建父目录。

然后,使用 ObjectOutputStream 类将 userList 对象写入文件。在 try-with-resources 语句中创建 ObjectOutputStream 对象 oos,并使用 FileOutputStream 包装 path,将 oos 写入文件中。在写入完成后,调用 flush 方法刷新缓冲区,并自动关闭 oos

最后,在 main 方法中指定了文件路径为 "d:/io/user.obj",并调用 saveUsers 方法将用户信息保存到文件中。