函数式接口详细定义

package java.lang;
import java.lang.annotation.*;
/**
* An informative annotation type used to indicate that an interface
* type declaration is intended to be a <i>functional interface</i> as
* defined by the Java Language Specification.
*
* Conceptually, a functional interface has exactly one abstract
* method. Since {@linkplain java.lang.reflect.Method#isDefault()
* default methods} have an implementation, they are not abstract. If
* an interface declares an abstract method overriding one of the
* public methods of {@code java.lang.Object}, that also does
* <em>not</em> count toward the interface's abstract method count
* since any implementation of the interface will have an
* implementation from {@code java.lang.Object} or elsewhere.
*
* <p>Note that instances of functional interfaces can be created with
* lambda expressions, method references, or constructor references.
*
* <p>If a type is annotated with this annotation type, compilers are
* required to generate an error message unless:
*
* <ul>
* <li> The type is an interface type and not an annotation type, enum, or class.
* <li> The annotated type satisfies the requirements of a functional interface.
* </ul>
* <p>However, the compiler will treat any interface meeting the
* definition of a functional interface as a functional interface
* regardless of whether or not a {@code FunctionalInterface}
* annotation is present on the interface declaration.
*
* @jls 4.3.2. The Class Object
* @jls 9.8 Functional Interfaces
* @jls 9.4.3 Interface Method Body
* @since 1.8
*/
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface FunctionalInterface {}
一种用于表示一个接口是Java语言规范定义的函数式接口的注解类型.
 

关键概念

从文件注释中我们可以看到函数式接口的关键概念
函数式接口只有一个抽象方法

由于default方法有一个实现,所以他们不是抽象的.
如果一个接口定义了一个抽象方法,而他恰好覆盖了Object的public方法,仍旧不算做接口的抽象方法, 因为它终将会在某处得到一个实现.(如果不是public的那么计数) 
也即是只有一个抽象方法默认不算,Object的public也不算
函数式接口的实例可以通过 lambda表达式  方法引用 或者构造方法引用进行表示
类型必须是接口,而不能是其他的比如class 而且需要符合函数式接口的定义要求 否则使用注解时编译器报错
不管他们是否有使用注解FunctionalInterface 进行注解, 编译器将会把任何满足函数式接口定义的接口当做一个函数式接口 也就是说不加也行,但是显然不加的话,就没有限制约束,后续可能增加了其他方法导致出错
 
 
 
 

常用函数式接口

四大基础接口   java.util.function 包
     接口                           抽象方法
  • Predicate<T>           boolean test(T t);
  • Consumer<T>          void accept(T t);
  • Function<T, R>        R apply(T t);
  • Supplier<T>             T get();
 
 
java.util.function.Predicate<T>
断言 也就是条件测试器 接收条件,进行测试
接口定义了一个名叫test的抽象方法,它接受泛型T对象,并返回一个boolean。
test (条件测试) , and-or- negate(与或非) 方法
 
 
java.util.function.Consumer<T>
消费者 消费数据 接收参数,返回void  数据被消费了
定义了一个名叫accept的抽象方法,它接受泛型T的对象,没有返回(void)
你如果需要访问类型T的对象,并对其执行某些操作,就可以使用这个接口
 
 
java.util.function.Function<T, R>
函数 有输入有输出 数据转换功能
接口定义了一个叫作apply的方法,它接受一个泛型T的对象,并返回一个泛型R的对象。
 
java.util.function.Supplier<T>
提供者 不需要输入,产出T 提供数据
无参构造方法 提供T类型对象
 

接口中的compose, andThen, and, or, negate 用来组合函数接口而得到更强大的函数接口
四大接口为基础接口,其他的函数接口都是通过这四个扩展而来的
此处的扩展是指概念的展开  不是平常说的继承或者实现,当然实现上可能是通过继承比如UnaryOperator
 

扩展方式:

 
参数个数上扩展
        比如接收双参数的,有 Bi 前缀, 比如 BiConsumer<T,U>, BiFunction<T,U,R> ;
特殊常用的变形
        比如 BinaryOperator , 是同类型的双参数 BiFunction<T,T,T> ,二元操作符 ; UnaryOperator 是 Function<T,T> 一元操作符。
类型上扩展
        比如接收原子类型参数的,比如 [Int|Double|Long]  [Function|Consumer|Supplier|Predicate]
 

为什么要有基本类型扩展

 
只有对象类型才能作为泛型参数,对于基本类型就涉及到装箱拆箱的操作,虽然是自动的
但是这不可避免给内存带来了额外的开销,装箱和拆箱都会带来开销
所以为了减小这些性能开销   对基本类型进行类型扩展
Stream 类的某些方法对基本类型和装箱类型做了区分
Java 8中,仅对 整型长整型双浮点型做了特殊处理  因为它们在数值计算中用得最多
 
