结合Java所有特性,系统全面讲解函数式接口及应用

Java
244
0
0
2023-07-09

技术的升级往往不是独立的,而是一次系统性的升级, 小部分升级通常是改BUG ,JDK8的升级意义非常重大,各个升级环环相扣!本篇介绍的函数式接口和上篇讲解的 紧密相关!本篇你只需要搞懂 什么是函数式接口 这个概念就行啦,代码写不写无所谓,非常简单!

掌握内容

  • 函数式接口概念和意义
  • 认识 JDK 内置函数式接口
  • 函数式接口配合Lambda实现
  • 自定义函数式接口
  • @FunctionalInterface注解作用

函数式接口

函数式接口就是一个有且仅有一个抽象方法,但是可以有多个非抽象方法的接口,是Lambda表达式的实现前提,可以使用 @FunctionalInterface 注解修饰

函数式接口意义

java 一直倡导面向对象,随着 Python 、 Scala 等语言的兴起和新技术的挑战,Java必须调整来支持更加广泛的技术要求,所以Java不单单OOP【面向对象编程】同样支持OOF【面向函数编程】。

以往需要通过匿名内部类实现,现在都可以通过Lambda实现, 其实Lambda表达式可以看做是一个函数式接口的实例

JDK内置函数式接口

注:不需要掌握,不需要掌握!我看网上很多资料都只写了四个内置接口,比较局限,这里对JDK内置接口做一个全面的说明,只需要知道有这么多内置接口,并不是只有四个就可以了

JDK8新增函数式接口:

JDK8新推出的函数式接口在 java.util. Function 包下

作用如下:

函数式接口

参数类型

返回类型

用途

Consumer 消费型接口

T

void

对类型为T的对象应用操作,包含方法: void accept(T t)

Supplier 供给型接口

T

返回类型为T的对象,包含方法: T get()

Function<T, R> 函数型接口

T

R

对类型为T的对象应用操作,并返回结果。结果是R类型的对象。包含方法: R apply(T t)

Predicate 断定型接口

T

boolean

确定类型为T的对象是否满足某约束,并返回boolean 值。包含方法: boolean test(T t)

BiFunction<T,U,R>

T, U

R

对类型为T,U参数应用操作,返回R类型的结果。包含方法为: Rapply(T t,U u)

UnaryOperator (Function子接口)

T

T

对类型为T的对象进行一元运算,并返回T类型的结果。包含方法为: Tapply(T t) ;

BinaryOperator (BiFunction子接口)

T,T

T

对类型为T的对象进行二元运算,并返回T类型的结果。包含方法为: Tapply(T t1,T t2) ;

BiConsumer<T,U>

T,U

void

对类型为T,U参数应用操作。包含方法为: voidaccept(Tt,Uu)

BiPredicate<T,U>

T,U

boolean

包含方法为: booleantest(Tt,Uu)

ToIntFunction

T

int

计算 int 值的函数

ToLongFunction

T

long

计算 long 值的函数

ToDoubleFunction

T

double

计算 double 值的函数

IntFunction

int

R

参数为 int 类型的函数

LongFunction

long

R

参数为 long 类型的函数

DoubleFunction

double

R

参数为 double 类型的函数

JDK8之前的函数式接口:

JDK8之前也存在函数式接口,在JDK8升级之后这些接口头部都加上了 @FunctionalInterface 修饰,如下:

  • java.lang.Runnable【熟悉吧,创建 线程 】
  • java.util.concurrent.Callable【创建线程】
  • java.security.PrivilegedAction【执行计算】
  • java.util.Comparator【Lambda一篇说过的 比较器】
  • java.io. File Filter【文件过滤器】
  • java.nio.file.PathMatcher【路径匹配】
  • java.lang.reflect.InvocationHandler【动态代理】
  • java.beans .PropertyChangeListener【属性变化监听器】
  • java.awt.event.ActionListener【事件监听器】
  • javax .swing.event.ChangeListener【change事件监听】

