Java8新特性:函数式(Functional)接口

简介: Java8新特性:函数式(Functional)接口

我是南城余!阿里云开发者平台专家博士证书获得者!

欢迎关注我的博客!一同成长!

一名从事运维开发的worker,记录分享学习。

专注于AI,运维开发,windows Linux 系统领域的分享!

本章节对应知识库

https://www.yuque.com/nanchengcyu/java

本内容来自尚硅谷课程,此处在知识库做了个人理解

————————————————

3.1 什么是函数式接口

  • 只包含一个抽象方法(Single Abstract Method,简称SAM)的接口,称为函数式接口。当然该接口可以包含其他非抽象方法。
  • 你可以通过 Lambda 表达式来创建该接口的对象。(若 Lambda 表达式抛出一个受检异常(即:非运行时异常),那么该异常需要在目标接口的抽象方法上进行声明)。
  • 我们可以在一个接口上使用 @FunctionalInterface 注解,这样做可以检查它是否是一个函数式接口。同时 javadoc 也会包含一条声明,说明这个接口是一个函数式接口。
  • java.util.function包下定义了Java 8 的丰富的函数式接口

3.2 如何理解函数式接口

  • Java从诞生日起就是一直倡导“一切皆对象”,在Java里面面向对象(OOP)编程是一切。但是随着python、scala等语言的兴起和新技术的挑战,Java不得不做出调整以便支持更加广泛的技术要求,即Java不但可以支持OOP还可以支持OOF(面向函数编程)
  • Java8引入了Lambda表达式之后,Java也开始支持函数式编程。
  • Lambda表达式不是Java最早使用的。目前C++,C#,Python,Scala等均支持Lambda表达式。
  • 面向对象的思想:
  • 做一件事情,找一个能解决这个事情的对象,调用对象的方法,完成事情。
  • 函数式编程思想:
  • 只要能获取到结果,谁去做的,怎么做的都不重要,重视的是结果,不重视过程。
  • 在函数式编程语言当中,函数被当做一等公民对待。在将函数作为一等公民的编程语言中,Lambda表达式的类型是函数。但是在Java8中,有所不同。在Java8中,Lambda表达式是对象,而不是函数,它们必须依附于一类特别的对象类型——函数式接口。
  • 简单的说,在Java8中,Lambda表达式就是一个函数式接口的实例。这就是Lambda表达式和函数式接口的关系。也就是说,只要一个对象是函数式接口的实例,那么该对象就可以用Lambda表达式来表示。

3.3 举例

举例1:

举例2:

作为参数传递 Lambda 表达式:

作为参数传递 Lambda 表达式:为了将 Lambda 表达式作为参数传递,接收Lambda 表达式的参数类型必须是与该 Lambda 表达式兼容的函数式接口的类型。

3.4 Java 内置函数式接口

3.4.1 之前的函数式接口

之前学过的接口,有些就是函数式接口,比如:

  • java.lang.Runnable
  • public void run()
  • java.lang.Iterable
  • public Iterator iterate()
  • java.lang.Comparable
  • public int compareTo(T t)
  • java.util.Comparator
  • public int compare(T t1, T t2)
3.4.2 四大核心函数式接口
函数式接口 称谓 参数类型 用途
Consumer<T> 消费型接口 T 对类型为T的对象应用操作,包含方法: void accept(T t)
Supplier<T> 供给型接口 返回类型为T的对象,包含方法:T get()
Function<T, R> 函数型接口 T 对类型为T的对象应用操作,并返回结果。结果是R类型的对象。包含方法:R apply(T t)
Predicate<T> 判断型接口 T 确定类型为T的对象是否满足某约束,并返回 boolean 值。包含方法:boolean test(T t)
3.4.3 其它接口

类型1:消费型接口

消费型接口的抽象方法特点:有形参,但是返回值类型是void

接口名 抽象方法 描述
BiConsumer<T,U> void accept(T t, U u) 接收两个对象用于完成功能
DoubleConsumer void accept(double value) 接收一个double值
IntConsumer void accept(int value) 接收一个int值
LongConsumer void accept(long value) 接收一个long值
ObjDoubleConsumer void accept(T t, double value) 接收一个对象和一个double值
ObjIntConsumer void accept(T t, int value) 接收一个对象和一个int值
ObjLongConsumer void accept(T t, long value) 接收一个对象和一个long值

类型2:供给型接口

