匿名类_Lambda..

1.匿名类

对于接口或者类的实现类 如果在整个项目中只使用了一次的话 那么就可以使用匿名类
(匿名类包括类的定义和对象的定义 但是缺乏类名 所以称之为匿名类)

作为参数 进行传递

 public class Person {
    public void eat(Eatable eatable){
         System.out.println("name:" + eatable.name() + " energy:" + eatable.energy());
    }
}
public interface Eatable {
    String name();
    int energy();
}
public class Main {
  public static void main(String[] args) {
      Person person = new Person();
      person.eat(new Eatable(){
         @Override
         public String name(){
           return "chocolate";
         }
         @Override
         public int energy(){
           return 100;
         }
      });
  }
}

赋值给变量 然后作为参数进行传递

public class Person {
    public void eat(Eatable eatable){
         System.out.println("name:" + eatable.name() + " energy:" + eatable.energy());
    }
}
public interface Eatable {
    String name();
    int energy();
}
public class Main {
  public static void main(String[] args) {
      Person person = new Person();
      Eatable eatable = new Eatable(){
         @Override
         public String name(){
           return "chocolate";
         }
         @Override
         public int energy(){
           return 100;
         }
      };
      person.eat(eatable);
      person.eat(eatable);
  }
}

1.使用注意

不能定义除了编译时常量以外的static成员

匿名类只能访问final或者有效final的局部变量

匿名类可以直接访问外部类的所有成员(即使被private修饰)
如果想要直接访问实例成员的话 那么就需要将匿名类定义在实例相关的代码块中(这是因为如果将匿名类定义在类相关的代码块中的话 那么可以在没有实例的前提下访问匿名类所在的静态方法 执行其中的代码之后 就发现因为没有实例导致没有实例成员 所以无法成功访问实例成员)

public class Person {
    public void eat(Eatable eatable){
         System.out.println("name:" + eatable.name() + " energy:" + eatable.energy());
    }
}
public interface Eatable {
    String name();
    int energy();
}
public class Main {
  // 定义实例变量
  public int no = 10;
  public static void main(String[] args) {
      Main main = new Main();
      main.test1();
  }
  public void test1(){
      Person person = new Person();
      Eatable eatable = new Eatable(){
         @Override
         public String name(){
           System.out.println(no);
           return "chocolate";
         }
         @Override
         public int energy(){
           return 100;
         }
      };
      person.eat(eatable);
      person.eat(eatable);
  }
}

匿名类不可以有构造方法 但是可以有初始化块

匿名类和局部类有很多特性是很相似的 可以一起记忆

2.实例

1.实例一(代码传递)

现在我有这么一个需求:
我要统计一下执行100次for循环的时间 我利用匿名类解决

public class Times {
    // 定义一个接口 为代码块的接口
    public interface Block{
        // 定义一个抽象方法 
        void execute();
    }
    // 定义一个方法 用于计算不同的代码段的执行时间
    public static void test(Block block){
        long begin = System.currentTimeMillis();
        block.execute();
        long end = System.currentTimeMillis();
        double duration = (end - begin) / 1000.0;
        System.out.println("一共持续了" + duration + "秒");
    }
}
public class Main {
    public static void main(String[] args){
        Times.test(new Times.Block(){
            @Override
            public void execute(){
                int n = 100;
                for(int i = 0; i < n; ++i){
                    System.out.println(i);
                }  
            }
        });
    }
}

2.实例二(代码传递)

现在我有这么一个需求:
就是统计一下执行10000次字符串拼接的时间

public class Times {
    // 定义一个接口 为代码块的接口
    public interface Block{
        // 定义一个抽象方法 
        void execute();
    }
    // 定义一个方法 用于计算不同的代码段的执行时间
    public static void test(Block block){
        long begin = System.currentTimeMillis();
        block.execute();
        long end = System.currentTimeMillis();
        double duration = (end - begin) / 1000.0;
        System.out.println("一共持续了" + duration + "秒");
    }
}
public class Main {
    public static void main(String[] args){
        Times.test(new Times.Block(){
            @Override
            public void execute(){
                String str = "";
                for(int i = 0; i < 10000; ++i){
                    str += i;                    
                }  
            }
        });
    }
}