函数式接口使用

在上篇中我们已经使用过 Runnable Consumer Supplier Comparator 等接口,这里我们再使用 Function Predicate 接口,其他接口如果用到了可以照葫芦画瓢即可!

Function接口

接口定义如下:

小贴士:接口中有且仅有一个抽象方法的接口就是一个函数式接口,和默认实现以及 静态方法 无关。
 package java.util.function;

import java.util.Objects;

/**
 *
 * @param <T> 输入参数类型
 * @param <R> 返回参数类型
 *
 * @出自.8
 */@FunctionalInterface
public interface Function<T, R> {

    /**
    抽象方法:输入T类型参数,返回R类型的值
    T和R是 泛型 哦,小伙伴不要搞混
     */    R apply(T t);

    /**
     JDK新特性,接口中可以存在默认实现
     */    default <V> Function<V, R> compose(Function<? super V, ?  extends  T> before) {
        Objects.requireNon null (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));
    }

    /**
    JDK接口新特性:可以有静态方法
     */     static  <T> Function<T, T> identity() {
        return t -> t;
    }
} 

接口特点: 有一个输入参数和一个输出参数,也就是 一进一出 ,如果你有需求是传入一个参数并返回一个参数的需求可以使用该接口实现

需求:

实现一个 字符串 转换功能,将输入的英文字符都转换为大写返回

分析:

  • 输入和输出数据都是字符串所有泛型类型均为 String
  • 调用 apply方法进行计算之后接收返回值

代码实现:

 public class FunctionMain {
    public static void main(String[] args) {

        //、原始匿名内部类写法
        Function<String, String> function = new Function<String, String>() {
            @Override
            public String apply(String inputStr) {
                // 转换为大写
                return inputStr.toUpperCase();
            }
        };

        String result = function.apply("Just give me a chance to start!");
        System.out.println(result);

        //、Lambda表达式写法
        Function<String,String> function = inputStr -> inputStr.toUpperCase();
        String lambdaResult = function.apply("Lambda really smells good!");
        System.out.println(lambdaResult);

    }
} 

Predicate接口

接口定义:

该接口也存在默认实现和静态方法,但是只有一个抽象方法,所以也是一个函数式接口

 package java.util.function;

import java.util.Objects;


@FunctionalInterface
public interface Predicate<T> {

    /**
     根据参数输入判断是否正确,返回true或者false
     */    boolean test(T t);


    default Predicate<T> and(Predicate<? super T> other) {
        Objects.requireNonNull(other);
        return (t) -> test(t) && other.test(t);
    }


    default Predicate<T> negate() {
        return (t) -> !test(t);
    }

    default Predicate<T> or(Predicate<? super T> other) {
        Objects.requireNonNull(other);
        return (t) -> test(t) || other.test(t);
    }

    
    static <T> Predicate<T> isEqual(Object targetRef) {
        return (null == targetRef)
                ? Objects::isNull
                : object -> targetRef.equals(object);
    }
}

接口特点: 该接口根据传入数据通过计算之后返回true或者false,如果你想要做单个参数的判断可以使用该接口

小贴士:Java中有两个Predicate类,不要导错包,认准java.util.function包,当然自定义的类也不要起这个名字,【有许多初学者喜欢起同名的类】

需求: 判断输入的数据是否大于0

分析:

  • 泛型定义为 Integer 类型
  • 通过判断返回结果即可

代码实现:

 public class PredicateMain {
    public static void main(String[] args) {

        //、原始实现方式
        Predicate predicate = new Predicate<Integer>() {
            @Override
            public boolean test(Integer num) {
                return num >;
            }
        };
        // 调用test方法
        boolean result = predicate.test(1024);
        System.out.println(result);

        //、Lambda表达式实现
        Predicate<Integer> predicate = num -> num > 0;
        // 调用test方法
        boolean lambdaResult = predicate.test(-1024);
        System.out.println(lambdaResult);

    }
} 
小贴士:这些默认方法的接口,使用时不要调用错方法就行!

