Java函数式编程


函数式接口

有且只有一个抽象方法的接口被称为函数式接口,该接口中可以包含其他的方法(默认,静态,私有)。函数式接口用@FunctionalInterface注解。

引入函数式编程后Java接口的定义发生了一些变化:

  1. 函数式接口:函数式接口中只能有一个抽象方法,这也是为了函数调用时避免带来二义性。使用@FunctionInterface标注接口为函数式接口(@FunctionInterface并不是一定要标注但若是标注可以在编译时就给你提示错误)

  2. 支持静态方法:支持静态方法目的完全出于编写类库,对某些行为进行抽象,但是接口中的静态方法不能被继承。

  3. 支持默认实现:这是不得已而为之,因为Java8引入了函数式接口,许多像Collection这样的基础接口中增加了方法,如果还是一个传统的抽象方法的话,那么可能很多第三方类库就会变得完全无法使用。新增一个方法所有实现类都要实现一次。被default修饰的方法–默认实现。其主要思想就是如果子类中没有实现,那么采用父类提供的默认实现。

定义了这种类型的接口,使得以其为参数的方法,可以在调用时,使用一个lambda表达式作为参数。从另一个方面说,一旦我们调用某方法,可以传入lambda表达式作为参数,则这个方法的参数类型,必定是一个函数式的接口。

关于函数式接口只有一个抽象方法
关于函数式接口只有一个抽象方法的理解:

  1. 一个函数式接口有且只有一个抽象方法。

  2. 默认方法不是抽象方法,因为它们已经实现了。

  3. 重写了超类Object类中任意一个public方法的方法并不算接口中的抽象方法。

    如果一个接口中声明的抽象方法是重写了超类Object类中任意一个public方法,那么这些抽象方法并不会算入接口的抽象方法数量中。因为任何接口的实现都会从其父类Object或其它地方获得这些方法的实现。

所以比如Comparator接口,有两个抽象方法compare和equals,但equals并不算入接口中的抽象方法,所以Comparator接口还是满足函数式接口的要求,Comparator接口是一个函数式接口

Lambda表达式

Lambda 表达式,也可称为闭包,它是推动 Java 8 发布的最重要新特性。

Lambda 允许把函数作为一个方法的参数(函数作为参数传递进方法中)。使用 Lambda 表达式可以使代码变的更加简洁紧凑。Lambda 表达式免去了使用匿名方法的麻烦,并且给予Java简单但是强大的函数化的编程能力。

在Lambda表达式中无需像传统写法那样声明参数和返回值类型,它会根据你的上下文通过类型推导你实现的是哪一个接口,从而跟具这个接口的定义知道你变量的类型。这也是为什么函数式接口只能声明一个方法的原因。

增加Lambda的目的,其实就是为了支持函数式编程,而为了支持Lambda表达式,才有了函数式接口。

另外,为了在面对大型数据集合时,为了能够更加高效的开发,编写的代码更加易于维护,更加容易运行在多核CPU上,java在语言层面增加了Lambda表达式。

语法特征

(parameters) -> expression

(parameters) -> { statements; }

是lambda表达式的重要特征:

  • 可选类型声明:不需要声明参数类型,编译器可以统一识别参数值。
  • 可选的参数圆括号:一个参数无需定义圆括号,但多个参数需要定义圆括号。
  • 可选的大括号:如果主体包含了一个语句,就不需要使用大括号。
  • 可选的返回关键字:如果主体只有一个表达式返回值则编译器会自动返回值,大括号需要指定表达式返回了一个数值。

方法引用

方法的引用让你可以重复使用现有的方法定义,并像lambda一样传递他们,在一些情况下,比起使用lambda表达式,它们似乎更易读,感觉也更自然。

语法:

  • 类::静态方法名

    指向静态方法的方法引用,例如Integer的compare方法 ,可以写成Integer::compare

    Comparator<Integer> c = Integer::compare;
    
  • 类::实例方法名

    指向任意类型实例方法的方法引用,例如String的equals方法,写成String::equals

    BiPredicate<String, String> b = String::equals;
    
  • 对象::实例方法名

    指向现有对象的实例方法的方法引用,例如System.out的println方法,写成System.out::println

    Consumer<String> con1 = System.out::println;
    User user = new User();
    Consumer<String> con2 = user::setUserName;
    

