Java8新特性探索之函数式接口
一、为什么引入函数式接口
作为Java函数式编程爱好者,我们都知道方法引用和 Lambda 表达式都必须被赋值,同时赋值需要类型信息才能使编译器保证类型的正确性。
我们先看一个Lambda代码示例:
x -> x.toString()
我们清楚这里返回类型必须是 String,但 x 是什么类型呢?
Lambda 表达式包含类型推导(编译器会自动推导出类型信息,避免了程序员显式地声明),编译器必须能够以某种方式推导出 x 的类型以生成正确的代码。
同样方法引用也存在此问题,假设你要传递 System.out :: println 到你正在编写的方法 ,你怎么知道传递给方法的参数的类型?
为了解决上述问题,Java 8 引入了函数式接口,在 java.util.function 包,它包含一组接口,这些接口是 Lambda 表达式和方法引用的目标类型,每个接口只包含一个抽象方法,称为函数式方法。只有确保接口中有且仅有一个抽象方法,Lambda表达式的类型信息才能顺利地进行推导。
二、如何使用函数式接口
在编写接口时,可以使用 @FunctionalInterface 注解强制执行此函数式方法模式:
在接口上使用注解
@FunctionalInterface,一旦使用该注解来定义接口,编译器将会强制检查该接口是否确实有且仅有一个抽象方法,否则将会报错。@FunctionalInterface
public interface MyFunction {
/**
* 自定义的抽象方法
*/
void run();
}在函数式接口,有且仅有一个抽象方法,
Object的public方法除外@FunctionalInterface
public interface MyFunction {
/**
* 自定义的抽象方法
*/
void run();
/**
* Object的equals方法
* @param obj
* @return
*/
@Override
boolean equals(Object obj);
/**
* Object的toString方法
* @return
*/
@Override
String toString();
/**
* Object的hashCode方法
* @return
*/
@Override
int hashCode();
}在函数式接口中,我们可以使用
default修饰符定义默认方法,使用static修饰符定义静态方法@FunctionalInterface
public interface MyFunction {
/**
* 自定义的抽象方法
*/
void run();
/**
* static修饰符定义静态方法
*/
static void staticRun() {
System.out.println("接口中的静态方法");
}
/**
* default修饰符定义默认方法
*/
default void defaultRun() {
System.out.println("接口中的默认方法");
}
}
为大家演示下自定义无泛型的函数式接口测试实例:
/**
* 自定义的无泛型函数式接口
*/
@FunctionalInterface
public interface MyFunction {
/**
* 自定义的抽象方法
* @param x
*/
void run(Integer x);
/**
* default修饰符定义默认方法
* @param x
*/
default void defaultMethod(Integer x) {
System.out.println("接口中的默认方法,接收参数是:" + x);
}
}
/**
* 测试类
*/
public class MyFunctionTest {
@Test
public void functionTest() {
test(6, (x) -> System.out.println("接口中的抽象run方法,接收参数是:" + x));
}
public void test(int n, MyFunction function) {
System.out.println(n);
function.defaultMethod(n);
function.run(n);
}
}输出结果:
6
接口中的默认方法,接收参数是:6
接口中的抽象run方法,接收参数是:6为大家演示下自定义有泛型的函数式接口测试实例:
/**
* 自定义的有泛型函数式接口
*/
@FunctionalInterface
public interface MyFunctionGeneric<T> {
/**
* 转换值
* @param t
* @return
*/
T convertValue(T t);
}
/**
* 测试类
*/
public class MyFunctionGenericTest {
@Test
public void convertValueTest() {
String result = toLowerCase((x) -> x.toLowerCase(), "ABC");
System.out.println(result);
}
public String toLowerCase(MyFunctionGeneric<String> functionGeneric, String value) {
return functionGeneric.convertValue(value);
}
}输出结果:
abc注意:作为参数传递 Lambda 表达式:为了将 Lambda 表达式作为参数传递,接收Lambda 表达式的参数类型必须是与该 Lambda 表达式兼容的函数式接口
的类型。
三、Java8四大内置核心函数式接口
首先总览下四大函数式接口的特点说明:
| 接口 | 参数类型 | 返回类型 | 方法 | 说明 |
|---|---|---|---|---|
| Consumer | T | void | void accept(T t) | 消费型接口,对类型T参数操作,无返回结果 |
| Supplier | - | T | T get() | 供给型接口,创造T类型参数 |
| Function | T | R | R apply(T t) | 函数型接口,对类型T参数操作,返回R类型参数 |
| Predicate | T | boolean | boolean test(T t) | 断言型接口,对类型T进行条件筛选操作 |
消费型接口
Consumer<T>
java.util.function.Consumer<T> 接口是消费一个数据,其数据类型由泛型决定。
接口源码:
package java.util.function;
import java.util.Objects;
@FunctionalInterface
public interface Consumer<T> {
void accept(T t);
default Consumer<T> andThen(Consumer<? super T> after) {
Objects.requireNonNull(after);
return (T t) -> { accept(t); after.accept(t); };
}
}
抽象方法: void accept(T t),接收并消费一个指定泛型的数据,无需返回结果。默认方法: default Consumer<T> andThen(Consumer<? super T> after),如果一个方法的参数和返回值全都是 Consumer 类型,那么就可以实现效果:消费数据的时候,首先做一个操作,然后再做一个操作,实现组合
public class ConsumerTest {
/**
* 先计算总分,再计算平均分
*/
@Test
public void calculate() {
Integer[] fraction = new Integer[] { 65, 76, 85, 92, 88, 99 };
consumer(fraction, x -> System.out.println(Arrays.stream(x).mapToInt(Integer::intValue).sum()),
y -> System.out.println(Arrays.stream(y).mapToInt(Integer::intValue).average().getAsDouble()));
}
public void consumer(Integer[] fraction, Consumer<Integer[]> x, Consumer<Integer[]> y) {
x.andThen(y).accept(fraction);
}
}
输出结果:
505
84.16666666666667
由于Consumer的default方法所带来的嵌套调用(连锁调用),对行为的抽象的函数式编程理念,展示的淋漓尽致。
其他的消费型函数式接口汇总说明:
| 接口名称 | 方法名称 | 方法签名 |
|---|---|---|
| DoubleConsumer | accept | (double) -> void |
| IntConsumer | accept | (int) -> void |
| LongConsumer | accept | (long) -> void |
| ObjDoubleConsumer | accept | (T, double) -> void |
| ObjIntConsumer | accept | (T, int) -> void |
| ObjLongConsumer | accept | (T, long) -> void |
供给型接口
Supplier<T>
java.util.function.Supplier<T> 接口仅包含一个无参的方法: T get() ,用来获取一个泛型参数指定类型的对象数据。
接口源码:
package java.util.function;
@FunctionalInterface
public interface Supplier<T> {
T get();
}
由于这是一个函数式接口,意味着对应的Lambda表达式需要对外提供一个符合泛型类型的对象数据。
public class SupplierTest {
public int getMax(Supplier<Integer> supplier) {
return supplier.get();
}
/**
* 获取数组元素最大值
*/
@Test
public void getMaxTest() {
Integer[] data = new Integer[] { 5, 4, 6, 3, 2, 1 };
int result = getMax(() -> {
int max = 0;
for (int i = 0; i < data.length; i++) {
max = Math.max(max, data[i]);
}
return max;
});
System.out.println(result);
}
}
其他的供给型函数式接口汇总说明:
| 接口名称 | 方法名称 | 方法签名 |
|---|---|---|
| BooleanSupplier | getAsBoolean | () -> boolean |
| DoubleSupplier | getAsDouble | () -> double |
| IntSupplier | getAsInt | () -> int |
| LongSupplier | getAsLong | () -> long |
函数型接口
Function
java.util.function.Function<T,R> 接口用来根据一个类型的数据得到另一个类型的数据,前者称为前置条件,后者称为后置条件。
接口源码:
package java.util.function;
import java.util.Objects;
@FunctionalInterface
public interface Function<T, R> {
R apply(T t);
default <V> Function<V, R> compose(Function<? super V, ? extends T> before) {
Objects.requireNonNull(before);
return (V v) -> apply(before.apply(v));
}
default <V> Function<T, V> andThen(Function<? super R, ? extends V> after) {
Objects.requireNonNull(after);
return (T t) -> after.apply(apply(t));
}
static <T> Function<T, T> identity() {
return t -> t;
}
}
抽象方法
apply(T t):该方法接收入参是一个泛型T对象,并返回一个泛型T对象。默认方法
andThen(Function<? super R, ? extends V> after):该方法接受一个行为,并将父方法处理过的结果作为参数再处理。compose(Function<? super V, ? extends T> before):该方法正好与andThen相反,它是先自己处理然后将结果作为参数传给父方法执行。@Test
public void andThenAndComposeTest() {
// 计算公式相同
Function<Integer, Integer> andThen1 = x -> x + 1;
Function<Integer, Integer> andThen2 = x -> x * 2;
Function<Integer, Integer> compose1 = y -> y + 1;
Function<Integer, Integer> compose2 = y -> y * 2;
// 注意调用的先后顺序
// 传入参数2后,先执行andThen1计算,将结果再传入andThen2计算
System.out.println(andThen1.andThen(andThen2).apply(2));
// 传入参数2后,先执行compose2计算,将结果再传入compose1计算
System.out.println(compose1.compose(compose2).apply(2));
}输出结果:
6
5静态方法
identity():获取到一个输入参数和返回结果一样的Function实例。
来一个自驾九寨沟的代码示例:
public class FunctionTest {
@Test
public void findByFunctionTest() {
Function<BigDecimal, BigDecimal> getMoney = m -> m.add(new BigDecimal(1000));
BigDecimal totalCost = getMoney.apply(new BigDecimal(500));
System.out.println("张三的钱包原本只有500元,自驾川西得去银行再取1000元,取钱后张三钱包总共有" + Function.identity().apply(totalCost) + "元");
BigDecimal surplus = cost(totalCost, (m) -> {
System.out.println("第二天出发前发现油不足,加油前有" + m + "元");
BigDecimal lubricate = m.subtract(new BigDecimal(300));
System.out.println("加油300后还剩余" + lubricate + "元");
return lubricate;
}, (m) -> {
System.out.println("到达景区门口,买景区票前有" + m + "元");
BigDecimal tickets = m.subtract(new BigDecimal(290));
System.out.println("买景区票290后还剩余" + tickets + "元");
return tickets;
});
System.out.println("最后张三返程到家还剩余" + surplus + "元");
}
public BigDecimal cost(BigDecimal money, Function<BigDecimal, BigDecimal> lubricateCost,
Function<BigDecimal, BigDecimal> ticketsCost) {
Function<BigDecimal, BigDecimal> firstNight = (m) -> {
System.out.println("第一晚在成都住宿前有" + m + "元");
BigDecimal first = m.subtract(new BigDecimal(200));
System.out.println("交完200住宿费还剩余" + first + "元");
return first;
};
Function<BigDecimal, BigDecimal> secondNight = (m) -> {
System.out.println("第二晚在九寨县住宿前有" + m + "元");
BigDecimal second = m.subtract(new BigDecimal(200));
System.out.println("交完200住宿费还剩余" + second + "元");
return second;
};
return lubricateCost.andThen(ticketsCost).andThen(secondNight).compose(firstNight).apply(money);
}
}
输出结果:
张三的钱包原本只有500元,自驾川西得去银行再取1000元,取钱后张三钱包总共有1500元
第一晚在成都住宿前有1500元
交完200住宿费还剩余1300元
第二天出发前发现油不足,加油前有1300元
加油300后还剩余1000元
到达景区门口,买景区票前有1000元
买景区票290后还剩余710元
第二晚在九寨县住宿前有710元
交完200住宿费还剩余510元
最后张三返程到家还剩余510元
其他的函数型函数式接口汇总说明:
| 接口名称 | 方法名称 | 方法签名 |
|---|---|---|
| BiFunction | apply | (T, U) -> R |
| DoubleFunction | apply | (double) -> R |
| DoubleToIntFunction | applyAsInt | (double) -> int |
| DoubleToLongFunction | applyAsLong | (double) -> long |
| IntFunction | apply | (int) -> R |
| IntToDoubleFunction | applyAsDouble | (int) -> double |
| IntToLongFunction | applyAsLong | (int) -> long |
| LongFunction | apply | (long) -> R |
| LongToDoubleFunction | applyAsDouble | (long) -> double |
| LongToIntFunction | applyAsInt | (long) -> int |
| ToDoubleFunction | applyAsDouble | (T) -> double |
| ToDoubleBiFunction | applyAsDouble | (T, U) -> double |
| ToIntFunction | applyAsInt | (T) -> int |
| ToIntBiFunction | applyAsInt | (T, U) -> int |
| ToLongFunction | applyAsLong | (T) -> long |
| ToLongBiFunction | applyAsLong | (T, U) -> long |
断言型接口
Predicate<T>
java.util.function.Predicate<T> 接口中包含一个抽象方法: boolean test(T t) ,用于条件判断的场景。默认方法:and or nagte (取反)。
接口源码:
package java.util.function;
import java.util.Objects;
@FunctionalInterface
public interface Predicate<T> {
boolean test(T t);
default Predicate<T> and(Predicate<? super T> other) {
Objects.requireNonNull(other);
return (t) -> test(t) && other.test(t);
}
default Predicate<T> negate() {
return (t) -> !test(t);
}
default Predicate<T> or(Predicate<? super T> other) {
Objects.requireNonNull(other);
return (t) -> test(t) || other.test(t);
}
static <T> Predicate<T> isEqual(Object targetRef) {
return (null == targetRef)
? Objects::isNull
: object -> targetRef.equals(object);
}
}
既然是条件判断,就会存在与、或、非三种常见的逻辑关系。其中将两个 Predicate 条件使用与逻辑连接起来实现并且的效果时,类始于 Consumer接口 andThen()函数 其他三个雷同。
public class PredicateTest {
/**
* 查找在渝北的Jack
*/
@Test
public void findByPredicateTest() {
List<User> list = Lists.newArrayList(new User("Johnson", "渝北"), new User("Tom", "渝中"), new User("Jack", "渝北"));
getNameAndAddress(list, (x) -> x.getAddress().equals("渝北"), (x) -> x.getName().equals("Jack"));
}
public void getNameAndAddress(List<User> users, Predicate<User> name, Predicate<User> address) {
users.stream().filter(user -> name.and(address).test(user)).forEach(user -> System.out.println(user.toString()));
}
}
输出结果:
User [name=Jack, address=渝北]
其他的断言型函数式接口汇总说明:
| 接口名称 | 方法名称 | 方法签名 |
|---|---|---|
| BiPredicate | test | (T, U) -> boolean |
| DoublePredicate | test | (double) -> boolean |
| IntPredicate | test | (int) -> boolean |
| LongPredicate | test | (long) -> boolean |
四、总结
Lambda 表达式和方法引用并没有将 Java 转换成函数式语言,而是提供了对函数式编程的支持。这对 Java 来说是一个巨大的改进,因为这允许你编写更简洁明了,易于理解的代码。
Java8新特性探索之函数式接口的更多相关文章
- java8新特性学习:函数式接口
本文概要 什么是函数式接口? 如何定义函数式接口? 常用的函数式接口 函数式接口语法注意事项 总结 1. 什么是函数式接口? 函数式接口其实本质上还是一个接口,但是它是一种特殊的接口:SAM类型的接口 ...
- Java8新特性探索之Stream接口
一.为什么引入Stream流 流是一系列与特定存储机制无关的元素--实际上,流并没有"存储"之说.使用流,无需迭代集合中的元素,就可以从管道提取和操作元素.这些管道通常被组合在一起 ...
- java8新特性 - 什么是函数式接口 @FunctionalInterface?
什么是函数式接口 @FunctionalInterface 源码定义 /** * An informative annotation type used to indicate that an int ...
- Java JDK1.8新特性之四大函数式接口
JDK 1.8的一些新特性 四大核心函数式接口(Consumer.Predicate.Supplier.Function),结合lambda表达式 import java.util.ArrayList ...
- 乐字节-Java8核心特性实战之函数式接口
什么时候可以使用Lambda?通常Lambda表达式是用在函数式接口上使用的.从Java8开始引入了函数式接口,其说明比较简单:函数式接口(Functional Interface)就是一个有且仅有一 ...
- Java8新特性第2章(接口默认方法)
在Java中一个接口一旦发布就已经被定型,除非我们能够一次性的更新所有该接口的实现,否者在接口的添加新方法将会破坏现有接口的实现.默认方法就是为了解决这一问题的,这样接口在发布之后依然能够继续演化. ...
- 01 语言基础+高级:1-10 JDK8新特性_day12【函数式接口】
day12[函数式接口] 主要内容自定义函数式接口函数式编程常用函数式接口 教学目标能够使用@FunctionalInterface注解能够自定义无参无返回函数式接口能够自定义有参有返回函数式接口能够 ...
- JAVA 8 主要新特性 ----------------(四)Lambda函数式接口
一.什么是函数式接口 只包含一个抽象方法的接口,称为函数式接口. 你可以通过 Lambda 表达式来创建该接口的对象.(若 Lambda 表达式抛出一个受检异常,那么该异常需要在目标接口的抽象方法 ...
- Java8新特性探索之Lambda表达式
为什么引入Lambda表达式? Lambda 表达式产生函数,而不是类. 在 JVM(Java Virtual Machine,Java 虚拟机)上,一切都是一个类,因此在幕后执行各种操作使 lamb ...
随机推荐
- ios自动识别电话并变色的问题解决方法
问题: 在做移动端页面时发现长串数字都被ios系统的手机识别为电话号码,且文字变成很土的蓝色,点击有下划线并弹出提示拨打该电话号码. 解决方法: 1.在head中加上下面这行代码就OK了(仅限于单页面 ...
- java获取一天前的时间
获取一天前的时间 Date date = new Date(); Calendar calendar = Calendar.getInstance(); calendar.setTime(date); ...
- mybatis动态条件组合分页查询
一.动态条件处理 需要使用mybatis的动态sql 1 <select id="selectItemByCondition" parameterType="com ...
- Docker系列——利用gogs搭建属于自己的git服务
gogs简介 Gogs的目标是打造一个最简单.最快速和最轻松的方式搭建自助Git服务.使用Go语言开发使得Gogs能够通过独立的二进制分发,并且支持Go语言支持的所有平台,包括 Linux.Mac O ...
- 电机AB相编码器测速
控制任务 检测编码器的脉冲并测速 电路设计 图1 直流电机带减速器和编码器 图2 编码器接线定义 编码器接线定义如下 M1:电机电源接口,绿色的 GND:编码器电源负极输入口,橙色的 C1:编码器A ...
- springcloud学习入门
Springcloud入门学习笔记 1. 项目初始化配置 1. 1. 新建maven工程 使用idea创建maven项目 1. 2. 在parent项目pom中导入以下依赖 <parent> ...
- php数据显示在html截取字符
<?php echo mb_subsrt($s['content'],0,20,'gbkj')?> gbk也可以是utf8根据实际情况来
- 第四届58topcoder编程大赛--地图路径规划
layout: post title: 第四届58topcoder编程大赛 subtitle: 58ACM catalog: true tags: - A* 算法 - C++ - 程序设计 问题及背景 ...
- 第一个月多测师讲解__项目讲解以及注意事项(肖sir)
一.目的讲解流程:(讲述业务时长10-15分钟为宜)1.自我介绍礼貌用语,姓名,籍贯,学校,个人技能,经验,表现,兴趣爱好等 ,1分钟 ,谢谢2.介绍项目的名字 ,项目的背景,(涉及什么架构)3.对项 ...
- Windows 上的苹果 mac Time Machine 时间机器免费替代品 FreeFileSync 操作指南
Windows 上的苹果 mac Time Machine 时间机器免费替代品 FreeFileSync 操作指南 前言:为什么不用 Windows 10 自带的备份还原呢?因为不稳定,不能很好的备份 ...