对基本类型做特殊处理的方法在命名上有明确的规范
  • 如果参数是基本类型,则不加前缀只需类型名即可
  • 如果方法返回类型为基本类型,则在基本类型前再加上一个 To
总结一句话:
加了类型前缀[Int|Double|Long] 表示参数是基本类型, 如果在此基础上又加上了To  表示返回类型是基本类型 
如有可能,应尽可能多地使用对基本类型做过特殊处理的方法,进而改善性能
 
 

函数式接口的实例

 
函数式接口的实例可以通过 lambda表达式  方法引用 或者构造方法引用进行表示
 

Lambda表达式

可以把Lambda表达式理解为简洁地表示可传递的匿名函数的一种方式,也就是用来表示匿名函数
它没有名称,但它有参数列表、函数主体、返回类型,可能还有一个可以抛出的异常列表。
特点
  • 匿名——我们说匿名,是因为它不像普通的方法那样有一个明确的名称:写得少而想得多!
  • 函数——我们说它是函数,是因为Lambda函数不像方法那样属于某个特定的类。但和方法一样,Lambda有参数列表、函数主体、返回类型,还可能有可以抛出的异常列表。
  • 传递——Lambda表达式可以作为参数传递给方法或存储在变量中。
  • 简洁——无需像匿名类那样写很多模板代码。
基本语法
 
Lambda的基本语法是
(parameters) -> expression
或(请注意语句的花括号)
(parameters) -> { statements; } 
 
Lambda表达式三个部分
  1. 参数列表
  2. 箭头   ( -> 把参数列表与Lambda主体分隔开)
  3. Lambda主体 (表达式或者语句)
一些变形
    1.  表达式不包含参数,使用空括号 () 表示没有参数         () -> System.out.println("Hello World");      
    2.  包含且只包含一个参数,可省略参数的括号                s  -> System.out.println("Hello World"); 
    3.  Lambda 表达式的主体不仅可以是一个表达式,而且也可以是一段代码块,使用大括号({})将代码块括起来
         该代码块和普通方法遵循的规则别无二致,可以用返回或抛出异常来退出。
         只有一行代码的 Lambda表达式也可使用大括号,用以明确 Lambda表达式从何处开始、到哪里结束。
() -> {
    System.out.print("Hello");
    System.out.println(" World");
};
  4.   Lambda 表达式也可以表示包含多个参数的方法   (Long x, Long y) -> x + y; 
     5.    可以把  4  中的表达式进行简化,(x, y) -> x + y;    这借助于类型推断 下面会说到 
 
Lambda只能引用值,而不是变量(要求事实上的final)
匿名内部类,需要引用它所在方法里的变量时,需要将变量声明为 final 
Lambda表达式不要求必须是final 变量  但是,该变量在既成事实上必须是final 
事实上的 final 是指只能给该变量赋值一次。换句话说,Lambda 表达式引用的是值,而不是变量 跟匿名内部类类似,使用的是变量值的拷贝 所以需要是不改变的
如果你试图给该变量多次赋值,然后在 Lambda 表达式中引用它,编译器就会报错
比如:
无需设置final 一切运行正常
一旦给hello变量重新赋值 ,编译器将会报错
 
 

方法引用

方法引用让你可以重复使用现有的方法定义  并像Lambda一样传递它们
方法引用使用  :: 来表示
方法引用主要有三类
(1) 指向静态方法的方法引用(例如Integer的parseInt方法, 写作Integer::parseInt) 
      也就是静态方法作用于对象上
 
示例:字符串转换为数值
 
 
 
(2)指向 任意类型实例方法 的方法引用(例如 String 的 length 方法,写作String::length)
  你在引用一个对象的方法,而这个对象本身是Lambda的一个参数。例如,Lambda表达式(String s) -> s.toUppeCase()   可以写作String::toUpperCase  
 
 
示例:打印字符串的长度 1个 3个  2个   (没有空格和换行所以挤在一起了)
 
 
(3) 指向现有对象的实例方法的方法引用
比如lambda表达式中调用字符串helloString的charAt()方法  helloString就是一个现有对象
示例:获取字符串位于给定序列的charAt值
 

构造函数引用

对于一个现有构造函数,你可以利用它的名称和关键字new来创建它的一个引用:
ClassName::new
它的功能与指向静态方法的引用类似
 
定义Class A   三个属性 设置了默认值 以观察构造方法的调用情况
class A {
private String s1="a";
private String s2="b";
private String s3="c";
A(){
}
A(String s1){
this.s1=s1;
} A(String s1,String s2){
this.s1=s1;
this.s2=s2;
}
A(String s1,String s2,String s3){
this.s1=s1;
this.s2=s2;
this.s3=s3;
} @Override
public String toString() {
final StringBuilder sb = new StringBuilder("A{");
sb.append("s1='").append(s1).append('\'');
sb.append(", s2='").append(s2).append('\'');
sb.append(", s3='").append(s3).append('\'');
sb.append('}');
return sb.toString();
}
}
可以看到分别调用了,无参构造方法 一个参数构造方法以及两个参数构造方法
 