这类接口的抽象方法特点:无参,但是有返回值

接口名 抽象方法 描述
BooleanSupplier boolean getAsBoolean() 返回一个boolean值
DoubleSupplier double getAsDouble() 返回一个double值
IntSupplier int getAsInt() 返回一个int值
LongSupplier long getAsLong() 返回一个long值

类型3:函数型接口

这类接口的抽象方法特点:既有参数又有返回值

接口名 抽象方法 描述
UnaryOperator T apply(T t) 接收一个T类型对象,返回一个T类型对象结果
DoubleFunction R apply(double value) 接收一个double值,返回一个R类型对象
IntFunction R apply(int value) 接收一个int值,返回一个R类型对象
LongFunction R apply(long value) 接收一个long值,返回一个R类型对象
ToDoubleFunction double applyAsDouble(T value) 接收一个T类型对象,返回一个double
ToIntFunction int applyAsInt(T value) 接收一个T类型对象,返回一个int
ToLongFunction long applyAsLong(T value) 接收一个T类型对象,返回一个long
DoubleToIntFunction int applyAsInt(double value) 接收一个double值,返回一个int结果
DoubleToLongFunction long applyAsLong(double value) 接收一个double值,返回一个long结果
IntToDoubleFunction double applyAsDouble(int value) 接收一个int值,返回一个double结果
IntToLongFunction long applyAsLong(int value) 接收一个int值,返回一个long结果
LongToDoubleFunction double applyAsDouble(long value) 接收一个long值,返回一个double结果
LongToIntFunction int applyAsInt(long value) 接收一个long值,返回一个int结果
DoubleUnaryOperator double applyAsDouble(double operand) 接收一个double值,返回一个double
IntUnaryOperator int applyAsInt(int operand) 接收一个int值,返回一个int结果
LongUnaryOperator long applyAsLong(long operand) 接收一个long值,返回一个long结果
BiFunction<T,U,R> R apply(T t, U u) 接收一个T类型和一个U类型对象,返回一个R类型对象结果
BinaryOperator T apply(T t, T u) 接收两个T类型对象,返回一个T类型对象结果
ToDoubleBiFunction<T,U> double applyAsDouble(T t, U u) 接收一个T类型和一个U类型对象,返回一个double
ToIntBiFunction<T,U> int applyAsInt(T t, U u) 接收一个T类型和一个U类型对象,返回一个int
ToLongBiFunction<T,U> long applyAsLong(T t, U u) 接收一个T类型和一个U类型对象,返回一个long
DoubleBinaryOperator double applyAsDouble(double left, double right) 接收两个double值,返回一个double结果
IntBinaryOperator int applyAsInt(int left, int right) 接收两个int值,返回一个int结果
LongBinaryOperator long applyAsLong(long left, long right) 接收两个long值,返回一个long结果

类型4:判断型接口

这类接口的抽象方法特点:有参,但是返回值类型是boolean结果。

接口名 抽象方法 描述
BiPredicate<T,U> boolean test(T t, U u) 接收两个对象
DoublePredicate boolean test(double value) 接收一个double值
IntPredicate boolean test(int value) 接收一个int值
LongPredicate boolean test(long value) 接收一个long值
3.4.4 内置接口代码演示

举例1:

package com.atguigu.four;
import java.util.Arrays;
import java.util.List;
public class TestConsumer {
    public static void main(String[] args) {
        List<String> list = Arrays.asList("java","c","python","c++","VB","C#");
        //遍历Collection集合,并将传递给action参数的操作代码应用在每一个元素上。
        list.forEach(s -> System.out.println(s));
    }
}

举例2:

package com.atguigu.four;
import java.util.function.Supplier;
public class TestSupplier {
    public static void main(String[] args) {
        Supplier<String> supplier = () -> "尚硅谷";
        System.out.println(supplier.get());
    }
}

举例3:

package com.atguigu.four;
import java.util.ArrayList;
public class TestPredicate {
    public static void main(String[] args) {
        ArrayList<String> list = new ArrayList<>();
        list.add("hello");
        list.add("java");
        list.add("atguigu");
        list.add("ok");
        list.add("yes");
        System.out.println("删除之前:");
        list.forEach(t-> System.out.println(t));
        //用于删除集合中满足filter指定的条件判断的。
        //删除包含o字母的元素
        list.removeIf(s -> s.contains("o"));
        System.out.println("删除包含o字母的元素之后:");
        list.forEach(t-> System.out.println(t));
    }
}

