前言

  Java8中函数接口有很多,大概有几十个吧,具体究竟是多少我也数不清,所以一开始看的时候感觉一脸懵逼,不过其实根本没那么复杂,毕竟不应该也没必要把一个东西设计的很复杂。

几个单词

  在学习了解之前,希望大家能记住几个单词,掌握这几个单词,什么3,40个官方的函数接口都是小问题了,不信的话接着往下看啦。ok,那这几个单词呢分别是supplier 提供者,consumer 消费者,function 函数,operation 运算符,binary 二元(就是数学里二元一次方程那个二元,代表2个的意思),双重的

四大基础函数接口

  函数接口,你可以理解为对一段行为的抽象,简单点说可以在方法就是将一段行为作为参数进行传递,这个行为呢,可以是一段代码,也可以是一个方法,那你可以想象在java8之前要将一段方法作为参数传递只能通过匿名内部类来实现,而且代码很难看,也很长,函数接口就是对匿名内部类的优化。

  虽然类库中的基本函数接口特别多,但其实总体可以分成四类,就好像阿拉伯数字是无限多的,但总共就10个基本数字一样,理解了这4个,其他的就都明白了。

Functio<T,R>接口

   function,顾名思义,函数的意思,这里的函数是指数学上的函数哦,你也可以说是严格函数语言中的函数,例如haskell里的,他接受一个参数,返回一个值,永远都是这样,是一个恒定的,状态不可改变的方法。其实想讲函数这个彻底将明白可以再开一篇博客了,所以这里不详细的说了。

   上面说到,函数接口是对行为的抽象,因此我方便大家理解,就用java中的方法作例子。

   Fcuntion接口是对接受一个T类型参数,返回R类型的结果的方法的抽象,通过调用apply方法执行内容。

public class Operation{

/*
下面这个方法接受一个int类型参数a,返回a+1,符合我上面说的接受一个参数,返回一个值
所以呢这个方法就符合Function接口的定义,那要怎么用呢,继续看例子
*/
public static final int addOne(int a){
return a+1;
} /*
该方法第二个参数接受一个function类型的行为,然后调用apply,对a执行这段行为
*/
public static int oper(int a, Function<Integer,Integer> action){
return action.apply(a);
} /* 下面调用这个oper方法,将addOne方法作为参数传递 */
pulic static void main(String[] args){
int x = 1;
int y = oper(x,x -> addOne(x));//这里可以换成方法引用的写法 int y = oper(x,Operation::addOne)
System.out.printf("x= %d, y = %d", x, y); // 打印结果 x=1, y=2 /* 当然你也可以使用lambda表达式来表示这段行为,只要保证一个参数,一个返回值就能匹配 */
y = oper(x, x -> x + 3 ); // y = 4
y = oper(x, x -> x * 3 ); // y = 3
} }

这里的箭头指向的位置就是形参,可以看到第二个箭头的Lambda表达式指向了Funtion接口

Consumer 接口

Consumer 接口翻译过来就是消费者,顾名思义,该接口对应的方法类型为接收一个参数,没有返回值,可以通俗的理解成将这个参数'消费掉了',一般来说使用Consumer接口往往伴随着一些期望状态的改变或者事件的发生,例如最典型的forEach就是使用的Consumer接口,虽然没有任何的返回值,但是却向控制台输出了语句。

Consumer 使用accept对参数执行行为

    public static void main(String[] args) {
Consumer<String> printString = s -> System.out.println(s);
printString.accept("helloWorld!");
//控制台输出 helloWorld!
}

Supplier 接口

Supplier 接口翻译过来就是提供者,和上面的消费者相反,该接口对应的方法类型为不接受参数,但是提供一个返回值,通俗的理解为这种接口是无私的奉献者,不仅不要参数,还返回一个值,使用get()方法获得这个返回值

        Supplier<String> getInstance = () -> "HelloWorld!";
System.out.println(getInstance.get());
// 控偶值台输出 HelloWorld

Predicate 接口

predicate<T,Boolean> 谓语接口,顾名思义,中文中的‘是’与‘不是’是中文语法的谓语,同样的该接口对应的方法为接收一个参数,返回一个Boolean类型值,多用于判断与过滤,当然你可以把他理解成特殊的Funcation<T,R>,但是为了便于区分语义,还是单独的划了一个接口,使用test()方法执行这段行为

    public static void main(String[] args) {
Predicate<Integer> predOdd = integer -> integer % 2 == 1;
System.out.println(predOdd.test(5));
//控制台输出 5
}

其他的接口

介绍完正面这四种最基本的接口,剩余的接口就可以很容易的理解了,java8中定义了几十种的函数接口,但是剩下的接口都是上面这几种接口的变种,大多为限制参数类型,数量,下面举几个例子。