当我们想要将匿名类作为参数进行传递的话 那么我们可以有两种做法
一种是将每种不同的实现储存在接口的不同实现类
一种是将每种不同的实现类储存在不同方法中 然后通过接口的匿名类调用前面不同实现类的方法

public class Times {
    // 定义一个接口 为代码块的接口
    public interface Block{
        // 定义一个抽象方法 
        void execute();
    }
    // 定义一个方法 用于计算不同的代码段的执行时间
    public static void test(Block block){
        long begin = System.currentTimeMillis();
        block.execute();
        long end = System.currentTimeMillis();
        double duration = (end - begin) / 1000.0;
        System.out.println("一共持续了" + duration + "秒");
    }
}
public class Main {
    public static void main(String[] args){
        Times.test(new Times.Block(){
            @Override
            public void execute(){
                test2();    
            }
        });
    }
    public static void test1(){
        String str = "";
        for(int i = 0; i < 10000; ++i){
            str += i;
        }
    }
    public static void test2(){
        int n = 100;
        for(int i = 0; i < n; ++i){
            System.out.println(i);
        }
    }
}

3.实例三(回调)

我们先来解释一下回调这个行为
所谓回调 就是将某种特定行为(常见的是函数)作为参数进行传递 并且满足延迟执行的特性(相对于定义后立马执行而言的) 其实代码传递也是回调

我们现在有这么一个需求 就是处理get请求 将处理的步骤储存在接口的匿名类中

// 网络工具类
public class Networks {
    // 首先定义一个接口 用于存放请求完毕以后执行的操作 由于不是定义完之后立马调用的 而是延迟调用的 所以说称之为回调
    public interface Block{
        // 请求成功以后执行的代码
        void success(Object response);
        // 请求失败后执行的代码
        void failure();
    }
    public static void get(String url, Block block){
        // 一般get请求的第一步是根据指定的url发送一个异步请求 这边不体现 等到后面学过之后就知道了

        // 第二步就是处理我们发送的请求 回馈用户是请求成功 还是请求失败
        boolean result = true;
        if(result){
            Object response = null;
            block.success(response);
        }else{
            block.failure();
        }
    }
}
public class Main {
    public static void main(String[] args) {
        Networks.get("https://baidu.com?name=axihh&age=2", new Networks.Block() {
            @Override
            public void success(Object response) {
                System.out.println("请求成功了");
            }

            @Override
            public void failure() {
                System.out.println("请求失败了");
            }
        });
    }
}

4.实例四(过滤器)

现在有这么一个需求:
我要对指定文件夹下的所有文件名按照一定的条件进行过滤操作 比如我想要获取指定文件夹下的带有类字的文件名组成的字符串数组 比如我想要获取指定文件夹下的所有后缀是pptx的文件名组成的字符串数组
反正就是不能够固定死这个过滤条件
所以我们需要提供一个接口的实现类 用于提供多样的实现方式 但是由于说这个实现类在整个项目中只使用过一次 所以我们将实现类改成匿名类会更好一点

public class Files {
    // 提供一个接口 用于定义过滤器的过滤条件
    public interface Filter{
        // 提供一个抽象方法 交由实现类去进行实现
        boolean accept(String fileName);
    }
    // 提供一个方法 用于获取指定路径下的所有文件名组成的字符串数组
    public static String[] getAllFileNames(String dir, Filter filter){
        // 1.根据指定路径获取所有文件名组成的字符串数组
        String[] allFileNames = new String[]{};
        // 2.就是遍历获取到的字符串数组中的所有字符串 然后将符合条件的储存到结果数组中
        for(String fileName: allFileNames) {
            if (filter.accept(fileName)) {
                
            }
        }
        // 3.返回结果数组
        return null;// 伪代码
    }
}
public class Main {
    public static void main(String[] args) {
        Files.getAllFileNames("D://.....", new Files.Filter() {
            @Override
            public boolean accept(String fileName) {
                return fileName.contains("类");
            }
        });
    }
}

5.实例五(排序)

现在有这么一个需求:
对一个乱序的数组进行正序打印
我们可以使用JDK中的java.util.Arrays类对数组进行排序操作