举例4:

package com.atguigu.four;
import java.util.function.Function;
public class TestFunction {
    public static void main(String[] args) {
        //使用Lambda表达式实现Function<T,R>接口,可以实现将一个字符串首字母转为大写的功能。
        Function<String,String> fun = s -> s.substring(0,1).toUpperCase() + s.substring(1);
        System.out.println(fun.apply("hello"));
    }
}
3.4.5 练习

练习1:无参无返回值形式

假如有自定义函数式接口Call如下:

public interface Call {
    void shout();
}

在测试类中声明一个如下方法:

public static void callSomething(Call call){
    call.shout();
}

在测试类的main方法中调用callSomething方法,并用Lambda表达式为形参call赋值,可以喊出任意你想说的话。

public class TestLambda {
  public static void main(String[] args) {
    callSomething(()->System.out.println("回家吃饭"));
    callSomething(()->System.out.println("我爱你"));
    callSomething(()->System.out.println("滚蛋"));
    callSomething(()->System.out.println("回来"));
  }
  public static void callSomething(Call call){
    call.shout();
  }
}
interface Call {
    void shout();
}

练习2:消费型接口

代码示例:Consumer接口

在JDK1.8中Collection集合接口的父接口Iterable接口中增加了一个默认方法:

public default void forEach(Consumer<? super T> action) 遍历Collection集合的每个元素,执行“xxx消费型”操作。

在JDK1.8中Map集合接口中增加了一个默认方法:

public default void forEach(BiConsumer<? super K,? super V> action)遍历Map集合的每对映射关系,执行“xxx消费型”操作。

案例:

(1)创建一个Collection系列的集合,添加一些字符串,调用forEach方法遍历查看

(2)创建一个Map系列的集合,添加一些(key,value)键值对,调用forEach方法遍历查看

示例代码:

@Test
  public void test1(){
    List<String> list = Arrays.asList("hello","java","lambda","atguigu");
    list.forEach(s -> System.out.println(s));
    }
  @Test
  public void test2(){
    HashMap<Integer,String> map = new HashMap<>();
    map.put(1, "hello");
    map.put(2, "java");
    map.put(3, "lambda");
    map.put(4, "atguigu");
    map.forEach((k,v) -> System.out.println(k+"->"+v));
  }

练习3:供给型接口

代码示例:Supplier接口

在JDK1.8中增加了StreamAPI,java.util.stream.Stream是一个数据流。这个类型有一个静态方法:

public static <T> Stream<T> generate(Supplier<T> s)可以创建Stream的对象。而又包含一个forEach方法可以遍历流中的元素:public void forEach(Consumer<? super T> action)

案例:

现在请调用Stream的generate方法,来产生一个流对象,并调用Math.random()方法来产生数据,为Supplier函数式接口的形参赋值。最后调用forEach方法遍历流中的数据查看结果。

@Test
  public void test2(){
    Stream.generate(() -> Math.random()).forEach(num -> System.out.println(num));
  }

练习4:功能型接口

代码示例:Function<T,R>接口

在JDK1.8时Map接口增加了很多方法,例如:

public default void replaceAll(BiFunction<? super K,? super V,? extends V> function) 按照function指定的操作替换map中的value。

public default void forEach(BiConsumer<? super K,? super V> action)遍历Map集合的每对映射关系,执行“xxx消费型”操作。

案例:

(1)声明一个Employee员工类型,包含编号、姓名、薪资。

(2)添加n个员工对象到一个HashMap<Integer,Employee>集合中,其中员工编号为key,员工对象为value。

(3)调用Map的forEach遍历集合

(4)调用Map的replaceAll方法,将其中薪资低于10000元的,薪资设置为10000。

(5)再次调用Map的forEach遍历集合查看结果

Employee类:

class Employee{
  private int id;
  private String name;
  private double salary;
  public Employee(int id, String name, double salary) {
    super();
    this.id = id;
    this.name = name;
    this.salary = salary;
  }
  public Employee() {
    super();
  }
  public int getId() {
    return id;
  }
  public void setId(int id) {
    this.id = id;
  }
  public String getName() {
    return name;
  }
  public void setName(String name) {
    this.name = name;
  }
  public double getSalary() {
    return salary;
  }
  public void setSalary(double salary) {
    this.salary = salary;
  }
  @Override
  public String toString() {
    return "Employee [id=" + id + ", name=" + name + ", salary=" + salary + "]";
  }
}