自定义函数式接口

分析:

  • 函数式接口就是有且仅有一个抽象方法,默认实现和静态方法不影响它是一个函数式接口【JDK8支持接口有默认方法和静态方法】
  • 接口,定义抽象即可,所以我这里都使用泛型,可以根据自己的需求定义,如果需求要限制类型也可以直接定义成具体的类型

接口定义:

 package com.stt.function.myfunction;

/**
 * 自定义函数式接口:
 * 定义:
 *、接口中只有一个抽象方法
 *、可以使用@FunctionInterface注释修饰,也可以不使用
 *      如果使用该注解报错,说明该接口不是一个函数式接口
 */@FunctionalInterface
public interface SttFunction<T,R,V> {

    /**
     * 接收两个参数,并返回一个参数
     * 注意:接口嘛,定义个大概就行了,具体什么参数,怎么返回就不需要说明了,具体实现的时候再说呗
     */    V calc(T t,R r);

} 

接口使用:

 package com.stt.function.myfunction;

public class SttFunctionMain {

    public static void main(String[] args) {
        //、原始方式,匿名内部类实现
        SttFunction<Integer, Integer, Integer> sttFunction = new SttFunction<Integer, Integer, Integer>() {
            @Override
            public Integer calc(Integer num, Integer num2) {
                return num * num2;
            }
        };
        Integer result = sttFunction.calc(2, 2);
        System.out.println(result);

        //、Lambda表达式调用
        SttFunction<Integer,Integer,Integer> sttFunction = (num,num2) -> num1 + num2;;

        Integer lambdaResult = sttFunction.calc(, 1);

        System.out.println(lambdaResult);

    }
} 
Lambda表达式就是香

包含默认实现的函数式接口:

包含默认方法和静态方法并不影响它是一个函数式接口

 package com.stt.function.myfunction;

/**
 * 自定义函数式接口:
 * 定义:
 *、接口中只有一个抽象方法
 *、可以使用@FunctionInterface注释修饰,也可以不使用
 *      如果使用该注解报错,说明该接口不是一个函数式接口
 */@FunctionalInterface
public interface SttFunction<T,R,V> {

    /**
     * 接收两个参数,并返回一个参数
     * 注意:接口嘛,定义个大概就行了,具体什么参数,怎么返回就不需要说明了,具体实现的时候再说呗
     */    V calc(T t,R r);
    
    default void defaultMethod() {
        System.out.println("也不知道实现点什么,反正JDK之后可以有默认实现!");
    }
    
    static void staticMethod() {
        System.out.println("同样不知道写点什么,反正JDK之后可以有静态方法!");
    }
} 

有多个抽象方法:

有两个以上抽象方法就不再是一个函数式接口,所以 @FunctionalInterface 注解报错,该注解可以用来 检验 接口是否为一个函数式接口

@FunctionalInterface注解

Java 8中专门为函数式接口引入了一个新的注解:@FunctionalInterface 。该注解放在接口上,表示此接口是一个函数式接口。并且提示编译器去检查接口是否仅包含一个抽象方法,即,是否符合 函数式编程 的定义

小贴士:如果自定义一个符合规范的函数式接口,也可以不加@FunctionalInterface注解,此注解只是起到一个提示编译器进行规范检查的作用

总结

  • 技术升级都是系统性的,仅升级修改某一部分通常是修复BUG
  • 函数式接口是Lambda的前提,JDK8之前通过匿名内部类实现,Lambda让编码变的简洁
  • 函数式接口中有且仅有一个抽象方法
  • 函数式接口可以使用 @FunctionalInterface 检验,也可以不使用该注解
  • JDK内置了许多函数式接口,可以按需使用,我们也可以自定义函数式接口
  • 在阅读部分框架源码时一定要认识Lambda表达式和函数式接口哦
下一篇为JDK8接口新特性,点关注不迷路!