构造器引用

  • 构造器的引用:ClassName::new

    对于一个现有构造函数,你可以利用它的名称和关键字new来创建它的一个引用,例如:String::new。

    Supplier<String> supplier = String::new;
    

Java8之前的函数式接口

Java8之前已经存在的函数式接口

函数式接口输入参数输出参数描述
Runnable--多线程自定义执行逻辑,无返回值
Callabler<V>-V多线程自定义执行逻辑,有返回值
PrivilegedAction<T>-T
Comparator<T>T、Tint比较器,常用语比较和排序
FileFilterFileboolean文件过滤器
PathMatcherPathboolean路径匹配器
InvocationHandlerObject、Method、Object[]Object动态代理,生成代理对象
PropertyChangeListenerPropertyChangeEvent-属性改变监听器
ActionListenerActionEvent-动作监听器
ChangeListenerChangeEvent-变动监听器

Java8新增的函数式接口

Java8新增的java.util.function包定义的函数式接口

函数式接口输入参数输出参数描述
Supplier<T>-T生产接口

指定接口的泛型是什么类型,那么接口的get方法就会生产什么类型的数据
IntSupplier-int
LongSupplier-long
DoubleSupplier-double
BooleanSupplier-boolean
Consumer<T>T-消费接口

指定接口的泛型是什么类型,那么接口的accept方法就会消费什么类型的数据
IntConsumerint-
LongConsumerlong-
DoubleConsumerdouble-
BiConsumer<T,U>T、U-
ObjIntConsumer<T>T、int-
ObjLongConsumer<T>T、long-
ObjDoubleConsumer<T>T、double-
Predicate<T>Tboolean判断接口

对接口的泛型类型数据进行判断,接口的test方法就是用来得到判断结果
IntPredicateintboolean
LongPredicatelongboolean
DoublePredicatedoubleboolean
BiPredicate<T, U>T、Uboolean
Function<T, R>TR转换接口

接口的apply方法把T数据类型的数据转换为R数据类型的数据
UnaryOperator<T>TT
ToIntFunction<T>Tint
ToLongFunction<T>Tlong
ToDoubleFunction<T>Tdouble
IntFunction<R>intR
IntToLongFunctionintlong
IntToDoubleFunctionintdouble
IntUnaryOperatorintint
LongFunction<R>longR
LongToIntFunctionlongint
LongToDoubleFunctionlongdouble
LongUnaryOperatorlonglong
DoubleFunction<R>doubleR
DoubleToIntFunctiondoubleint
DoubleToLongFunctiondoublelong
DoubleUnaryOperatordoubledouble
BiFunction<T, U, R>T、UR
ToIntBiFunction<T, U>T、Uint
ToLongBiFunction<T, U>T、Ulong
ToDoubleBiFunction<T, U>T、Udouble
BinaryOperator<T>T、TT
IntBinaryOperatorint、intint
LongBinaryOperatorlong、longlong
DoubleBinaryOperatordouble、doubledouble

Supplier(生产)

Supplier<T>
抽象方法

public T get():用来获取一个泛型参数指定类型的对象数据

Supplier< T > 接口被称之为生产型接口,指定接口的泛型是什么类型,那么接口中的get方法就会生产什么类型的数据。

举例:
求数组的最大值最小值

public static void main(String[] args) {
    Integer[] arr = {2, 7, 3, 4, 5};
    //求最大值
    Integer max = get(() -> {
        Integer a = arr[0];
        for (Integer integer : arr) {
            a = integer >= a ? integer : a;
        }
        return a;
    });
    System.out.println("max:"+max);
    // 求最小值
    Integer min = get(() -> {
        Integer a = arr[0];
        for (Integer integer : arr) {
            a = integer <= a ? integer : a;
        }
        return a;
    });
    System.out.println("min:"+min);
}

public  static <T> T get(Supplier<T> supplier) {
    return supplier.get();
}

日志

max:7
min:2

Consumer(消费)

Consumer<T>

抽象方法

public void accept(T t):对给定的参数执行此操作。   

默认方法

public default Consumer<T> andThen(Consumer<? super T> after):返回一个组合的 Consumer ,按顺序执行该操作,然后执行 after操作。 

Consumer<T> 接口被称之为消费型接口,指定接口的泛型是什么类型,那么就可以使用接口中的accept方法消费什么类型的数据,具体怎么消费就需要自定义。

而其默认方法则是可以把两个Consumer接口组合在一起对数据进行消费,谁写在前边就先消费谁,其调用格式为con1.andThen(con2).accept(s)。