如果三个构造方法如何设置呢?
我们只需要定义函数接口即可
 
再次运行
 
 
 
 

类型检查与类型推断

 

类型检查

我们知道当我们操作赋值运算时会有类型检查
比如:
那么对于函数式接口与函数值呢 
函数式接口 变量名 = Lambda-匿名函数/方法引用/构造方法引用;
 
那么函数作为值是如何进行类型检查的?
 
Lambda的类型是从使用Lambda的上下文推断出来的
上下文中Lambda表达式需要的类型称为目标类型
上下文是比如接受它传递的方法的形式参数,或接受它的值的局部变量
形式参数或者局部变量都会有类型的定义与声明
 
比如筛选 1~9之中小于5的数值

List<Integer> listNum = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9);

List filteredNum = listNum.stream().filter(i -> i.compareTo(5) < 0).collect(Collectors.toList());

System.out.println(filteredNum);

这个示例中接收  Lambda表达式  作为参数的形式参数为  Predicate<? super T> predicate
也就是目标类型    函数接口为Predicate<T> 
找到了目标类型 我们的T为Integer
也就是Predicate<Integer> 
他的抽象方法为 boolean test(T t);   也就是  boolean test(Integer t);  接收一个Integer返回一个boolean
我们的Lambda匿名函数 i -> i.compareTo(5) < 0 就是接收一个Integer  返回一个boolean 所以类型检查通过
简单说就是:
1. 通过形参类型或者变量类型 找到函数接口进而找到抽象方法的声明
2. 然后在与参数值进行比对查看是否匹配
 
可以看得出来,Lambda表达式最终匹配的是 函数接口中的抽象方法的方法签名
如果不同的函数接口,具有相互兼容的抽象方法签名  那么一个Lambda表达式显然可以匹配多个函数接口
 
特殊的void兼容规则
如果一个Lambda的主体是一个语句表达式, 它就和一个返回void的函数描述符兼容(当然需要参数列表也兼容)。
就是说 如果主体是一个语句,不管做什么或者调用方法返回其他的类型,他都可以兼容void
 
例如
List的add方法   boolean add(E e);

List<String> list= new ArrayList<>();

// Predicate返回了一个boolean

Predicate<String> p = s -> list.add(s);

// Consumer返回了一个void

Consumer<String> b = s -> list.add(s);

上面的代码都可以通过编译,并且运行
 

类型推断

 
类型推断的概念,在Java中不是第一次出现
Java SE 7之前,声明泛型对象的代码如下
List<String> list = new ArrayList<String>();
Java 7中,可以使用如下代码:
List<String> list = new ArrayList<>();
这就是类型推断  ,一个最直接直观的好处就是可以简化代码的书写,这不就是语法糖么
 
针对 Lambda表达式也有类型推断
Java编译器可以根据  上下文(目标类型)推断出用什么函数式接口来配合Lambda表达式
然后就可以获取到函数接口对应的函数描述符也就是那个抽象方法的方法签名
编译器可以了解Lambda表达式的参数类型,这样就可以在Lambda语法中省去标注参数类型
 
 
比如刚才的筛选 1~9之中小于5的数值的例子中,就可以有如下几种写法

.filter((Integer i) -> { return i.compareTo(5) < 0;}).collect(Collectors.toList());

.filter((Integer i) ->i.compareTo(5) < 0).collect(Collectors.toList());

.filter(i ->i.compareTo(5) < 0).collect(Collectors.toList());

 
 

如何使用函数式接口

 
函数式接口定义了函数的类型   有了类型就如同其他类型 比如 int 一样  
你可以定义变量
你可以传递参数
你可以返回
 
一个函数方法有方法签名和方法体两部分内容组成
函数接口只是有了方法签名
方法体由函数式接口的实例传递(也就是Lambda表达式-匿名函数   方法引用 构造方法引用 )
具体的调用则是调用抽象方法  抽象方法的方法体就是函数式接口的实例
比如:
定义一个函数式接口,也可以使用预置的 比如 Predicate等
 
然后就是定义变量 使用Lambda实例化
再接着就是方法调用
 
 
 
 

