Java语言基础(SE)-第三节 面向对象(三) 接口、多态、匿名类、Lambda、方法引用

接口(Interface)

  1. API(Application Programming Interface)
    1. 应用编程接口,提供给开发者调用的一组功能(无须提供源码)
  2. Java 中的接口
    1. 一系列方法声明的集合,用来定义规范、标准
    2. 接口就相当于OC中的协议,自定一系列的标准,谁遵守,谁就要实现,只是OC中的协议是可以设置遵守者是否选择性的实现,这里接口一旦遵守是必须实现

接口中可以定义的内容

  1. 接口的定义:右击->new->interface,可以看到接口跟类特别相似,接口用interface修饰,类用class修饰
  2. 可以定义:抽象方法、常量、嵌套类型,从 Java 8 开始可以定义:默认方法、静态方法(类方法)
    1. 上述可以定义的内容都是隐式 public 的,因此可以省略 public 关键字
    2. 从 Java 9 开始可以定义 private 方法
  3. 常量可以省略 static、final
  4. 抽象方法可以省略 abstract,但默认是抽象方法
  5. 不能自定义构造方法、不能定义(静态)初始化块、不能实例化
  6. 举例
    1. 一个child要找家教,不限制职业,只需要家教具备2个能力:教编程、教足球即可
    2. 因此建立一个接口用来提供家教的标准
    3. 各种行业的对象要服从这个标准
     //接口JiaJiaoAble
     package com.zh;
     public interface JiaJiaoAble {
         //抽象方法、常量
         //public abstract void test1();
         //可以省略public abstract 
         //void test2();
         public final static int a = 10;
         //可以省略final static
         int b = 10;
         //嵌套类型
         class A{}
         //家教接口标准   
         public abstract void jaoBianCheng(Child child);
         public abstract void jaoZuQiu(Child child);
     }
            
     //孩子
     package com.zh;
     public class Child {
         private String name;
         //家教
         private JiaJiaoAble jiaJiao;
         public Child(String name) {
             this.name = name;
         }
         public String getName() {
             return name;
         }
         //此时的类型为接口的类型,传入的对象必须实现了该接口的方法
         public void setJiaJiao(JiaJiaoAble jiaJiao) {
             this.jiaJiao = jiaJiao;
         }
         //学习
         public void study() {
             jiaJiao.jaoBianCheng(this);
             jiaJiao.jaoZuQiu(this);
         }
     }
            
     //学生成为一个家教
     package com.zh;
     //implements 实现接口
     public class Student implements JiaJiaoAble {
         public void jaoBianCheng(Child child) {
             System.out.println("Student 教" + child.getName() + "编程");
         }
         public void jaoZuQiu(Child child) {
             System.out.println("Student 教" + child.getName() + "足球");
         }
     }
            
     //老师成为一个家教
     package com.zh;
     //implements 实现接口
     public class Teacher implements JiaJiaoAble {
         public void jaoBianCheng(Child child) {
             System.out.println("Teacher 教" + child.getName() + "编程");
         }
         public void jaoZuQiu(Child child) {
             System.out.println("Teacher 教" + child.getName() + "足球");
         }
     }
            
     //main函数
     public static void main(String[] args) {
         Child chi = new Child("jack");
         chi.setJiaJiao(new Student());
         chi.study();
         chi.setJiaJiao(new Teacher());
         chi.study();
         /* 打印
         Student 教jack编程
         Student 教jack足球
         Teacher 教jack编程
         Teacher 教jack足球
         */
     }
    

接口的细节

  1. 接口名称可以在任何使用类型的地方使用

     int a = 10;
     JiaJiaoAble jia;
    
  2. 一个类可以通过 implements 关键字实现一个或多个接口,多个接口用逗号隔开
    1. 实现接口的类必须实现接口中定义的所有抽象方法,除非它是个抽象类
    2. 如果一个类实现的多个接口中有相同的抽象方法,只需要实现此方法一次
    3. extends 和 implements 可以一起使用,implements 必须写在 extends 的后面
    4. 当父类和接口中的方法签名一样时,那么返回值类型也必须一样
      1. 就是两个方法必须一模一样
  3. 一个接口可以通过 extends 关键字继承一个或者多个接口
    1. 当多个父接口中的方法签名一样时,那么返回值类型也必须一样
      1. 就是两个方法必须一模一样
     public class Dog extends Animal implements Eatable {
         @Override
         public void eat(String name) {
         }
     }
     public class Student implements JiaJiaoAble , BaoMu{
         ...
     }
    

接口的升级问题

  1. 如果接口需要升级,比如增加新的抽象方法
    1. 由于接口中的方法类必须实现,因此会导致大幅的代码改动,以前实现接口的类都得改动
  2. 若想在不改动以前实现类的前提下进行接口升级,从 Java 8 开始,有 2 种方案
    1. 默认方法(Default Method)
    2. 静态方法(Static Method)