public class Main {
    public static void main(String[] args) {
        // 如果我们想要对数组进行排序操作然后进行正序打印的话
        Integer[] arrays = new Integer[]{11, 33, 22};
        Arrays.sort(arrays);
        System.out.println(Arrays.toString(arrays));// 打印的结果是数组形式的数组
        System.out.println(arrays);// 打印的结果是数组对象类型@数组对象哈希值
    }
}

但是Arrays.sort的默认排序方式是正序排序 所以我们如果想要实现数组的逆序排序的话 就需要对Comparator接口进行实现 我们是通过匿名类的方式进行实现最合适 因为这个实现类就只是用了一次
他的排序规则储存在Comparator接口中的int compare(Object o1, Object o2)抽象方法中
如果返回值大于0的话 那么说明o1 > o2
如果返回值等于0的话 那么说明o1 = o2
如果返回值小于0的话 那么说明o1 < o2
并且他会将他所认为大的放在他所认为小的右边 将他所认为小的放在他所认为大的左边
并且比较的时候是将每一个位置和剩余的位置统统进行了比较 但是会进行去重操作
(Integer[] arrays = new Integer[]{11, 33, 22}; 他会先比较0和1 然后比较0和2 最后比较1和2)
所以如果我们想要对一个数组进行逆序排序的话 那么就可以将返回值设置为o2 - o1 这样的话 那么他就会认为实际上比较大的是小的 会将其放置于左边 实际上比较小的是大的 会将其放置在右边

public class Main {
    public static void main(String[] args) {
        // 如果我们想要对数组进行排序操作然后进行正序打印的话
        Integer[] arrays = new Integer[]{11, 33, 22};
        Arrays.sort(arrays, new Comparator<Integer>(){
            //
            @Override
            public int compare(Integer o1, Integer o2) {
                return o2 - o1;
            }
        });
        System.out.println(Arrays.toString(arrays));
    }
}

2.Lambda表达式

我们首先值得注意的是:
所有接口都不能够显示继承某一个类 但是所有的接口的默认隐式的继承自Object类 所以说在Comparator接口中的equals实际上不是抽象方法 而是来自Object类中的方法 如果你去调用该方法 实际上是去Object类中调用

Lambda表达式是Java8以后才有的语法

所谓函数式接口 指的是只包含一个抽象方法的接口
可以在该接口上方加上一个@FunctionalInterface用于限制该接口的行为
如果我们在加上此注解以后 仍然要添加接口的话 那么他就会报错

当我们的匿名类实现的是函数式接口时 可以使用Lambda表达式 比如:Comparator接口就是一个典型的函数式接口

1.实例

Lambda表达式的格式:
(抽象方法的参数列表)->{参数列表的方法体}

public class Main {
    public static void main(String[] args) {
        // 如果我们想要对数组进行排序操作然后进行正序打印的话
        Integer[] arrays = new Integer[]{11, 33, 22};
        Arrays.sort(arrays, (Integer o1, Integer o2) -> {return o2 - o1;});
        System.out.println(Arrays.toString(arrays));
    }
}

2.Lambda表达式的简化

1.对于所有的Lambda表达式来说 我们统统都可以省略参数列表的参数类型
2.如果参数列表中只有一个参数的话 那么我们就可以省略参数列表的()
3.如果参数列表中由其他数量的参数的话 那么我们不可以省略参数列表的()
4.如果方法体中只有一条语句的话 那么我们可以省略{}和return语句以及分号

public class Main {
    public static void main(String[] args) {
        // 如果我们想要对数组进行排序操作然后进行正序打印的话
        Integer[] arrays = new Integer[]{11, 33, 22};
        Arrays.sort(arrays, (o1, o2)->o2 - o1);
        System.out.println(Arrays.toString(arrays));
    }
}

3.Lambda表达式的使用注意

Lambda表达式只能访问final或者有效final修饰的局部变量
Lambda表达式中 方法体不会引入新的作用域 但是参数会引入新的作用域 就相当于方法体其实是直接暴露在所在代码块中的 而参数则不然