测试类:

import java.util.HashMap;
public class TestLambda {
  public static void main(String[] args) {
    HashMap<Integer,Employee> map = new HashMap<>();
    Employee e1 = new Employee(1, "张三", 8000);
    Employee e2 = new Employee(2, "李四", 9000);
    Employee e3 = new Employee(3, "王五", 10000);
    Employee e4 = new Employee(4, "赵六", 11000);
    Employee e5 = new Employee(5, "钱七", 12000);
    map.put(e1.getId(), e1);
    map.put(e2.getId(), e2);
    map.put(e3.getId(), e3);
    map.put(e4.getId(), e4);
    map.put(e5.getId(), e5);
    map.forEach((k,v) -> System.out.println(k+"="+v));
    System.out.println();
    map.replaceAll((k,v)->{
      if(v.getSalary()<10000){
        v.setSalary(10000);
      }
      return v;
    });
    map.forEach((k,v) -> System.out.println(k+"="+v));
  }
}

练习5:判断型接口

代码示例:Predicate接口

JDK1.8时,Collecton接口增加了一下方法,其中一个如下:

public default boolean removeIf(Predicate<? super E> filter) 用于删除集合中满足filter指定的条件判断的。

public default void forEach(Consumer<? super T> action) 遍历Collection集合的每个元素,执行“xxx消费型”操作。

案例:

(1)添加一些字符串到一个Collection集合中

(2)调用forEach遍历集合

(3)调用removeIf方法,删除其中字符串的长度<5的

(4)再次调用forEach遍历集合

import java.util.ArrayList;
public class TestLambda {
  public static void main(String[] args) {
    ArrayList<String> list = new ArrayList<>();
    list.add("hello");
    list.add("java");
    list.add("atguigu");
    list.add("ok");
    list.add("yes");
    list.forEach(str->System.out.println(str));
    System.out.println();
    list.removeIf(str->str.length()<5);
    list.forEach(str->System.out.println(str));
  }
}

练习6:判断型接口

案例:

(1)声明一个Employee员工类型,包含编号、姓名、性别,年龄,薪资。

(2)声明一个EmployeeSerice员工管理类,包含一个ArrayList集合的属性all,在EmployeeSerice的构造器中,创建一些员工对象,为all集合初始化。

(3)在EmployeeSerice员工管理类中,声明一个方法:ArrayList get(Predicate p),即将满足p指定的条件的员工,添加到一个新的ArrayList 集合中返回。

(4)在测试类中创建EmployeeSerice员工管理类的对象,并调用get方法,分别获取:

  • 所有员工对象
  • 所有年龄超过35的员工
  • 所有薪资高于15000的女员工
  • 所有编号是偶数的员工
  • 名字是“张三”的员工
  • 年龄超过25,薪资低于10000的男员工

示例代码:

Employee类:

public class Employee{
  private int id;
  private String name;
  private char gender;
  private int age;
  private double salary;
  public Employee(int id, String name, char gender, int age, double salary) {
    super();
    this.id = id;
    this.name = name;
    this.gender = gender;
    this.age = age;
    this.salary = salary;
  }
  public Employee() {
    super();
  }
  public int getId() {
    return id;
  }
  public void setId(int id) {
    this.id = id;
  }
  public String getName() {
    return name;
  }
  public void setName(String name) {
    this.name = name;
  }
  public double getSalary() {
    return salary;
  }
  public void setSalary(double salary) {
    this.salary = salary;
  }
  @Override
  public String toString() {
    return "Employee [id=" + id + ", name=" + name + ", gender=" + gender + ", age=" + age + ", salary=" + salary
        + "]";
  }
}

员工管理类:

class EmployeeService{
  private ArrayList<Employee> all;
  public EmployeeService(){
    all = new ArrayList<Employee>();
    all.add(new Employee(1, "张三", '男', 33, 8000));
    all.add(new Employee(2, "翠花", '女', 23, 18000));
    all.add(new Employee(3, "无能", '男', 46, 8000));
    all.add(new Employee(4, "李四", '女', 23, 9000));
    all.add(new Employee(5, "老王", '男', 23, 15000));
    all.add(new Employee(6, "大嘴", '男', 23, 11000));
  }
  public ArrayList<Employee> get(Predicate<Employee> p){
    ArrayList<Employee> result = new ArrayList<Employee>();
    for (Employee emp : result) {
      if(p.test(emp)){
        result.add(emp);
      }
    }
    return result;
  }
}