默认方法(Default Method)

  1. 用 default 修饰默认方法
    1. 默认方法只能是实例方法
    2. 默认方法可以直接在接口中实现,继承者可以不用实现,也可以覆盖实现
     //接口
     package com.zh;
     public interface Eatable {
         default void eat(String name) {
             System.out.println("Eatable - eat - " + name);
         }	
     }
     //Dog
     package com.zh;
     public class Dog implements Eatable {
     }
     //Cat
     package com.zh;
     public class Cat implements Eatable {
         @Override
         public void eat(String name) {
             //先调用接口的实现,接口名称调用
             Eatable.super.eat(name);
             System.out.println("Cat - eat - " + name);
         }
     }
        
     //main函数
     public class Main {
     public static void main(String[] args) {
         Dog dog = new Dog();
         dog.eat("bone");
         Cat cat = new Cat();
         cat.eat("fish");
     }
     //打印
     Eatable - eat - bone
     Eatable - eat - fish
     Cat - eat - fish
    
  2. 默认方法的使用
    1. 当一个类实现的接口中有默认方法时,这个类可以
      1. 啥也不干,沿用接口的默认实现:dog.eat("bone");
      2. 重新定义默认方法,覆盖默认方法的实现: cat.eat("fish");
      3. 重新声明默认方法,将默认方法声明为抽象方法(此类必须是抽象类)
        1. 就是Dog首先用abstract修饰成为抽象类
        2. 然后声明一个抽象方法public abstract void eat(String name);
    2. 当一个接口继承的父接口中有默认方法时,这个接口可以
      1. 啥也不干,沿用接口的默认实现
      2. 重新定义默认方法,覆盖默认方法的实现
      3. 重新声明默认方法,将默认方法声明为抽象方法
  3. 默认方法的细节
    1. 如果父类定义的非抽象方法与接口的默认方法相同时,最终将调用父类的方法
    2. 如果父类定义的抽象方法与接口的默认方法相同时,要求子类实现此抽象方法
      1. 可以通过 super 关键字调用接口的默认方法
    3. 如果(父)接口定义的默认方法与其他(父)接口定义的方法相同时,要求子类型实现此默认方法

图1

静态方法(Static Method)

  1. 接口中定义的静态方法只能通过接口名调用,不能被继承
    1. 不能被继承意思是,A接口有静态方法a,B接口继承A接口,B.a(),调用,是错误的,不能继承
     //接口Eatable
     package com.zh;
     public interface Eatable {
         static void eat(String name) {
             System.out.println("Eatable - eat - " + name);
         }	
     }
        
     //接口Sleepable
     package com.zh;
     public interface Sleepable {
         static void eat(String name) {
             System.out.println("Sleepable - eat - " + name);
         }
     }
        
     //接口Dog
     package com.zh;
     public interface Dog extends Sleepable, Eatable {
         static void eat(String name) {
             System.out.println("Dog - eat - " + name);
         }	
     }
        
     //类Cat
     package com.zh;
     public class Cat implements Eatable {
     }
        
     //main函数
     Dog.eat("1");
     Eatable.eat("2");
     Sleepable.eat("3");
     //尽管Cat实现Eatable接口,但是却无法通过Cat的任何方式去调用Eatable的接口方法
     //错误
     Cat.eat();
     Cat cat = new Cat();
     //错误
     cat.eat();
     /*
     Dog - eat - 1
     Eatable - eat - 2
     Sleepable - eat - 3
     */
    

抽象类与接口对比

  1. 抽象类和接口的用途还是有点类似,该如何选择?何时选择抽象类?
    1. 在紧密相关的类之间共享代码
    2. 需要除 public 之外的访问权限
      1. 接口所有的成员都是public
    3. 需要定义实例变量、非 final 的静态变量
      1. 接口只能定义常量
  2. 何时选择接口?
    1. 不相关的类实现相同的方法,只是定义行为,不关心具体是谁实现了行为
    2. 想实现类型的多重继承
      1. 类的继承只能单继承,不能多继承
      2. 接口可以多继承,类只能单继承

多态(Polymorphism)

  1. 什么是多态?
    1. 具有多种形态
    2. 同一操作作用于不同的对象,产生不同的执行结果
  2. 多态的体现
    1. 父类(接口)类型指向子类对象
    2. 调用子类重写的方法
  3. JVM 会根据引用变量指向的具体对象来调用对应的方法
    1. 这个行为叫做:虚方法调用(virtual method invocation)
    2. 类似于 C++ 中的虚函数调用
  4. 多态实例

     package com.zh;
     public class Animal {
         public void speak() {
             System.out.println("Animal - speak");
         }
     }
        
     package com.zh;
     public class Dog extends Animal {
         public void speak() {
             System.out.println("Dog - speak");
         }
     }
        
     package com.zh;
     public class Cat extends Animal {
         public void speak() {
             System.out.println("Cat - speak");
         };
     }
        
     /*********以上是类的继承体系,以下是接口的实现体系*******/
    
     package com.zh;
     public interface Runnable {
         void run();
     }
        
     package com.zh;
     public class Pig implements Runnable {
         @Override
         public void run() {
             System.out.println("Pig - run");
         }
     }
       
     package com.zh;
     public class Person implements Runnable {
         @Override
         public void run() {
             System.out.println("Person - run");
         }
     }
        
     //main函数
     public static void main(String[] args) {
         speak(new Dog()); //Dog - speak
         speak(new Cat()); //Cat - speak
         run(new Pig()); //Pig - run
         run(new Person()); //Person - run
     }
     static void speak(Animal animal) {
         animal.speak();
     }
     static void run(Runnable runnable) {
         runnable.run();
     }
    

类方法的调用细节

  1. 静态(类)方法不能实现多态

     public class Animal {
         public static void run() {
             System.out.println("Animal - run");
         }
     }
     public class Dog extends Animal {
         public static void run() {
             System.out.println("Dog - run");
         }
     }
     Dog.run(); //Dog - run
     Animal.run(); //Animal - run
     Dog dog1 = new Dog();
     dog1.run();//Dog - run
     //静态方法不能实现多态
     Animal dog2 = new Dog();
     dog2.run();//Animal - run
    
  2. 多态只适用于实例

成员变量的访问细节

图1

instanceof

  1. 可以通过 instanceof 判断某个类型是否属于某种类型

     public class Animal {}
     //接口
     public interface Runnable {}
     public class Dog extends Animal implements Runnable {}
     Object dog = new Dog();
     System.out.println(dog instanceof Dog); //true
     System.out.println(dog instanceof Animal); //true
     System.out.println(dog instanceof Runnable);//true
     System.out.println(dog instanceof String);//false
    
  2. instanceof 使用

     public class Cat extends Animal {
         public void miao() {
             System.out.println("Cat-miao");
         }
     }
     public class Dog extends Animal{
         public void wang() {
             System.out.println("Dog-wang");
         }
     }
        
     speak(new Dog()); //Dog-wang
     speak(new Cat()); //Cat-miao
     public static void speak(Animal animal) {
         if (animal instanceof Dog) {
             ((Dog) animal).wang();
         }else {
             ((Cat) animal).miao();
         }
     }
    

匿名类(Anonymous Class)

  1. 接口、抽象类实现类,在整个项目中只用过一次,可以考虑使用匿名类

     public interface Eatable {
         String name();
         int energy();
     }
     public class Person {
         public void eat(Eatable e) {
             System.out.println("eat - " + e.name() + "-" + e.energy());
         }
     }
        
     Person person = new Person();
     //接口当做类来实现
     person.eat(new Eatable() {
         @Override
         public String name() {
             return "Apple";
         }
         @Override
         public int energy() {
             return 100;
         }
     });//eat - Apple-100
        	
     Eatable beef = new Eatable() {
         @Override
         public String name() {
             return "Apple";
         }
         @Override
         public int energy() {
             return 100;
         }
     };
        
     Person person2 = new Person();
     //多次使用
     person2.eat(beef); //eat - Apple-100
     person2.eat(beef); //eat - Apple-100
        
     //抽象类
     public abstract class Animal {
         public abstract void run();
     }
     Animal animalx = new Animal() {
         @Override
         public void run() {
             System.out.println("Animal-run");
         }
     };
     animalx.run(); //Animal-run
    
  2. 特点:

    1. 匿名类就是一个接口、抽象类的实现,而且不需要类来承载。
      1. 通常的方式是需要一个类来实现接口方法、继承抽象类,然后创建这个类的实例,然后用实例来调用
      2. 现在省略了这个承载类,所以叫匿名类
    2. 即仅仅想使用接口、抽象类中的方法,直接通过匿名类实现即可

       Eatable beef2 = new Eatable() {
           @Override
           public String name() {
               return "Apple";
           }
           @Override
           public int energy() {
               return 100;
           }
       };
       beef2.name();
       beef2.energy();
      

匿名类的使用注意

  1. 匿名类不能定义除编译时常量以外的任何 static 成员(同局部类)
  2. 匿名类只能访问 final 或者 有效 final 的局部变量(同局部类)
  3. 匿名类可以直接访问外部类中的所有成员(即使被声明为 private)(同局部类)
    1. 匿名类只有在实例相关的代码块中使用,才能直接访问外部类中的实例成员(实例变量、实例方法)
  4. 匿名类不能自定义构造方法,但可以有初始化块

     Animal animalx = new Animal() {
         int age;
         //不能定义除编译时常量以外的任何 static 成员
         //static int x;
         //编译时常量
         final static int y =0;
         //可以有初始化快
         {
             int xxx;
         }
         @Override
         public void run() {
             System.out.println("Animal-run");
         }
     };
    

匿名类的常见用途

  1. 代码传递

     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 + "s");
         }
     }
        
     package com.zh;
     //注意:需要导入
     import com.zh.Times.Block;
     public class Main {
         public static void main(String[] args) {
             //写一个工具类,统计某段代码执行时间
             Times.test(new Block() {
                 @Override
                 public void execute() {
                     //需要测试的代码快
                     //for(int i = 0;i<10; i++) {
                         //System.out.println(i);
                     //}
                     test();
                 }
                    
             });
         }
            
         static void test() {
             for(int i = 0;i<10; i++) {
                 System.out.println(i);
             }
         }
     }
    
  2. 过滤器

     //过滤器
     public class Files {
         //接口
         public interface Filter {
             boolean accept(String filename);
         }
         public static String[] getAllFileNames(String dir,Filter filter) {
             //1.先获取dir路径下所有文件
             String[] allFileNames = {};
             //2. 过滤
             for (String filename : allFileNames) {
                 if(filter.accept(filename)) {
                     //将符合条件的文件名装入
                 }
             }
             //3. 返回所有装起来的文件名
             return allFileNames;
         }
     }
        
     //导入执行
     import com.zh.Files.Filter;
     Files.getAllFileNames("F://", new Filter() {
         @Override
         public boolean accept(String filename) {
             return filename.contains("类");
         }
     });
    
  3. 回调

     public class Networks {
         //接口
         public interface Block {
             void success(Object response);
             void failure();
         }
         public static void get(String url,Block callback) {
             //1. 根据url发送一个异步请求(开启一条新的线程)
             //2. 请求完毕,返回结果
             boolean result = true;
             if(result) {
                 Object response = null;
                 callback.success(response);
             }else {
                 callback.failure();
             }
         }
     }
     public static void main(String[] args) {
     Networks.get("http://www.baidu.com", new Block() {
         @Override
         public void success(Object response) {
             //请求成功
         }
         @Override
         public void failure() {
             //请求失败
         }
         });
     }
    
  4. 排序

    1. 可以使用 JDK 自带的 java.util.Arrays 类对数组进行排序

       Integer[] array = { 33, 22, 11, 77, 66, 99 }; 
       Arrays.sort(array);  
       //打印数组的字符串格式   
       // [11, 22, 33, 66, 77, 99] 
       System.out.println(Arrays.toString(array));
       //1. Arrays.sort这个方法首先将数组进行升序排列
       Arrays.sort(array, new Comparator<Integer>() { 
           @Override 
           //2. 根据return这个返回值的正负来判断,然后重新排序;
           //如果返回值大于0,就认为o1>o2,把o1放右边,o2放左边
           //不要受o2-o1的影响,里面可以写成常量,1,-1,0都行
           //比如o2为77,o1为99,o2-o1= -22,return结果小于0,就认为o1小于o2,将99方左边,77放右边
           public int compare(Integer o1, Integer o2) { 
               return o2 - o1; 
           } 
       }); 
       // [99, 77, 66, 33, 22, 11] 
       System.out.println(Arrays.toString(array));
      
    2. Arrays.sort 默认是升序排列,把比较小的元素放左边,把比较大的元素放右边
    3. compare 的返回值
      1. 等于 0: o1 == o2
      2. 大于 0: o1 > o2
      3. 小于 0: o1 < o2
    4. Comparable(在泛型中讲) vs Comparator
      1. 如果数组元素本身具备可比较性(实现了 java.util.Comparable 接口)
        1. 可以直接使用 Arrays.sort 方法进行排序
      2. java.util.Comparator 的存在意义?
        1. 可以在不修改类源代码的前提下,修改默认的比较方式(比如官方类、第三方类)
        2. 可以让一个类提供多种比较方式

