本文写的是java8新特性中的函数式接口、lambda
表达式,介绍一下它们的 是什么 和 怎么用。
若想了解java8其它新特性,请到 00-java8常用新特性文章索引 阅读。
1 函数式接口
1.1 是什么
只包含一个抽象方法的接口,不是规定接口中只能有一个方法,因为接口中还可以包含 default方法和 static 方法。
- 在接口上声明
@FunctionalInterface
注解,可以检查该接口是否是一个函数式接口
,同时 javadoc 也会包含一条声明,说明这个接口是一个函数式接口
。 - 使用
lambda
表达式可以创建一个函数式接口
的实例(下面介绍)。
1.2 Java内置的四大核心函数式接口
JDK中提供了一些默认的函数式接口,能够满足大部分业务场景,当然,也可以通过自定义的函数式接口处理业务。
1.2.1 Consumer<T>
消费型接口:对给定的 T 类型的参数,进行操作,
核心方法:void accept(T t);
源码
@FunctionalInterface
public interface Consumer<T> {
/**
* 对给定的 T 类型的参数,进行操作
*
* @param t the input argument
*/
void accept(T t);
/**
* 这个是接口中定义的 default 方法,也是 java8 的新特性
* 后面章节会介绍接口中的 default 方法,这里可以把看作是普通的成员方法
*/
default Consumer<T> andThen(Consumer<? super T> after) {
Objects.requireNonNull(after);
return (T t) -> { accept(t); after.accept(t); };
}
}
1.2.2 Supplier<T>
供给型接口: 获取一个 T 类型的对象
核心方法:T get();
源码
@FunctionalInterface
public interface Supplier<T> {
/**
* 返回一个 T 类型的对象
*
* @return a result
*/
T get();
}
1.2.3 Function<T, R>
函数型接口: 对 T 类型的参数进行操作,返回 R 类型的结果
核心方法:R apply(T t);
源码
@FunctionalInterface
public interface Function<T, R> {
/**
* 对 T 类型的参数进行操作,返回 R 类型的结果
*
* @param t the function argument
* @return the function result
*/
R apply(T t);
/**
* default 方法,也是 java8 的新特性
* 后面章节会介绍接口中的 default 方法,这里可以把看作是普通的成员方法
*/
default <V> Function<V, R> compose(Function<? super V, ? extends T> before) {
Objects.requireNonNull(before);
return (V v) -> apply(before.apply(v));
}
/**
* default 方法,也是 java8 的新特性
* 后面章节会介绍接口中的 default 方法,这里可以把看作是普通的成员方法
*/
default <V> Function<T, V> andThen(Function<? super R, ? extends V> after) {
Objects.requireNonNull(after);
return (T t) -> after.apply(apply(t));
}
/**
* static 方法,也是 java8 的新特性
* 后面章节会介绍接口中的 static 方法,这里可以把看作是普通的类方法
*/
static <T> Function<T, T> identity() {
return t -> t;
}
}
1.2.4 Predicate<T>
断定型接口: 判断 T 类型的参数,是否满足某约束,返回 boolean 值
核心方法:boolean test(T t);
源码
@FunctionalInterface
public interface Predicate<T> {
/**
* 判断 T 类型的参数,是否满足某约束,返回 boolean 值
*
* @param t the input argument
* @return {@code true} if the input argument matches the predicate,
* otherwise {@code false}
*/
boolean test(T t);
/**
* default 方法,也是 java8 的新特性
* 后面章节会介绍接口中的 default 方法,这里可以把看作是普通的成员方法
*/
default Predicate<T> and(Predicate<? super T> other) {
Objects.requireNonNull(other);
return (t) -> test(t) && other.test(t);
}
/**
* default 方法,也是 java8 的新特性
* 后面章节会介绍接口中的 default 方法,这里可以把看作是普通的成员方法
*/
default Predicate<T> negate() {
return (t) -> !test(t);
}
/**
* default 方法,也是 java8 的新特性
* 后面章节会介绍接口中的 default 方法,这里可以把看作是普通的成员方法
*/
default Predicate<T> or(Predicate<? super T> other) {
Objects.requireNonNull(other);
return (t) -> test(t) || other.test(t);
}
/**
* static 方法,也是 java8 的新特性
* 后面章节会介绍接口中的 static 方法,这里可以把看作是普通的类方法
*/
static <T> Predicate<T> isEqual(Object targetRef) {
return (null == targetRef)
? Objects::isNull
: object -> targetRef.equals(object);
}
}
1.3 四大核心函数式接口简单使用
四大核心函数式接口简单使用
/**
* Description: 函数式接口简单使用
*
* @author Xander
* datetime: 2020/8/31 9:25
*/
public class FuncIntfTest {
/**
* 要生成的字符串长度
*/
public static final int LENGTH = 2;
/**
* 十六进制字符数组
*/
public static char[] hexChars = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'};
public static void main(String[] args) {
int setLength = 5;// set 的 length
Set<String> set = new LinkedHashSet<>();
Supplier<String> supplier = getRandomHexStrSupplier();// 获取一个 Supplier:随机十六进制字符串生成器
for (int i = 0; i < setLength; i++) {
set.add(supplier.get());//生成随机 16 进制字符串
}
Consumer<Set> setConsumer = getSetConsumer();//获取一个Consumer: 遍历打印 Set
System.out.println("----------打印所有的16进制字符串---------");
setConsumer.accept(set);//遍历打印 set 中的每个元素
//将字符串转为 long 值
Function<String, Long> toDecimalFunction = new Function<String, Long>() {
@Override
public Long apply(String s) {
Long value = Long.valueOf(s, 16);//将16进制字符串转为10进制的long值
return value;
}
};
Predicate<String> lt150Predicate = new Predicate<String>() {
/**
* 判断一个十六进制字符串是否小于150
*
* @param s
* @return
*/
@Override
public boolean test(String s) {
Long value = Long.valueOf(s, 16);
return value.longValue() < 150;
}
};
Set<Long> longSet = new LinkedHashSet<>();
for (String str : set) {
if (lt150Predicate.test(str)) {//判断一个十六进制字符串是否小于150
Long value = toDecimalFunction.apply(str);//将16进制字符串转为10进制的long值
longSet.add(value);
}
}
System.out.println("----------打印小于150的值---------");
setConsumer.accept(longSet);//遍历打印 longSet 中的每个元素
}
/**
* 获取一个Supplier:随机十六进制字符串生成器
*
* @return
*/
private static Supplier<String> getRandomHexStrSupplier() {
Supplier<String> supplier = new Supplier<String>() {
/**
* 生成随机 16 进制字符串
*
* @return
*/
@Override
public String get() {
Random random = new Random();
StringBuilder sb = new StringBuilder();
for (int i = 0; i < LENGTH; i++) {
sb.append(hexChars[random.nextInt(hexChars.length)]);
}
return sb.toString();
}
};
return supplier;
}
/**
* 获取一个Consumer: 遍历打印 Set
*
* @return
*/
private static Consumer<Set> getSetConsumer() {
Consumer<Set> consumer = new Consumer<Set>() {
/**
* 遍历打印 set 中的每个元素
*
* @param set
*/
@Override
public void accept(Set set) {
if (set == null || set.isEmpty())
return;
for (Object s : set) {
System.out.println(s);
}
}
};
return consumer;
}
}
运行结果:
----------打印所有的16进制字符串---------
48
B1
0B
46
5F
----------打印小于150的值---------
72
11
70
95
1.4 Java其他内置的函数式接口
函数式接口 | 参数类型 | 返回类型 | 用途 |
---|---|---|---|
BiFunction<T, U, R> | T, U | R | 对类型为 T, U 参数应用操作, 返回 R 类型的结果。 核心方法为 R apply(T t, U u); |
UnaryOperator (Function子接口) |
T | T | 对类型为T的对象进行一元运算, 并返回T类型的结果。 核心方法为 T apply(T t); |
BinaryOperator (BiFunction 子接口) |
T, T | T | 对类型为T的对象进行二元运算, 并返回T类型的结果。 核心方法为 T apply(T t1, T t2); |
BiConsumer<T, U> | T, U | void | 对类型为T, U 参数应用操作。 核心方法为 void accept(T t, U u) |
ToIntFunction ToLongFunction ToDoubleFunction |
T | int/long/double | T 类型的参数, 返回int\long\double 类型的结果, 核心方法为 int\long\double applyAsInt(T value); |
IntFunction LongFunction DoubleFunction |
int/long/double | R | 参数分别为int、long、double 类型的函数, 返回 R 类型的结果, 核心方法: R apply(int\long\double value); |
2、 Lambda 表达式
作用:可以通过
Lambda
表达式来创建函数式接口的对象,这样可以取代大部分的匿名内部类,可优化代码结构,写出更加优雅的代码。
2.1 Lambda 是什么
Java8中引入了一个新的操作符 “
->
” 该操作符称为箭头操作符或 Lambda 操作符
箭头操作符将 Lambda 表达式拆分成两部分:
左侧:参数列表
右侧:业务逻辑代码, 即 Lambda 体
2.2 Lambda 简单使用
可以看出 lambda 表达式的代码量更少,更加简洁。
/**
* 分别通过匿名内部类和 lambda 表达式方式新建 Runnable 对象,创建线程,打印随机整数
*/
@Test
public void test00() {
Random random = new Random();
// 匿名内部类创建一个 Runable 对象
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("匿名内部类:" + random.nextInt(10));
}
});
thread.start();
// lambda 表达式创建一个 Runable(函数式接口) 对象
Runnable rLambda = () -> System.out.println("lambda表达式1:" + random.nextInt(10));
Thread tLambda1 = new Thread(rLambda);
tLambda1.start();
//lambda 可以代替函数式接口的匿名内部类作为参数传递
Thread tLambda2 = new Thread(() -> System.out.println("lambda表达式2:" + random.nextInt(10)));
tLambda2.start();
}
运行结果:
匿名内部类:8
lambda表达式1:2
lambda表达式2:5
2.3 类型推断
Lambda表达式中的类型推断,实际上是Java 7就引入的目标类型推断的扩展,Java7中的菱形操作符,它可使javac推断出泛型参数的类型。
List<String> list1 = new ArrayList<String>();
List<String> list2 = new ArrayList<>();
Lambda 表达式中的参数类型依赖于上下文环境,是由编译器推断出来的。lambda表达式中无需指定类型,亦可编译通过。
// 类型推断,lambda可以推断出参数 str 的类型为String
Consumer<String> c = str -> System.out.println(str);
2.3 Lambda 语法格式
Lambda 语法格式分别从 参数 和 lambda体 进行介绍
2.3.1 参数:无参
参数:无参,参数部分为 ()
/**
* 参数:无参
*/
@Test
public void test01() {
// 参数:无参,参数部分为 ()
Supplier<Integer> supplier = () -> {
int num = new Random().nextInt(10);
System.out.println(num);
return num;
};
System.out.println(supplier.get());
}
运行结果:
4
2.3.2 参数:只有一个参数
参数:只有一个参数,可以省略括号 ()
/**
* 参数:只有一个参数
*/
@Test
public void test02() {
// 参数:只有一个参数,可以省略括号 ()
Consumer<String> c1 = str -> {
System.out.println(str);
};
c1.accept("hello lambda");
}
运行结果:
hello lambda
2.3.3 参数:多个参数
参数:多个参数,所有的参数写在小括号 () 中
/**
* 参数:多个参数
*/
@Test
public void test03() {
//参数:多个参数,所有的参数写在小括号 () 中
BinaryOperator<Integer> binaryOperator = (n1, n2) -> {
return n1 + n2;
};
System.out.println(binaryOperator.apply(2, 3));
}
运行结果:
5
2.3.4 lambda体:一行语句,无返回值
lambda体:一行语句,无返回值,可以省略大括号
/**
* lambda体:一行语句
*/
@Test
public void test04() {
// lambda体:一行语句,返回值,可以省略大括号
Consumer<String> c1 = str -> System.out.println(str);
c1.accept("lambda体:一行语句,返回值,可以省略大括号");
}
运行结果:
lambda体:一行语句,返回值,可以省略大括号
2.3.5 lambda体:一行语句,有返回值
lambda体:一行语句,有返回值,可以省略 return 关键字 和 大括号
/**
* lambda体:一行语句,有返回值
*/
@Test
public void test05() {
// 二元运算:加法
BinaryOperator<Integer> biOpr1 = (n1, n2) -> {
return n1 + n2;
};
// 二元运算:乘法
// lambda体:一行语句,有返回值,可以省略 return 关键字 和 大括号
BinaryOperator<Integer> biOpr2 = (n1, n2) -> n1 * n2;
System.out.println(biOpr1.apply(5, 10));
System.out.println(biOpr2.apply(5, 10));
}
运行结果:
15
50
2.3.6 lambda体:多行语句
lambda体:多行语句,所有代码语句要写在大括号 {} 中
/**
* lambda体:多行语句
*/
@Test
public void test06() {
// 二元运算:加法
// lambda体:多行语句,所有代码语句要写在大括号 {} 中
BinaryOperator<Integer> biOpr1 = (n1, n2) -> {
System.out.println("参数一:" + n1);
System.out.println("参数二:" + n2);
return n1 + n2;
};
System.out.println("加法运算结果:" + biOpr1.apply(5, 10));
}
运行结果:
参数一:5
参数二:10
加法运算结果:15
好了,函数式接口和 lambda 表达式的简单介绍就到这,多加理解,学以致用,才是王道。