public class OuterClass {
    // 定义一个外部类的成员变量
    private int age = 20;
    // 定义一个内部类
    public class InnerClass{
        // 定义一个内部类的成员变量
        private int age = 10;
        // 定义一个方法
        void inner(){
            // 定义一个和参数同名的局部变量
            int v = 4;// error
            // 通过Lambda表达式定义一个函数式接口的实现类
            Testable t = v -> {
                System.out.println(v);// 3
                System.out.println(age);// 由于this是指向InnerClass对象的 所以说访问的是其类内部的age变量
                System.out.println(this.age);// 同上所示
                System.out.println(OuterClass.this.age);// 这个语句访问的是InnerClass的age变量
                System.out.println(InnerClass.this.age);// 这个语句访问的是OuterClass的age变量
            };
            t.test(3);
            System.out.println(v);// error
        }
    }
    public static void main(String[] args) {
        new OuterClass().new InnerClass().inner();
    }
}

age和this.age为什么打印的结果是InnerClass中的age呢 这是因为方法体和参数都没有引入新的作用域 而是直接暴露在所在代码块中的 所以他的this是指向InnerClass对象的 所以说访问的也是内部类的指定变量 并且你还不能够Lambda表达式所在的代码块中去访问Lambda表达式中的参数 这是因为参数虽然没有引入新的作用域但是他是有些特权的 所以你不能无法访问到指定的参数 并且你还不能在Lambda表达式中定义和参数同名的变量 这是因为参数不会引入新的作用域导致的(Java允许不同的作用域定义同名的变量 值得注意的是初始化块并没有引入新的作用域)

如果将上述的需求改成匿名类去实现的话 那么打印结果有会有什么区别呢

public class OuterClass {
    // 定义一个外部类的成员变量
    private int age = 20;
    // 定义一个内部类
    public class InnerClass{
        // 定义一个内部类的成员变量
        private int age = 10;
        // 定义一个方法
        void inner(){
            int v = 4;// 允许
            // 通过Lambda表达式定义一个函数式接口的实现类
            Testable t = new Testable() {
                @Override
                public void test(int v) {
                    System.out.println(v);// 3
                    System.out.println(age);// 10
                    System.out.println(this.age);// error
                    System.out.println(OuterClass.this.age);// 20
                    System.out.println(InnerClass.this.age);// 10
                }
            };
            t.test(3);
            System.out.println(v);// error
        }
    }
    public static void main(String[] args) {
        new OuterClass().new InnerClass().inner();
    }
}

对于匿名类来说 参数和方法体都引入了新的作用域
我们还是主要讨论一下age和this.age两个访问语句
从打印结果可以看出 age可以打印 而且打印的结果是内部类的age this.age则不能够被打印
对于age来说 由于我们的匿名类或者Lambda相当于局部类 所以说可以直接访问外部类的所有成员(即使是实例成员 如果被定义在了实例相关的代码块中 依然可以进行访问) 所以说他访问的结果就是外部类 即InnerClass类中的age
对于this.age来说 我们的this是指向这个匿名类对象 但是由于我们的匿名类中没有定义指定的变量 又由于我们的匿名类没有继承任何父类 所以既不能在本类中找到 又不能向上进行寻找 所以说不论通过什么渠道都无法访问到指定的变量 所以只好报错了
从上述说明可以知道 其实this.变量和变量并不是完全等价的 在一些特殊情况中 还是有些区别的 比如在局部类中进行访问

3.方法引用

如果Lambda表达式仅仅是调用某个方法的话 那么就可以通过方法引用进行简化
其中的简化方式有四种 分别是:
引用类方法、引用指定对象的实例方法、引用指定类型的任意对象的实例方法、引用构造方法

1.引用类方法

引用类方法时 方法引用的格式为:类名::类方法

public interface Testable {
    void test(int v1, int v2);
}
public class Main {
    public static void main(String[] args) {
        // 通过匿名类的形式对接口进行实现
        Testable t1 = new Testable() {
            @Override
            public int test(int v1, int v2) {
                return Math.max(v1, v2);
            }
        };
        System.out.println(t1.test(10, 20));// 20
        // 通过Lambda表达式的形式对接口进行实现
        Testable t2 = (v1, v2) -> Math.max(v1, v2);
        System.out.println(t2.test(10, 20));// 20
        // 通过方法引用的形式对接口进行实现
        Testable t3 = Math::max;
        System.out.println(t3.test(10, 20));// 20
    }
}

2.引用特定对象的实例方法

当我们引用的是特定对象的实例方法时 我们的格式为:对象名::实例方法