Lambda Expression

Lambda 表达式是 Java 8 开始才有的语法

函数式接口(Functional Interface**)

  1. 概念:包含1个抽象方法的接口
  2. 可以在接口上面加上 @FunctionalInterface 注解,表示它是一个函数式接口

     @FunctionalInterface 
     public interface Testable { 
         void test(int v); 
     }
    
  3. 函数式接口中还可以有其他方法(非抽象方法):可以参考 Comparator接口
    1. ​有且只有一个抽象方法的声明;
    2. 可以有多个静态方法的实现
    3. 可以有多个default方法(默认方法)
    4. 可以有多个Object的public的抽象方法
      1. 就是Object这个基础类已经实现的Public方法
      2. 也可以认为是默认方法
       package com.zh;
       @FunctionalInterface
       public interface Eatable2 {
           //有且只有一个抽象方法
           int calculate(int v1, int v2); 
           //可以有多个Object的public的抽象方法 
           // equals toString 方法是Object类中已经实现的方法
           boolean equals(Object obj);
           String toString();
       }
      
  4. 总结:不管函数式接口中到底有多少个方法,有且只有一个抽象方法的声明,需要使用者进行实现,其余的方法一定都已经实现了。

Lambda表达式概念

  1. Lambda表达式标准格式

     (参数列表)->{
         return 返回值;
     }
    
    1. 参数列表可以省略参数类型
    2. 当只有一条语句时:可以省略大括号、分号、return
    3. 当只有一个参数时:可以省略小括号
    4. 当没有参数时:不能省略小括号
    5. Lambda 只能访问 final 或者 有效 final 的局部变量(同匿名类)
  2. 匿名类实现的是函数式接口时,可以使用 Lambda 表达式进行简化
    1. 就是匿名类实现接口,这个接口只有一个抽象方法(函数式接口)
    2. 注意:这里是用lambda进行简化,就是替换,可以达到相同的目的。但是Lambda 表达 不等于 匿名类实现的是函数式接口
  3. Lambda 实例
    1. 例1

       //函数式接口
       @FunctionalInterface 
       public interface Calculator { 
           int calculate(int v1, int v2); 
       }
              
       //main函数
       static void execute(int v1, int v2, Calculator c){ 
           System.out.println(c.calculate(v1, v2)); 
       }
       public static void main(String[] args) { 
           execute(10, 20, (int v1, int v2) -> { 
               return v1 + v2; 
           }); // 30
           execute(11, 22, (v1, v2) -> v1 + v2); // 33
       }
      
    2. 上面案例过滤器的Lambda写法

       Files.getAllFileNames("F://", (filename)->{
           return filename.contains("类");
       });
       Files.getAllFileNames("F://", (filename)->filename.contains("类"));
       Files.getAllFileNames("F://", filename->filename.contains("类"));
      