类型限制接口

  • 参数类型,例如IntPredicate,LongPredicate, DoublePredicate,这几个接口,都是在基于Predicate接口的,不同的就是他们的泛型类型分别变成了Integer,Long,Double,IntConsumer,LongConsumer, DoubleConsumer比如这几个,对应的就是Consumer<Integer>,Consumer<Long>,Consumer<Double>,其余的是一样的道理,就不再举例子了
  • 返回值类型,和上面类似,只是命名的规则上多了一个To,例如IntToDoubleFunction,IntToLongFunction, 很明显就是对应的Funtion<Integer,Double>Fcuntion<Integer,Long>,其余同理,另外需要注意的是,参数限制与返回值限制的命名唯一不同就是To,简单来说,前面不带To的都是参数类型限制,带To的是返回值类型限制,对于没有参数的函数接口,那显而易见只可能是对返回值作限制。例如LongFunction<R>就相当于Function<Long,R> 而多了一个To的ToLongFunction<T>就相当于Function<T,Long>,也就是对返回值类型作了限制。

数量限制接口

  • 有些接口需要接受两名参数,此类接口的所有名字前面都是附加上Bi,是Binary的缩写,开头也介绍过这个单词了,是二元的意思,例如BiPredicate,BiFcuntion等等,而由于java没有多返回值的设定,所以Bi指的都是参数为两个

Operator接口

  • 此类接口只有2个分别是UnaryOperator<T> 一元操作符接口,与BinaryOperator<T>二元操作符接口,这类接口属于Function接口的简写,他们只有一个泛型参数,意思是Funtion的参数与返回值类型相同,一般多用于操作计算,计算 a + b的BiFcuntion如果限制条件为Integer的话 往往要这么写BiFunction<Integer,Integer,Integer> 前2个泛型代表参数,最后一个代表返回值,看起来似乎是有点繁重了,这个时候就可以用BinaryOperator<Integer>来代替了。

下面是各种类型的接口的示意图,相信只要真正理解了,其实问题并不大

关于lambda的限制

Java8中的lambda表达式,并不是完全闭包,lambda表达式对值封闭,不对变量封闭。简单点来说就是局部变量在lambda表达式中如果要使用,必须是声明final类型或者是隐式的final例如

int num = 123;
Consumer<Integer> print = () -> System.out.println(num);

就是可以的,虽然num没有被声明为final,但从整体来看,他和final类型的变量的表现是一致的,可如果是这样的代码

int num = 123;
num ++;
Consumer<Integer> print = () -> System.out.println(num);

则无法通过编译器,这就是对值封闭(也就是栈上的变量封闭)

如果上文中的num是实例变量或者是静态变量就没有这个限制。

看到这里,自然而然就会有疑问为什么会这样?或者说为什么要这么设计。理由有很多,例如函数的不变性,线程安全等等等,这里我给一个简单的说明

  • 为什么局部变量会有限制而静态变量和全局变量就没有限制,因为局部变量是保存在栈上的,而众所周知,栈上的变量都隐式的表现了它们仅限于它们所在的线程,而静态变量与实例变量是保存在静态区与堆中的,而这两块区域是线程共享的,所以访问并没有问题。
  • 现在我们假设如果lambda表达式可以局部变量的情况,实例变量存储在堆中,局部变量存储在栈上,而lambda表达式是在另外一个线程中使用的,那么在访问局部变量的时候,因为线程不共享,因此lambda可能会在分配该变量的线程将这个变量收回之后,去访问该变量。所以说,Java在访问自由局部变量时,实际上是在访问它的副本,而不是访问原始变量。如果局部变量仅仅赋值一次那就没有什么区别了。
  • 严格保证这种限制会让你的代码变得无比安全,如果你学习或了解过一些经典的函数式语言的话,就会知道不变性的重要性,这也是为什么stream流可以十分方便的改成并行流的重要原因之一。

总结

本篇介绍了四大函数接口和他们引申出的各类接口,终点是对不同种类行为的封装导致了设计出不同的函数接口,另外在使用函数接口或者lambda表达式的时候,要注意lambda对值封闭这个特性。

下一篇:开始Java8之旅(五) -- Java8中的排序