public interface Testable {
    void test(int v);
}
public class Person {
    // 定义一个setter方法 用于打印参数age
    public void setAge(int age){
        System.out.println("Person-setAge-" + age);
    }
}
public class Main {
    static void execute(Testable t, int v){
        t.test(v);
    }
    public static void main(String[] args) {
        // 首先我们先将Testable接口中的test抽象方法的实现设置为调用System.out(PrintStream对象)中的println方法
        // 1.Lambda表达式的形式
        execute(v -> System.out.println(v), 10);
        // 2.方法引用的形式
        execute(System.out::println, 20);
        // 其次我们将Testable接口中的test抽象方法的实现设置为调用自定义类中的setter方法
        // 1.Lambda表达式的形式
        execute(v -> new Person().setAge(v), 10);
        // 2.方法引用的形式
        execute(new Person()::setAge, 20);
    }
}

1.引用当前类中定义的实例方法

格式还是对象名::实例方法 只不过对象名变成了this引用

public interface Testable {
    void test(int v);
}
public class Person {
    // 定义一个setter方法 用于打印参数age
    public void setAge(int age){
        System.out.println("Person-setAge-" + age);
    }
    public void show(){
        // 首先通过Lambda表达式进行接口的实现 实现中是对Person对象的setAge方法的调用
        Testable t1 = v -> this.setAge(v);
        // 然后通过方法引用的方式进行接口的实现 视线中是对Person对象的setAge方法的调用
        Testable t2 = this::setAge;
        // 然后调用t1中的抽象方法test
        t1.test(10);
        // 然后调用t2中的抽象方法test
        t2.test(10);
    }
}
public class Main {
    public static void main(String[] args) {
        Person person = new Person();
        person.show();
    }
}

2.引用父类中定义的实例方法

格式还是:对象名::实例方法 只不过对象变成了super引用

public interface Testable {
    void test(int v);
}
public class Person {
    // 定义一个setter方法 用于打印参数age
    public void setAge(int age){
        System.out.println("Person-setAge-" + age);
    }

}
public class Student extends Person{
    @Override
    public void setAge(int age) {
        System.out.println("Student-setAge-" + age);
    }
    public void show(){
        // 1.首先通过Lambda表达式对接口进行实现 具体实现为调用父类中的setAge方法
        Testable t1 = v -> super.setAge(v);
        // 2.然后通过方法引用的方式对接口进行实现 具体实现为调用父类中的setAge方法
        Testable t2 = super::setAge;
        // 然后分别调用两个对象中的test方法
        t1.test(10);
        t2.test(10);
    }

}
public class Main {
    public static void main(String[] args) {
        Student student = new Student();
        student.show();
    }
}

3.引用特定类型的任意对象的实例方法

该方法引用的格式为:类名::实例方法
我们其实可以发现 第二种方式是将抽象方法的参数全部作为方法体中调用方法的参数 但是这个案例中 并没有全部作为方法体中调用方法的参数 所以 而是一部分成为调用者 一部分成为参数
由于参数作为调用者属于不确定的对象 所以属于特定类型的任意对象

public class Main {
    public static void main(String[] args) {
        // 对指定数组进行排序操作
        String[] arrays = new String[]{"acc", "Abc", "bcc", "Bbc"};
        // 通过Arrays.sort对指定数组进行排序操作
        // 1.通过Lambda表达式的形式对指定接口进行实现 进行的是降序的操作
//        Arrays.sort(arrays, (o1, o2) -> o2.compareToIgnoreCase(o1));
        // 2.通过方法引用的方式对指定接口进行实现 如果只有方法引用单独存在的话 那么只能进行升序操作
        Arrays.sort(arrays, String::compareToIgnoreCase);
        System.out.println(Arrays.toString(arrays));
    }
}

4.引用构造方法

格式为:类名::new

public interface Testable {
    Object test(int v);
}
public class Person {
    // 提供一个构造方法
    public Person(int age){
        System.out.println("Person-age-" + age);
    }
}
public class Main {
    public static void main(String[] args) {
        // 首先是通过Lambda表达式的方式实现一个接口
        Testable t1 = v -> new Person(v);
        // 然后返回上述对象中的抽象方法
        System.out.println(t1.test(10));
        // 接着通过方法引用的方式实现一个接口
        Testable t2 = Person::new;
        System.out.println(t2.test(20));
    }
}

4.常用的函数式接口