匿名类 vs Lambda

  1. 尽管使用Lambda可以取代函数式接口的匿名类实现,但是二者并不相等
  2. Lambda 没有引入新的作用域
  3. 举例:
    1. 函数式接口Testable

              
       @FunctionalInterface public interface Testable { 
           void test(int v); 
       }
      
    2. 使用Lambda替代匿名类

       public class OuterClass { 
           private int age = 1;
           public class InnerClass {
               private int age = 2; 
               void inner() { 
                   // int v = 4; // error 
                   Testable t = v -> { 
                       System.out.println(v); // 3 
                       //注意这两句: 没有引入新的作用域,就是在Lambda表达式内部执行的代码,去掉Lambda表达式执行结果是一样,因此下面两句代码都是2
                       System.out.println(age); // 2 
                       System.out.println(this.age); // 2 
                       System.out.println(InnerClass.this.age); // 2 
                       System.out.println(OuterClass.this.age); // 1 
                   }; 
                   t.test(3);
                   //上面一堆Lambda表达式貌似等价于下面这一堆,因此上面再定义一个int v = 4;会报错!!!
                   //int v = 3;
                   //System.out.println(v); // 3 
                   //System.out.println(age); // 2 
                   //System.out.println(this.age); // 2 
                   //System.out.println(InnerClass.this.age); // 2 
                   //System.out.println(OuterClass.this.age); // 1 
                          
               }
           }
       }
       //main函数
       new OuterClass().new InnerClass().inner();
      
    3. 使用匿名类

       public class OuterClass { 
           private int age = 1;
           public class InnerClass {
               private int age = 2; 
               void inner() {
                   //不会报错
                   int v = 4;
                   Testable t = new Testable() {
                       @Override
                       public void test(int v) { 
                           System.out.println(v); // 3 
                           System.out.println(age); // 2 ,局部类,直接访问外部成员即使是private
                           //这里会报错,this此时指的就是匿名类这个对象,二这个匿名对象并没有age成员,所以报错
                           // System.out.println(this.age); // error 
                           System.out.println(InnerClass.this.age); // 2 
                           System.out.println(OuterClass.this.age); // 1
                       }
                   }; 
                   t.test(3);
               }
           }
       }
      

