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));
  }
}
目录
相关文章
|
5天前
|
安全 Java 大数据
探索Java的奇妙世界:语言特性与实际应用
探索Java的奇妙世界:语言特性与实际应用
|
6天前
|
存储 安全 Java
[Java基础面试题] Map 接口相关
[Java基础面试题] Map 接口相关
|
6天前
|
Java
【Java基础】详解面向对象特性(诸如继承、重载、重写等等)
【Java基础】详解面向对象特性(诸如继承、重载、重写等等)
10 0
|
11天前
|
Java 开发者
探索 Java 的函数式接口和 Lambda 表达式
【4月更文挑战第19天】Java 中的函数式接口和 Lambda 表达式提供了简洁、灵活的编程方式。函数式接口有且仅有一个抽象方法,用于与 Lambda(一种匿名函数语法)配合,简化代码并增强可读性。Lambda 表达式的优点在于其简洁性和灵活性,常用于事件处理、过滤和排序等场景。使用时注意兼容性和变量作用域,它们能提高代码效率和可维护性。
|
12天前
|
机器学习/深度学习 Java API
Java8中的新特性
Java8中的新特性
|
12天前
|
Java
Java接口中可以定义哪些方法?
【4月更文挑战第13天】
14 0
Java接口中可以定义哪些方法?
|
14天前
|
设计模式 Java
Java接口与抽象类
Java接口与抽象类
17 0
JAVA802_函数式接口的概述、Supplier供给型接口、consumer消费型接口、Predicate断定型接口、Function函数型接口(下)
③. consumer 只有输入,没有返回值 ④. Predicate有一个输入参数,返回boolean ⑤. Function:有一个输入参数,有一个输出
121 0
JAVA802_函数式接口的概述、Supplier供给型接口、consumer消费型接口、Predicate断定型接口、Function函数型接口(下)
|
3天前
|
设计模式 安全 Java
【JAVA】Java 中什么叫单例设计模式?请用 Java 写出线程安全的单例模式
【JAVA】Java 中什么叫单例设计模式?请用 Java 写出线程安全的单例模式