在java.util.function包中定义了许多常用的函数式接口供我们使用
比如:
Supplier、Consumer、Predicate、Function等

1.Supplier

在这里插入图片描述

现在有这么一个需求:
我想要打印一下参数中第一个不为空且长度不为0的字符串

public class Main {
    public static void main(String[] args) {
        String str1 = "jack";
        String str2 = "rose";
        System.out.println(getFirstNotEmptyString(str1, str2));
    }
    public static String getFirstNotEmptyString(String str1, String str2){
        if(str1 != null && str1.length() > 0)return str1;
        if(str2 != null && str2.length() > 0)return str2;
        return null;
    }
}

这是最普通的解决方案 但是他有一个缺陷就是:当我str1已经满足条件的话 那么str2我压根就不会去对他进行一个使用(这边主要是对他进行一个判断操作) 那么我们对他构建一个具体的字符串也就毫无意义了 那么有没有一种手段就是能够让str2在他必要的时候才去构建出来呢
其实就是引入Supplier接口 起到了延迟创建的功能

public class Main {
    public static void main(String[] args) {
        String str1 = "jack";
        System.out.println(getFirstNotEmptyString(str1, new Supplier<String>() {
            @Override
            public String get() {
                System.out.println("我调用了get方法");
                return "rose";
            }
        }));
    }

    public static String getFirstNotEmptyString(String str1, Supplier<String> str2Supplier){
        if(str1 != null && str1.length() > 0)return str1;
        if(str2Supplier == null)return null;
        String str2 = str2Supplier.get();
        if(str2 != null && str2.length() > 0)return str2;
        return null;
    }
}

从这个打印结果来看 我们明显可以知道他确实没有打印get方法中的打印语句 也就没有为str2构建具体的字符串 我们的目的达到了

而且上述代码还可以将匿名类简化成Lambda表达式

public class Main {
    public static void main(String[] args) {
        String str1 = "jack";
        System.out.println(getFirstNotEmptyString(str1, () -> {
            System.out.println("我调用了get方法");
            return "rose";
        }));
    }

    public static String getFirstNotEmptyString(String str1, Supplier<String> str2Supplier){
        if(str1 != null && str1.length() > 0)return str1;
        if(str2Supplier == null)return null;
        String str2 = str2Supplier.get();
        if(str2 != null && str2.length() > 0)return str2;
        return null;
    }
}

但是其实我们也可以自定义一个类似结构的接口来解决这个需求

public class Main {
    private interface GetString{
        public String get();
    }
    public static void main(String[] args) {
        String str1 = "";
        System.out.println(getFirstNotEmptyString(str1, new GetString() {
            @Override
            public String get() {
                System.out.println("我调用了get方法");
                return "rose";
            }
        }));
    }

    public static String getFirstNotEmptyString(String str1, GetString str2GetString){
        if(str1 != null && str1.length() > 0)return str1;
        if(str2GetString == null)return null;
        String str2 = str2GetString.get();
        if(str2 != null && str2.length() > 0)return str2;
        return null;
    }
}

从结果可以看出 我们直到要使用str2的时候 才真正将其构建出来

但是其实jdk内置的Supplier才更具有通用性 因为我不一定获取的是第一个符合条件的字符串 我也可以获取第一个符合条件的Integer 也可以是Double 所以jdk内置的Supplier支持泛型 可以提高代码的复用率

我们可以说一下他的用途:
我们可以利用他来模拟逻辑运算符(|| &&)的短路效果(可以通过左子表达式获取到整个表达式的结果 我们就不计算右子表达式的结果)

2.Consumer

在这里插入图片描述
现在有这么一个需求:
就是我想要遍历一个某个类型的数组 并且针对每一个数组元素进行某个操作

public class Main {
    public static void main(String[] args) {
        Integer[] is = {11, 22, 33};
        forEach(is, new Consumer<Integer>() {
            @Override
            public void accept(Integer integer) {
                String result = ((integer & 1) == 0) ? "偶数" : "奇数";
                System.out.println(integer + "是" + result);
            }
        });
    }
    public static <T> void forEach(T[] nums, Consumer<T> consumer){
    	if(nums == null || consumer == null)return;
        for (T num: nums) {
            consumer.accept(num);
        }
    }
}