常用函数式接口

  1. java.util.function 包中提供了很多常用的函数式接口
    1. Supplier、Consumer、Predicate、Function
  2. 特点:
    1. 跟OC的block很相似
    2. 函数式编程:实参传递函数的实现,函数的调用在方法实现中调用

Supplier

  1. 定义

     @FunctionalInterface public interface Supplier<T> { 
         //无参数
         T get(); 
     }
    
  2. 应用(注意理解!!

    1. 有时使用Supplier传参,可以避免代码的浪费执行(有必要的时候再执行)

       //需求:实现一个函数,传入2个字符串参数,返回第一个不为空的字符串
       //特点:如果第一个参数不为空,则第二个参数直接不考虑
       package com.zh;
       import java.util.function.Supplier;
       public class Main {
           public static void main(String[] args) {
               //打印:makeString Jack
               //从打印结果来看,首先是调用了makeString函数,然后才会调用getFirstNotEmptyString1
               System.out.println(getFirstNotEmptyString1("Jack", makeString()));
               //打印:jack
               //从打印结果来看,发现第一个参数不为空,直接忽略了makeString
               System.out.println(getFirstNotEmptyString2("jack", ()->makeString()));//使用Lambda
               //使用匿名类,传参为函数实现
               System.out.println(getFirstNotEmptyString2("jack", new Supplier<String>() {
                   @Override
                   public String get() {
                       return makeString();
                   }
               }));
           }
           static String makeString() {
               System.out.println("makeString");
               return String.format("%d %d %d", 1, 2, 3);
           }
           //方法1
           static String getFirstNotEmptyString1(String s1, String s2) { 
               if (s1 != null && s1.length() != 0) return s1; 
               if (s2 != null && s2.length() != 0) return s2; 
               return null; 
           }
           //方法2
           static String getFirstNotEmptyString2(String s1, Supplier<String> s2) { 
               if (s1 != null && s1.length() != 0) return s1; 
               if (s2 == null) return null; 
               //方法调用在内部
               String str = s2.get(); 
               return (str != null && str.length() != 0) ? str : null; 
           }
       }
      
    2. 特点:

      1. 第二个参数是一个函数参数,传值必须是匿名类实现或者Lambda表达式
      2. 就相当于OC中的传参是Block
      3. 延迟了代码的执行
      4. 使用场景,Supplier这就相当于一个block,调用已经实现,传参传递的是函数的实现

Consumer

  1. 定义

     @FunctionalInterface 
     public interface Consumer<T> { 
         //有参数
         void accept(T t);
         //该方法是默认方法,已经实现。返回值是一个Consumer
         default Consumer<T> andThen(Consumer<? super T> after) {
             //判断传入参数after不能为null
             Objects.requireNonNull(after);
             //accept(t)是调用当前对象的accept方法
             //after.accept(t):是调用传进来的after的accept方法
             return (T t) -> { accept(t); after.accept(t); };
         };
     }
    
  2. 应用

    1. 例1:

       //hook:遍历数组元素,并对每个元素做一个操作
       void forEach(int[] nums, Consumer<Integer> c) { 
           if (nums == null || c == null) return; 
           for (int n : nums) { 
               //函数(block)调用在方法内部
               c.accept(n); 
           } 
       }
              
       int[] nums = { 11, 33, 44, 88, 77, 66 }; 
       //传参为函数(block)实现
       forEach(nums, (n) -> {
           String result = ((n & 1) == 0) ? "偶数" : "奇数";
           System.out.println(n + "是" + result); 
       });
      
    2. andThen

       //hook:遍历数组元素,并对每个元素做一个操作
       void forEach(int[] nums, Consumer<Integer> c1, Consumer<Integer> c2) { 
           if (nums == null || c1 == null || c2 == null) return; 
           for (int n : nums) { 
               //2个函数先后执行
               c1.andThen(c2).accept(n); 
           } 
       }
              
       int[] nums = { 11, 33, 44, 88, 77, 66 }; 
       forEach(nums, (n) -> {
           String result = ((n & 1) == 0) ? "偶数" : "奇数";
           System.out.println(n + "是" + result); 
       }, (n) -> {
           String result = ((n % 3) == 0) ? "能" : "不能";
           System.out.println(n + result + "被3整除"); 
       });
      

Predicate

  1. 定义

     @FunctionalInterface 
     public interface Predicate<T> { 
         //返回值为boolean
         boolean test(T t);
         default Predicate<T> and(Predicate<? super T> other) {
             Objects.requireNonNull(other);
             return (T t) -> test(t) && other.test(t);
         }
         default Predicate<T> negate(){
             return (T t) -> !test(t);
         }
         default Predicate<T> or(Predicate<? super T> other){
             Objects.requireNonNull(other);
             return (T t) -> test(t) || other.test(t);
         }
         //静态方法的实现
         static <T> Predicate<T> isEqual(Object targetRef) {
             return (null == targetRef)
                 ? Objects::isNull
                 : object -> targetRef.equals(object);
         }
     }
    
  2. 应用

    1. 例1:

       //把所有数组元素拼接起来,然后元素进行过滤
       String join(int[] nums, Predicate<Integer> p) { 
           if (nums == null || p == null) return null; 
           StringBuilder sb = new StringBuilder(); 
           for (int n : nums) { 
               if (p.test(n)) { //函数调用
                   sb.append(n).append("_"); 
               } 
           } 
           sb.deleteCharAt(sb.length() - 1); 
           return sb.toString();
       }
       int[] nums = { 11, 33, 44, 88, 77, 66 }; 
       //函数实现
       String str = join(nums, (n) -> (n & 1) == 0); 
       // 44_88_66 
       System.out.println(str);
      
    2. and

       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 n : nums) { 
               if (p1.and(p2).test(n)) {//二者同时成立
                   sb.append(n).append("_"); 
               } 
           } 
           sb.deleteCharAt(sb.length() - 1); 
           return sb.toString();
       }
       int[] nums = { 11, 33, 44, 88, 77, 66 }; 
       String str = join(nums, (n) -> (n & 1) == 0, (n) -> (n % 3) == 0); 
       // 66 
       System.out.println(str);
      
    3. or

       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 n : nums) { 
               if (p1.or(p2).test(n)) {//二者其中一个成立
                   sb.append(n).append("_"); 
               } 
           } 
           sb.deleteCharAt(sb.length() - 1); 
           return sb.toString();
       }
       int[] nums = { 11, 33, 44, 88, 77, 66 }; 
       String str = join(nums, (n) -> (n & 1) == 0, (n) -> (n % 3) == 0); 
       // 33_44_88_66 
       System.out.println(str);
      
    4. negate

       String join(int[] nums, Predicate<Integer> p) { 
           if (nums == null || p == null) return null; 
           StringBuilder sb = new StringBuilder(); 
           for (int n : nums) { 
               if (p.negate().test(n)) { 
                   sb.append(n).append("_"); 
               } 
           } 
           sb.deleteCharAt(sb.length() - 1); 
           return sb.toString();
       }
       int[] nums = { 11, 33, 44, 88, 77, 66 }; 
       String str = join(nums, (n) -> (n & 1) == 0); 
       // 11_33_77 
       System.out.println(str);
      