开始Java8之旅(四) --四大函数接口的更多相关文章

  1. Java8函数之旅(四) --四大函数接口

    前言   Java8中函数接口有很多,大概有几十个吧,具体究竟是多少我也数不清,所以一开始看的时候感觉一脸懵逼,不过其实根本没那么复杂,毕竟不应该也没必要把一个东西设计的很复杂. 几个单词   在学习 ...

  2. java8中规范的四大函数式接口

    java8中规范的四大函数式接口: 1.Consumer<T>   :消费型接口    void accept(T t); 2.Supplier<T>      :供给型接口  ...

  3. 【Java 8】函数式接口(二)—— 四大函数接口介绍

    前言 Java8中函数接口有很多,大概有几十个吧,具体究竟是多少我也数不清,所以一开始看的时候感觉一脸懵逼,不过其实根本没那么复杂,毕竟不应该也没必要把一个东西设计的很复杂. 几个单词 在学习了解之前 ...

  4. Java8新特性—四大内置函数式接口

    Java8新特性--四大内置函数式接口 预备知识 背景 Lambda 的设计者们为了让现有的功能与 Lambda 表达式良好兼容,考虑了很多方法,于是产生了函数接口这个概念. 什么是函数式接口? 函数 ...

  5. [二] java8 函数式接口详解 函数接口详解 lambda表达式 匿名函数 方法引用使用含义 函数式接口实例 如何定义函数式接口

    函数式接口详细定义 package java.lang; import java.lang.annotation.*; /** * An informative annotation type use ...

  6. Java8学习笔记(二)--三个预定义函数接口

    三个函数接口概述 JDK预定义了很多函数接口以避免用户重复定义.最典型的是Function: @FunctionalInterface public interface Function<T, ...

  7. java8新特性——四大内置核心函数式接口

    在前面几篇简单介绍了一些Lambda表达式得好处与语法,我们知道使用Lambda表达式是需要使用函数式接口得,那么,岂不是在我们开发过程中需要定义许多函数式接口,其实不然,java8其实已经为我们定义 ...

  8. Java8内置的四大核心函数式接口

    package java_8; import org.junit.Test; import java.util.ArrayList; import java.util.Arrays; import j ...

  9. [一] java8 函数式编程入门 什么是函数式编程 函数接口概念 流和收集器基本概念

      本文是针对于java8引入函数式编程概念以及stream流相关的一些简单介绍 什么是函数式编程?   java程序员第一反应可能会理解成类的成员方法一类的东西 此处并不是这个含义,更接近是数学上的 ...

随机推荐

  1. 201521123122 Java 第二周学习总结

    1. 本周学习总结 1.进一步了解了对码云的使用,学会了将本地代码上传到码云以及将码云上的代码克隆到eclipse上. 2.感觉本章学的基本语法和c的基本上差不多啊 3.string的对象创建后无法修 ...

  2. 201521123100 《Java程序设计》第13周学习总结

    1. 本周学习总结 以你喜欢的方式(思维导图.OneNote或其他)归纳总结多网络相关内容. 2. 书面作业 1. 网络基础 1.1 比较ping www.baidu.com与ping cec.jmu ...

  3. 201521123113《Java程序设计》第10周学习总结

    1. 本周学习总结 2. 书面作业 本次PTA作业题集异常.多线程 Q1.finally 题目4-2 1.1 截图你的提交结果(出现学号) 1.2 4-2中finally中捕获异常需要注意什么? fi ...

  4. [04] Cookie概念和基本使用

    1.Cookie是什么 Cookie,中文名称为"小型文本文件"或"小甜饼",指某些网站为了辨别用户身份而储存在用户本地终端上的数据(通常经过加密). 很多网站 ...

  5. GUI PasswordField

    GUI.PasswordField public static function PasswordField(position: Rect, password: string, maskChar: c ...

  6. [android游戏开发初学]SurfaceView初探之缓冲区测试

    先上测试代码 private static class PathView extends SurfaceView implements SurfaceHolder.Callback{ private ...

  7. KMP算法的来龙去脉

    1. 引言 字符串匹配是极为常见的一种模式匹配.简单地说,就是判断主串TT中是否出现该模式串PP,即PP为TT的子串.特别地,定义主串为T[0-n−1]T[0-n−1],模式串为P[0-p−1]P[0 ...

  8. Dubbo与Zookeeper、SpringMVC整合和使用

    作为dubbo框架初学者,能让框架跑起来非常不容易,非常感谢网上诸多大神提供的文章,本人参考文章地址是:https://my.oschina.net/xshuai/blog/891281 不过别人的记 ...

  9. 转换时间对象和字符串对象&添加时间

    /* *基本思路,将字符串时间转化为时间对象,通过毫秒数来加减时间,然后在转化为字符串输出 */ //转化字符时间yy-mm-dd hh:mm:ss 为时间对象   使用split进行字符串的分割,取 ...

  10. 【转】css清除浮动float的三种方法总结,为什么清浮动?浮动会有那些影响?

    摘要: css清除浮动float的三种方法总结,为什么清浮动?浮动会有那些影响?     一.抛一块问题砖(display: block)先看现象: 分析HTML代码结构: <div class ...