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

函数式接口详细定义

packagejava.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 {@linkplainjava.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 {@codejava.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 {@codejava.lang.Object} or elsewhere.

*

*

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 {@codeFunctionalInterface}

* 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

*@since1.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           boolean test(T t);

Consumer          void accept(T t);

Function        R apply(T t);

Supplier             T get();

java.util.function.Predicate

断言 也就是条件测试器 接收条件,进行测试

接口定义了一个名叫test的抽象方法,它接受泛型T对象,并返回一个boolean。

test (条件测试) , and-or- negate(与或非) 方法

5b74db69b9ad086bf1fb20975a396359.png

java.util.function.Consumer

消费者 消费数据 接收参数,返回void  数据被消费了

定义了一个名叫accept的抽象方法,它接受泛型T的对象,没有返回(void)

你如果需要访问类型T的对象,并对其执行某些操作,就可以使用这个接口

9c1c5fd4ce5ea21c844737ca0832d11f.png

java.util.function.Function

函数 有输入有输出 数据转换功能

接口定义了一个叫作apply的方法,它接受一个泛型T的对象,并返回一个泛型R的对象。

24abb13488895e9b2f416e5be738f449.png

java.util.function.Supplier

提供者 不需要输入,产出T 提供数据

无参构造方法 提供T类型对象

abb5fa9bffa980b6e88fcf5ffc2fbd83.png

接口中的compose, andThen, and, or, negate 用来组合函数接口而得到更强大的函数接口

四大接口为基础接口,其他的函数接口都是通过这四个扩展而来的

此处的扩展是指概念的展开  不是平常说的继承或者实现,当然实现上可能是通过继承比如UnaryOperator

扩展方式:

参数个数上扩展

比如接收双参数的,有 Bi 前缀, 比如 BiConsumer, BiFunction ;

特殊常用的变形

比如 BinaryOperator , 是同类型的双参数 BiFunction ,二元操作符 ; UnaryOperator 是 Function 一元操作符。

类型上扩展

比如接收原子类型参数的,比如 [Int|Double|Long]  [Function|Consumer|Supplier|Predicate]

ca43573e8a18a247d8e1114198d6c204.png

为什么要有基本类型扩展

只有对象类型才能作为泛型参数,对于基本类型就涉及到装箱拆箱的操作,虽然是自动的

但是这不可避免给内存带来了额外的开销,装箱和拆箱都会带来开销

所以为了减小这些性能开销   对基本类型进行类型扩展

Stream 类的某些方法对基本类型和装箱类型做了区分

Java 8中,仅对 整型、长整型和双浮点型做了特殊处理  因为它们在数值计算中用得最多

对基本类型做特殊处理的方法在命名上有明确的规范

如果参数是基本类型,则不加前缀只需类型名即可

如果方法返回类型为基本类型,则在基本类型前再加上一个 To

总结一句话:

加了类型前缀[Int|Double|Long] 表示参数是基本类型, 如果在此基础上又加上了To  表示返回类型是基本类型

如有可能,应尽可能多地使用对基本类型做过特殊处理的方法,进而改善性能

函数式接口的实例

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

Lambda表达式

可以把Lambda表达式理解为简洁地表示可传递的匿名函数的一种方式,也就是用来表示匿名函数

它没有名称,但它有参数列表、函数主体、返回类型,可能还有一个可以抛出的异常列表。

特点

匿名——我们说匿名,是因为它不像普通的方法那样有一个明确的名称:写得少而想得多!

函数——我们说它是函数,是因为Lambda函数不像方法那样属于某个特定的类。但和方法一样,Lambda有参数列表、函数主体、返回类型,还可能有可以抛出的异常列表。

传递——Lambda表达式可以作为参数传递给方法或存储在变量中。

简洁——无需像匿名类那样写很多模板代码。

基本语法

Lambda的基本语法是

(parameters) -> expression

或(请注意语句的花括号)

(parameters) -> { statements; }

Lambda表达式三个部分

参数列表

箭头   ( -> 把参数列表与Lambda主体分隔开)

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 表达式中引用它,编译器就会报错

比如:

1ed0fa39009f9ffbdff20babd2217998.png

无需设置final 一切运行正常

09f93265d46905f3bc6253431983dd5b.png

一旦给hello变量重新赋值 ,编译器将会报错

19d878fc1cfa689a1629a2da0a48db3b.png

方法引用

方法引用让你可以重复使用现有的方法定义  并像Lambda一样传递它们

方法引用使用  :: 来表示

方法引用主要有三类

(1) 指向静态方法的方法引用(例如Integer的parseInt方法, 写作Integer::parseInt)

也就是静态方法作用于对象上

e46dcedd1f7e147c94467eda1ea4fa4e.png

示例:字符串转换为数值

30d07e7b453ed757e3bc4289d1ca1340.png

a8a0049fd5cfb253ae9fb0d8973c4721.png

(2)指向 任意类型实例方法 的方法引用(例如 String 的 length 方法,写作String::length)

你在引用一个对象的方法,而这个对象本身是Lambda的一个参数。例如,Lambda表达式(String s) -> s.toUppeCase()   可以写作String::toUpperCase

9124a61883bd636e76a5018ee0b2f349.png

示例:打印字符串的长度 1个 3个  2个   (没有空格和换行所以挤在一起了)

5ac5a22f471c0275909b816fe13b5c3c.png

7eef799faceaf20315da37a1be52634f.png

(3) 指向现有对象的实例方法的方法引用

比如lambda表达式中调用字符串helloString的charAt()方法  helloString就是一个现有对象

4f42cdeaba0f822f598f1ceb68c305b2.png

示例:获取字符串位于给定序列的charAt值

0c98649dac12fcd9200355b01a1eb88e.png

da7842f969361123c7be6fd3e8292512.png

构造函数引用

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

ClassName::new

它的功能与指向静态方法的引用类似

定义Class A   三个属性 设置了默认值 以观察构造方法的调用情况

classA {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;

}

@OverridepublicString 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('}');returnsb.toString();

}

}