Function

  1. 定义

     @FunctionalInterface 
     public interface Function<T, R> {
         //参数类型、返回值类型,都是泛型
         R apply(T t);
         default <V> Function<V, R> compose(Function<? super V, ? extends T> before) {
             Objects.requireNonNull(before);
             return (V v) -> apply(before.apply(v));
         }
         default <V> Function<T, V> andThen(Function<? super R, ? extends V> after) {
             Objects.requireNonNull(after);
             return (T t) -> after.apply(apply(t));
         }
         static <T> Function<T, T> identity() {return t -> t;}
     }
    
  2. 应用

    1. 例1:

       //字符串转为Integer然后求和
       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); //字符串转为Integer然后累加
           } 
           return result; 
       }
       String[] strs = { "12", "567", "666" }; 
       int result = sum(strs, Integer::valueOf); 
       System.out.println(result);
      
    2. andThen

       // 将所有数字的个位数加起来 
       //参数传递2个函数实现
       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;
       }
       String[] strs = { "12", "567", "666" }; 
       int result = sum(strs, Integer::valueOf, (i) -> i % 10); 
       // 15 
       System.out.println(result);
      
    3. compose

       // 将所有数字的个位数加起来 
       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;
       }
       String[] strs = { "12", "567", "666" }; 
       int result = sum(strs, Integer::valueOf, (i) -> i % 10); 
       // 15 
       System.out.println(result);
      

