[二] java8 函数式接口详解 函数接口详解 lambda表达式 匿名函数 方法引用使用含义 函数式接口实例 如何定义函数式接口
函数式接口详细定义
package java.lang; import java.lang.annotation.*; /** * An informative annotation type used to indicate that an interface * type declaration is intended to be a functional interface 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 * not 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. * *一种用于表示一个接口是Java语言规范定义的函数式接口的注解类型.Note that instances of functional interfaces can be created with * lambda expressions, method references, or constructor references. * *
If a type is annotated with this annotation type, compilers are * required to generate an error message unless: * *
*
*- The type is an interface type and not an annotation type, enum, or class. *
- The annotated type satisfies the requirements of a functional interface. *
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 {}
关键概念
从文件注释中我们可以看到函数式接口的关键概念函数式接口只有一个抽象方法 由于default方法有一个实现,所以他们不是抽象的. 如果一个接口定义了一个抽象方法,而他恰好覆盖了Object的public方法,仍旧不算做接口的抽象方法, 因为它终将会在某处得到一个实现.(如果不是public的那么计数) 也即是只有一个抽象方法默认不算,Object的public也不算 |
函数式接口的实例可以通过 lambda表达式 方法引用 或者构造方法引用进行表示 |
类型必须是接口,而不能是其他的比如class 而且需要符合函数式接口的定义要求 否则使用注解时编译器报错 |
不管他们是否有使用注解FunctionalInterface 进行注解, 编译器将会把任何满足函数式接口定义的接口当做一个函数式接口 也就是说不加也行,但是显然不加的话,就没有限制约束,后续可能增加了其他方法导致出错 |
常用函数式接口
四大基础接口 java.util.function 包
接口 抽象方法
|
java.util.function.Predicate |
java.util.function.Consumer |
java.util.function.Function |
java.util.function.Supplier 提供者 不需要输入,产出T 提供数据 无参构造方法 提供T类型对象 |
接口中的compose, andThen, and, or, negate 用来组合函数接口而得到更强大的函数接口 四大接口为基础接口,其他的函数接口都是通过这四个扩展而来的 此处的扩展是指概念的展开 不是平常说的继承或者实现,当然实现上可能是通过继承比如UnaryOperator |
扩展方式:
参数个数上扩展 ????????比如接收双参数的,有 Bi 前缀, 比如 BiConsumer为什么要有基本类型扩展
只有对象类型才能作为泛型参数,对于基本类型就涉及到装箱拆箱的操作,虽然是自动的 但是这不可避免给内存带来了额外的开销,装箱和拆箱都会带来开销 所以为了减小这些性能开销 对基本类型进行类型扩展 Stream 类的某些方法对基本类型和装箱类型做了区分 Java 8中,仅对 整型、长整型和双浮点型做了特殊处理 因为它们在数值计算中用得最多 对基本类型做特殊处理的方法在命名上有明确的规范- 如果参数是基本类型,则不加前缀只需类型名即可
- 如果方法返回类型为基本类型,则在基本类型前再加上一个 To
加了类型前缀[Int|Double|Long] 表示参数是基本类型, 如果在此基础上又加上了To 表示返回类型是基本类型 |
函数式接口的实例
函数式接口的实例可以通过 lambda表达式 方法引用 或者构造方法引用进行表示Lambda表达式
可以把Lambda表达式理解为简洁地表示可传递的匿名函数的一种方式,也就是用来表示匿名函数 它没有名称,但它有参数列表、函数主体、返回类型,可能还有一个可以抛出的异常列表。 特点- 匿名——我们说匿名,是因为它不像普通的方法那样有一个明确的名称:写得少而想得多!
- 函数——我们说它是函数,是因为Lambda函数不像方法那样属于某个特定的类。但和方法一样,Lambda有参数列表、函数主体、返回类型,还可能有可以抛出的异常列表。
- 传递——Lambda表达式可以作为参数传递给方法或存储在变量中。
- 简洁——无需像匿名类那样写很多模板代码。
(parameters) -> expression |
(parameters) -> { statements; } |
- 参数列表
- 箭头 ( -> 把参数列表与Lambda主体分隔开)
- Lambda主体 (表达式或者语句)
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变量重新赋值 ,编译器将会报错() -> {System.out.print("Hello");System.out.println(" World");};
方法引用
方法引用让你可以重复使用现有的方法定义 并像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-匿名函数/方法引用/构造方法引用; |
List
List filteredNum = listNum.stream().filter(i -> i.compareTo(5) < 0).collect(Collectors.toList());
System.out.println(filteredNum);
1. 通过形参类型或者变量类型 找到函数接口进而找到抽象方法的声明 2. 然后在与参数值进行比对查看是否匹配 |
如果不同的函数接口,具有相互兼容的抽象方法签名 那么一个Lambda表达式显然可以匹配多个函数接口 |
特殊的void兼容规则 如果一个Lambda的主体是一个语句表达式, 它就和一个返回void的函数描述符兼容(当然需要参数列表也兼容)。 就是说 如果主体是一个语句,不管做什么或者调用方法返回其他的类型,他都可以兼容void |
List
// Predicate返回了一个boolean
Predicate
// Consumer返回了一个void
Consumer
类型推断
类型推断的概念,在Java中不是第一次出现 Java SE 7之前,声明泛型对象的代码如下List |
List |
.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());