5eeefbfcfd56b0e9902e9e7a6ed4f45f.png

73883b839eb8660354b478db5f69cde2.png

可以看到分别调用了,无参构造方法 一个参数构造方法以及两个参数构造方法

如果三个构造方法如何设置呢?

我们只需要定义函数接口即可

dc9ddb6cb4928a7b387004fc9c6f1e4e.png

db6569047b2aeae3e37ac1b38b5c909a.png

再次运行

2e6525c717dd98cb4215548f45fbdbc3.png

类型检查与类型推断

类型检查

我们知道当我们操作赋值运算时会有类型检查

比如:

d5f43707de865a35e929b3023aa8f37d.png

那么对于函数式接口与函数值呢

函数式接口 变量名 = Lambda-匿名函数/方法引用/构造方法引用;

那么函数作为值是如何进行类型检查的?

Lambda的类型是从使用Lambda的上下文推断出来的

上下文中Lambda表达式需要的类型称为目标类型

上下文是比如接受它传递的方法的形式参数,或接受它的值的局部变量

形式参数或者局部变量都会有类型的定义与声明

比如筛选 1~9之中小于5的数值

List 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);

9d82c08be632ac439f873d58f63a6d7d.png

这个示例中接收  Lambda表达式  作为参数的形式参数为  Predicate super T> predicate

也就是目标类型    函数接口为Predicate

找到了目标类型 我们的T为Integer

也就是Predicate

他的抽象方法为 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 list= new ArrayList<>();

// Predicate返回了一个boolean

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

// Consumer返回了一个void

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

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

类型推断

类型推断的概念,在Java中不是第一次出现

Java SE 7之前,声明泛型对象的代码如下

List list = new ArrayList();

Java 7中,可以使用如下代码:

List 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等

6875f3143993a54c6d7020017b40b926.png

然后就是定义变量 使用Lambda实例化

再接着就是方法调用

9d05522bee7201ed2420ab4b85b73f74.png

a6f664cb43001bf4cba6ff101fde4abf.png

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值