方法引用(Method Reference)

  1. 如果 Lambda 中的内容仅仅是调用某个方法,可以使用方法引用(Method Reference)来简化
    1. Lambda对匿名类实现简化,那方法引用是对Lambda的简化
     种类                               用法
     引用静态方法                        ClassName::staticMethodName
     引用特定对象的实例方法                ObjectName::instanceMethodName
     引用特定类型的任意对象的实例方法        ClassName::methodName
     引用构造方法                        ClassName::new
     引用当前类中定义的实例方法            this::instanceMethodName
     引用父类中定义的实例方法              super::instanceMethodName
    
  2. 引用类方法

     @FunctionalInterface public interface Testable { 
         int test(int v1, int v2); 
     }
     Testable t1 = (v1, v2) -> Math.max(v1, v2); 
     // 20 
     System.out.println(t1.test(10, 20));
     Testable t2 = Math::max; 
     // 20 
     System.out.println(t2.test(10, 20));
    
  3. 引用特定对象的实例方法

     @FunctionalInterface 
     public interface Testable { 
         void test(int v); 
     }
     public class Person { 
         public void setAge(int age) { 
             System.out.println( "Person - setAge - " + age); 
         } 
     }
        
     static void execute(Testable t, int v) { 
         t.test(v); 
     }
     // 10
     execute(v -> System.out.println(v), 10); 
     // Person - setAge - 10 
     execute(v -> new Person().setAge(v), 10);
     //这个方法println的调用这是特定对象System.out
     // 20
     execute(System.out::println, 20); 
     //这个方法setAge的调用这是特定对象Person对象
     // Person - setAge - 20 
     execute(new Person()::setAge, 20);
    
  4. 引用特定类型的任意对象的实例方法

     String[] strings = { "Jack", "james", "Apple", "abort" };
     //compareToIgnoreCase这个方法的调用者是任意对象,不是特定的某个对象
     Arrays.sort(strings, (s1, s2) -> s1.compareToIgnoreCase(s2));
     // [abort, Apple, Jack, james] 
     System.out.println(Arrays.toString(strings));
     Arrays.sort(strings, String::compareToIgnoreCase); 
     // [abort, Apple, Jack, james] 
     System.out.println(Arrays.toString(strings));
    
  5. 引用构造方法

     @FunctionalInterface 
     public interface Testable { 
         Object test(int v); 
     }
     public class Person { 
         public Person(int age) { 
             System.out.println("Person - " + age); 
         } 
     }
        
     Testable t1 = v -> new Person(v); 
     // Person - 18 
     // Person@816f27d 
     System.out.println(t1.test(18));
     //引用构造方法
     Testable t2 = Person::new; 
     // Person - 18 
     // Person@6ce253f1 
     System.out.println(t2.test(18));
    
  6. 引用数组的构造方法

     Testable t1 = v -> new int[v]; 
     // 3 
     System.out.println(((int[]) t1.test(3)).length);
     //引用数组的构造方法
     Testable t2 = int[]::new; 
     // 3 
     System.out.println(((int[]) t2.test(3)).length);
    
  7. 引用当前类中定义的实例方法

     @FunctionalInterface public interface Testable { 
         void test(int v); 
     }
     public class Person {
         public void setAge(int age) { 
             System.out.println("setAge - " + age); 
         }
         public void show() {
             Testable t1 = v -> setAge(v); 
             // setAge - 10 
             t1.test(10);
             //引用当前类中定义的实例方法
             Testable t2 = this::setAge; 
             // setAge - 10 
             t2.test(10);
         }
     }
    
  8. 引用父类中定义的实例方法

     @FunctionalInterface 
     public interface Testable { 
         void test(int v); 
     }
     public class Person { 
         public void setAge(int age) { 
             System.out.println( "Person - setAge - " + age); 
         } 
     }
     public class Student extends Person {
         public void setAge(int age) { 
             System.out.println("Student - setAge - " + age); 
         } 
         public void show() { 
             Testable t1 = v -> super.setAge(v); 
             // Person - setAge - 10 
             t1.test(10);
                
             //引用父类中定义的实例方法
             Testable t2 = super::setAge; 
             // Person - setAge - 10 
             t2.test(10);
         }
     }
    