举例:

public static void main(String[] args) {
    User user = new User();
    user.setId("1");
    // 打印用户信息
    consumer(user, (u) -> System.out.println(JSON.toJSONString(u)));
    // 修改用户信息
    consumer(user, (u) -> u.setId("2"));
    System.out.println(JSON.toJSONString(user));
}

public static void consumer(User user, Consumer<User> consumer) {
    consumer.accept(user);
}

日志

{"id":"1"}
{"id":"2"}

BiConsumer<T, U>
和Consumer<T>类似

举例:

public static void main(String[] args) {
    User user = new User();
    user.setId("1");
    // 修改用户ID为指定值
    biConsumer(user,"2", (u,id) ->u.setId(id));
    System.out.println(JSON.toJSONString(user));
}

public static void biConsumer(User user, String id, BiConsumer<User, String> biConsumer) {
    biConsumer.accept(user, id);
}

日志

{"id":"2"}

Predicate(判断)

Predicate<T>

抽象方法

public boolean test(T t):在给定的参数上评估这个谓词。

默认方法

public default Predicate<T> and(Predicate<? super T> other):返回一个组合的谓词,表示该谓词与另一个谓词的短路逻辑AND。
public default Predicate<T> or(Predicate<? super T> other):返回一个组合的谓词,表示该谓词与另一个谓词的短路逻辑或。  
public default Predicate<T> negate():返回表示此谓词的逻辑否定的谓词。

Predicate< T > 接口就是用来对某种数据类型的数据进行判断,接口中的test方法就是用来制定判断规则并把结果返回

其三个默认方法其实就是相对三个逻辑——与、或、非。

举例:

public static void main(String[] args) {
    Integer integer = 2;
    Predicate<Integer> predicate1 = (i) -> i > 1;
    Predicate<Integer> predicate2 = (i) -> i < 2;
    Predicate<Integer> predicate3 = (i) -> i < 0;

    System.out.println(predicate1.test(integer));
    System.out.println(predicate1.and(predicate2).test(integer));
    System.out.println(predicate1.negate().or(predicate3).test(integer));
}

日志:

true
false
false

BiPredicate<T, U>
和Predicate<T>类似
举例:

public static void main(String[] args) {
    Integer integer1 = 2;
    Integer integer2 = 2;
    BiPredicate<Integer, Integer> predicate1 = (i1, i2) -> i1.compareTo(i2) > 0;
    BiPredicate<Integer, Integer> predicate2 = (i1, i2) -> i1.compareTo(i2) == 0;

    System.out.println(predicate1.test(integer1, integer2));
    System.out.println(predicate1.or(predicate2).test(integer1, integer2));
    System.out.println(predicate2.negate().test(integer1, integer2));
}

日志:

false
true
false

Function(转换)

Function<T,R>
抽象方法

public R apply(T t):将此函数应用于给定的参数。 

默认方法

public default <V> Function<T,V> andThen(Function<? super R,? extends V> after):返回一个组合函数,首先将该函数应用于其输入,然后将 after函数应用于结果。  

Function< T , R > 接口被用作转换数据类型的,可调用其apply方法把T数据类型的数据转换为R数据类型的数据,但转换规则需要自定义

另外,其默认方法也是把两个Function接口连接起来,第二个Function接口会把第一个Function接口的输出结果作为输入,也可认为这是链式组合(类似方法的链式调用)

举例:

public static void main(String[] args) {
    Integer integer = 2;
    Function<Integer, Integer> function1 =  (i) -> i+1;
    Function<Integer, Integer> function2 =  (i) -> i*10;
    System.out.println(function1.apply(integer));
    System.out.println(function2.apply(integer));
    System.out.println(function1.andThen(function2).apply(integer));
}

日志:

3
20
30

BiFunction<T, U, R>
和Function<T,R>类似
举例:

public static void main(String[] args) {
    Integer integer1 = 2;
    Integer integer2 = 5;
    BiFunction<Integer, Integer, Integer> biFunction = (i1,i2) -> i1+i2;
    Function<Integer, Integer> function = (i) -> i*10;
    System.out.println(biFunction.apply(integer1, integer2));
    System.out.println(biFunction.andThen(function).apply(integer1, integer2));
}

日志:

7
70



参考文章:常用函数式接口的介绍与使用

  • 4
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值