[二] java8 函数式接口详解 函数接口详解 lambda表达式 匿名函数 方法引用使用含义 函数式接口实例 如何定义函数式接口的更多相关文章

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

    函数式接口详细定义 函数式接口只有一个抽象方法 由于default方法有一个实现,所以他们不是抽象的. 如果一个接口定义了一个抽象方法,而他恰好覆盖了Object的public方法,仍旧不算做接口的抽 ...

  2. Python:lambda表达式(匿名函数)

    lambda表达式: 通常是在需要一个函数,但是又不想费神去命名一个函数的场合下使用,也就是指匿名函数. 当我们在传入函数时,有些时候,不需要显式地定义函数,直接传入匿名函数更方便. 在Python中 ...

  3. Java8 Lambda表达式实战之方法引用(一)

    方法的引用 方法引用是用来直接访问类或者实例的已经存在的方法或者构造方法,方法引用提供了一种引用而不执行方法的方式,如果抽象方法的实现恰好可以使用调用另外一个方法来实现,就有可能可以使用方法引用 方法 ...

  4. lambda表达式 匿名函数

    lambda函数是一种快速定义单行最小函数的方法,是从Lisp借鉴而来的,可以用在任何需要函数的地方. 基础 lambda语句中,冒号前是参数,可以有多个,用逗号分割:冒号右边是返回值. lambda ...

  5. C++中对C的扩展学习新增语法——lambda 表达式(匿名函数)

    1.匿名函数基础语法.调用.保存 1.auto lambda类型 2.函数指针来保存注意点:[]只能为空,不能写东西 3.std::function来保存 2.匿名函数捕捉外部变量(值方式.引用方式) ...

  6. lambda表达式匿名函数

    匿名函数是一个“内联”语句或表达式,可在需要委托类型的任何地方使用.可以使用匿名函数来初始化命名委托,或传递命名委托(而不是命名委托类型)作为方法参数. C# 中委托的发展 在 C# 1.0 中,您通 ...

  7. C#函数式程序设计之函数、委托和Lambda表达式

    C#函数式程序设计之函数.委托和Lambda表达式 C#函数式程序设计之函数.委托和Lambda表达式   相信很多人都听说过函数式编程,提到函数式程序设计,脑海里涌现出来更多的是Lisp.Haske ...

  8. Lambda表达式匿名类实现接口方法

    Lamb表达式匿名类实现接口方法 import java.util.ArrayList; public class HandlerDemo{ public static void main(Strin ...

  9. Lambda表达式和函数试接口的最佳实践 · LiangYongrui's Studio

    1.概述 本文主要深入研究java 8中的函数式接口和Lambda表达式,并介绍最佳实践. 2.使用标准的函数式接口 包java.util.function中的函数是接口已经可以满足大部分的java开 ...

随机推荐

  1. react 的进阶

    一 react 中table报错 validateDOMNesting(...): <tr> cannot appear as a child of <table>. See ...

  2. ctf密码学习题总结

    1.变异凯撒 加密密文:afZ_r9VYfScOeO_UL^RWUc格式:flag{ }   一看题中说的是凯撒加密,我就赶快使用工具列出了所有的组合,然而发现没有一个是我想要的. 于是乎,又重新审题 ...

  3. 【转】 微软在.NET官网上线.NET 架构指南

    原文地址:http://www.cnblogs.com/shanyou/p/6676357.html. 微软在Visual Studio 2017 正式发布的时候也上线了一个参考应用https://g ...

  4. 201771010118 马昕璐《面向对象程序设计java》第十周学习总结

    第一部分:理论知识学习部分 泛型:也称参数化类型(parameterized type)就是在定义类.接口和方法时,通过类型参数 指示将要处理的对象类型. 泛型程序设计(Generic program ...

  5. 远程shell脚本执行工具类

    /** * 远程shell脚本执行工具类 */public class RemoteShellExecutorUtils { private static final Logger logger = ...

  6. cadence网络表解读及导入

    绘制完成原理图,并且通过了DRC检验之后,需要创建和导入网络表,下面网络表内容做简单总结.

  7. layui 表格内容显示更改

    在cole 中使用temple 属性进行修改 例: table.render({ elem: '#messageTable' ,id: 'search_table_mId' ,height: 500 ...

  8. python 中间件

    中间件一.什么是中间件 中间件是一个用来处理Django的请求和响应的框架级别的钩子.它是一个轻量.低级别的插件系统,用于在全局范围内改变Django 的输入和输出.每个中间件组件都负责做一些特定的功 ...

  9. Three.js学习笔记02

    1.改变相机的位置,让物体移动 通过下面的代码改变相机的位置: camera.position.x =camera.position.x +1; 将相机不断的沿着x轴移动1个单位,也就是相机向右移动. ...

  10. 3-3 Hadoop集群完全分布式配置部署

    Hadoop集群完全分布式配置部署 下面的部署步骤,除非说明是在哪个服务器上操作,否则默认为在所有服务器上都要操作.为了方便,使用root用户. 1.准备工作 1.1 centOS6服务器3台 手动指 ...