综合实例

<X, Y> void process(Iterable<X> eles, Predicate<X> tester, Function<X, Y> mapper,  Consumer<Y> block) { 
        if (eles == null || tester == null) return; 
        if (mapper == null || block == null) return; 
        for (X ele : eles) { 
            if (!tester.test(ele)) continue; 
            Y data = mapper.apply(ele); 
            block.accept(data); 
        }
}
public class Person {
    private String name; private int age; 
    public Person(String name, int age) {
        this.name = name;
        this.age = age; 
    } 
    public int getAge() {
        return age; 
    } 
    @Override 
    public String toString() {
        return "Person [name=" + name + ", age=" + age + "]"; 
    }
}

List<Person> ps = new ArrayList<>(); 
ps.add(new Person("Jack", 20)); 
ps.add(new Person("Rose", 10)); 
ps.add(new Person("Kate", 15)); 
ps.add(new Person("Larry", 40)); 
process(ps, (p) -> p.getAge() >= 15 && p.getAge() <= 25, Person::toString, System.out::println); 
// Person [name=Jack, age=20] 
// Person [name=Kate, age=15]

List<Integer> is = new ArrayList<>(); 
is.add(11); 
is.add(22); 
is.add(33); 
is.add(44); 
process(is, (i) -> (i & 1) == 0, (i) -> "test_" + i, System.out::println); 
// test_22 
// test_44
Table of Contents