测试类:

public class TestLambda {
  public static void main(String[] args) {
    EmployeeService es = new EmployeeService();
    es.get(e -> true).forEach(e->System.out.println(e));
    System.out.println();
    es.get(e -> e.getAge()>35).forEach(e->System.out.println(e));
    System.out.println();
    es.get(e -> e.getSalary()>15000 && e.getGender()=='女').forEach(e->System.out.println(e));
    System.out.println();
    es.get(e -> e.getId()%2==0).forEach(e->System.out.println(e));
    System.out.println();
    es.get(e -> "张三".equals(e.getName())).forEach(e->System.out.println(e));
    System.out.println();
    es.get(e -> e.getAge()>25 && e.getSalary()<10000 && e.getGender()=='男').forEach(e->System.out.println(e));
  }
}
目录
相关文章
|
2天前
|
Java
Java中抽象类和接口有什么区别?
本文阐述了Java中抽象类和接口的区别,包括类型扩展、方法/属性访问控制符、方法实现、使用目的等方面的不同,并提供了抽象类和接口的使用示例,以及Java中其他类型的类(普通类、内部类等)的简介。
7 0
Java中抽象类和接口有什么区别?
|
12天前
|
Java 测试技术
Java接口的生产环境应用注意点
在Java生产环境中,合理使用接口对提升代码质量至关重要。设计接口时应遵循单一职责原则,采用清晰命名,并控制方法数量。默认方法应谨慎使用,避免与实现类产生冲突。通过版本化管理接口更新,确保向后兼容。实现接口时需明确行为,保持实现与接口分离,利用多态增强灵活性。关注性能影响,适当文档注释及充分测试确保接口稳定可靠。综合运用这些策略,可以显著提高系统的可扩展性和维护性。
|
12天前
|
Java
Java 接口的简化理解
Java 接口是一种强大的概念,用于定义方法签名而非具体实现,作为行为规范,强调功能而非实现细节。接口是特殊的引用类型,包含常量和方法签名。其特点包括:无实现方法体、支持多重继承、内置常量定义。通过示例展示了如何定义和实现接口,以及如何通过接口引用调用实现类的方法。接口的应用场景包括抽象化、插件架构和松耦合设计。从 Java 8 起,接口还支持默认方法和静态方法,进一步增强了其灵活性和扩展性。理解接口是 Java 编程的基础之一。
|
13天前
|
Java
接口和抽象类【Java面向对象知识回顾②】
本文讨论了Java中抽象类和接口的概念与区别。抽象类是不能被实例化的类,可以包含抽象和非抽象方法,常用作其他类的基类。接口是一种纯抽象类型,只包含抽象方法和常量,不能被实例化,且实现接口的类必须实现接口中定义的所有方法。文章还比较了抽象类和接口在实现方式、方法类型、成员变量、构造方法和访问修饰符等方面的不同,并探讨了它们的使用场景。
接口和抽象类【Java面向对象知识回顾②】
|
1天前
|
Java 编译器
【Java】用一个动物王国的例子,讲清楚抽象类和接口
【Java】用一个动物王国的例子,讲清楚抽象类和接口
7 0
|
Java
Java接口和抽象类
Java接口和抽象类
81 0
|
2月前
|
设计模式 Java
【惊天揭秘】Java编程绝技大曝光:接口、抽象类、静态类与非静态类的神秘面纱终被揭开!
【8月更文挑战第22天】Java支持面向对象编程,通过接口、抽象类、静态类(如枚举与工具类)及普通类实现设计原则。接口定义行为规范,允许多重继承;抽象类含未实现的抽象方法,需子类完成;静态类常为工具类,提供静态方法;普通类则实例化对象。恰当运用这些结构能提升程序质量。
34 2
|
5月前
|
设计模式 搜索推荐 Java
java接口和抽象类的区别,以及使用选择
java接口和抽象类的区别,以及使用选择
55 0
|
2月前
|
Java 开发者
Java中的接口和抽象类
Java中的接口和抽象类
26 3
|
3月前
|
网络协议 Java
Java面试题:什么是Java中的接口?与抽象类有什么区别?
Java面试题:什么是Java中的接口?与抽象类有什么区别?
34 0