但是现在我要进行多个操作 而且多个操作要分属于多个匿名类或者Lambda表达式中

public class Main {
    public static void main(String[] args) {
        Integer[] is = {11, 22, 33};
        forEach(is, integer -> {
            String result = ((integer & 1) == 0) ? "偶数" : "奇数";
            System.out.println(integer + "是" + result);
        }, integer -> {
            String result = ((integer % 3) == 0) ? "能被3整除" : "不能被3整除";
            System.out.println(integer + result);
        });
    }
    public static <T> void forEach(T[] nums, Consumer<T> consumer1, Consumer<T> consumer2){
    	if(nums == null || consumer1 == null || consumer2 == null)return;
        for (T num: nums) {
            consumer1.andThen(consumer2).accept(num);// 等价于consumer1.accept(num);consumer2.accept(num)
        }
    }
}

andThen的作用在注释中
我们也来说明一下他的用途:其实就是利用他来内置操作

3.Perdicate

在这里插入图片描述
现在我们有这么一个需求:
要将数组中的偶数进行拼接的结果返回

public class Main {
    public static void main(String[] args) {
        int[] nums = {11, 22, 33, 44, 55};
        System.out.println(join(nums, num -> (num & 1) == 0 ? true : false));
    }
    public static String join(int[] nums, Predicate<Integer> p){
    	if(nums == null || p == null)return null;
        StringBuilder sb = new StringBuilder();
        for (int num: nums) {
            if(p.test(num)){
                sb.append(num).append("_");
            }
        }
        sb.deleteCharAt(sb.length() - 1);
        return sb.toString();
    }
}

如果我们现在改动一下需求:
要将数组中既能够被2整除又能够被3整除的数拼接起来的结果返回

public class Main {
    public static void main(String[] args) {
        int[] nums = {11, 22, 33, 44, 55, 66};
        System.out.println(join(nums, num -> (num & 1) == 0 ? true : false, num -> (num % 3) == 0 ? true : false));
    }
    public static String join(int[] nums, Predicate<Integer> p1, Predicate<Integer> p2){
    	if(nums == null || p1 == null || p2 == null)return null;
        StringBuilder sb = new StringBuilder();
        for (int num: nums) {
            if(p1.and(p2).test(num)){
                sb.append(num).append("_");
            }
        }
        sb.deleteCharAt(sb.length() - 1);
        return sb.toString();
    }
}

现在有在改变一下需求:
将满足2的倍数或者3的倍数的数拼接起来并返回

public class Main {
    public static void main(String[] args) {
        int[] nums = {11, 22, 33, 44, 55, 66};
        System.out.println(join(nums, num -> (num & 1) == 0 ? true : false, num -> (num % 3) == 0 ? true : false));
    }
    public static String join(int[] nums, Predicate<Integer> p1, Predicate<Integer> p2){
    	if(nums == null || p1 == null || p2 == null)return null;
        StringBuilder sb = new StringBuilder();
        for (int num: nums) {
            if(p1.or(p2).test(num)){
                sb.append(num).append("_");
            }
        }
        sb.deleteCharAt(sb.length() - 1);
        return sb.toString();
    }
}

默认方法中不仅仅支持and、or 还支持
现在利用negate对条件取反 变成将奇数拼接的结果返回

public class Main {
    public static void main(String[] args) {
        int[] nums = {11, 22, 33, 44, 55, 66};
        System.out.println(join(nums, num -> (num & 1) == 0 ? true : false));
    }
    public static String join(int[] nums, Predicate<Integer> p1){
    	if(nums == null || p1 == null)return null;
        StringBuilder sb = new StringBuilder();
        for (int num: nums) {
            if(p1.negate().test(num)){
                sb.append(num).append("_");
            }
        }
        sb.deleteCharAt(sb.length() - 1);
        return sb.toString();
    }
}

他的用途是作为过滤器存在 可以内置过滤条件 过滤掉目标数组中的一些元素

4.Function

在这里插入图片描述
我现在有一个需求是:
对字符串数组中的整数字符串进行加和操作

public class Main {
    public static void main(String[] args) {
        String[] strs = {"11", "22", "33"};
        System.out.println(sum(strs, new Function<String, Integer>(){

            @Override
            public Integer apply(String s) {
                return Integer.valueOf(s);
            }
        }));// 66
    }
    public static int sum(String[] strs, Function<String, Integer> f){
    	if(strs == null || f == null)return 0;
        int result = 0;
        for(String str: strs){
            result += f.apply(str);
        }
        return result;
    }
}

如果我改动一下需求:
就是对字符串数组中的每一个字符参与加和 之后让每一个字符串取模于10
f1.andThen(f2).apply()等价于先执行f1.apply() 在执行f2.apply()

public class Main {
    public static void main(String[] args) {
        String[] strs = {"11", "22", "33"};
        System.out.println(sum(strs, new Function<String, Integer>(){

            @Override
            public Integer apply(String s) {
                return Integer.valueOf(s);
            }
        }, new Function<Integer, Integer>(){

            @Override
            public Integer apply(Integer integer) {
                return integer % 10;
            }
        }));// 1 + 2 + 3 = 6
    }
    public static int sum(String[] strs, Function<String, Integer> f1, Function<Integer, Integer> f2){
    	if(strs == null || f1 == null || f2 == null)return 0;
        int result = 0;
        for(String str: strs){
            result += f1.andThen(f2).apply(str);
        }
        return result;
    }
}

现在在改动一下需求:
和之前那个需求一样 但是就是你转一下执行顺序
f1.compose(f2).apply() 先执行f2 在执行f1

public class Main {
    public static void main(String[] args) {
        String[] strs = {"11", "22", "33"};
        System.out.println(sum(strs, new Function<String, Integer>(){

            @Override
            public Integer apply(String s) {
                return Integer.valueOf(s);
            }
        }, new Function<Integer, Integer>(){

            @Override
            public Integer apply(Integer integer) {
                return integer % 10;
            }
        }));// 1 + 2 + 3 = 6
    }
    public static int sum(String[] strs, Function<String, Integer> f1, Function<Integer, Integer> f2){
    	if(strs == null || f1 == null || f2 == null)return 0;
        int result = 0;
        for(String str: strs){
            result += f2.compose(f1).apply(str);
        }
        return result;
    }
}

他的主要用途是:如果你的需求是他接口的两个泛型分别对应类型转换前后的两个类型

5.综合案例

现在有这么一个需求:
定义一个Person类型的数组 并且将内部的符合条件的Person对象转换成String类型的 然后将其打印出来

public class Main {
    public static void main(String[] args) {
        List<Person> li = new ArrayList<>();
        li.add(new Person("sandy", 11));
        li.add(new Person("jack", 22));
        li.add(new Person("tim", 33));
        li.add(new Person("tom", 24));
        process(li, person -> person.getAge() >= 15 && person.getAge() <= 25, Person::toString, System.out::println);
    }
    // 其实就是定义一个Person类型的数组 并且将内部的符合条件的Person对象转换成String类型的 然后将其打印出来
    public static <X, Y> void process(Iterable<X> it, Predicate<X> p, Function<X, Y> f, Consumer<Y> c){
        // 首先遍历数组
        for(X i: it){
            // 如果不符合条件的元素 就不执行相关操作
            if(!p.test(i))continue;
            // 符合条件的话 那么首先转换成字符串类型的 然后在执行打印操作
            Y newI = f.apply(i);
            c.accept(newI);
        }
    }
}

现在还有一个需求是:
让数组储存int数据 然后将符合条件的整数转换为字符串 然后打印出来

public class Main {
    public static void main(String[] args) {
        List<Integer> li = new ArrayList<>();
        li.add(11);
        li.add(22);
        li.add(33);
        li.add(44);
        process(li, i -> (i & 1) == 0, i -> "test_" + i, System.out::println);
    }
    // 其实就是定义一个Person类型的数组 并且将内部的符合条件的Person对象转换成String类型的 然后将其打印出来
    public static <X, Y> void process(Iterable<X> it, Predicate<X> p, Function<X, Y> f, Consumer<Y> c){
        // 首先遍历数组
        for(X i: it){
            // 如果不符合条件的元素 就不执行相关操作
            if(!p.test(i))continue;
            // 符合条件的话 那么首先转换成字符串类型的 然后在执行打印操作
            Y newI = f.apply(i);
            c.accept(newI);
        }
    }
}

从打印结果来看 符合我们的需求

  • 6
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

axihaihai

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值