JDK8新特性之Lambda表达式(一)
函数式接口
在了解Lambda表达式之前我们必须了解一下函数式接口,Lambda表达式实际上是依赖于函数式接口的。
定义
如果一个接口中,只声明了一个抽象方法,那么这个接口就成为函数式接口。
我们一般使用@FunctionalInterface来标注,这个注解的作用是检验一个接口是否是函数式接口,这个注解是非必须的,换句话说如果一个不加这个注解但是只有一个抽象方法那么这个接口仍然是函数式接口。但是但最好在接口上使用注解@FunctionalInterface进行声明,以免团队的其他人员错误地往接口中添加新的方法。
需要注意的是,在JDK8后接口中允许有方法实现,像默认方法、静态方法这些都不属于抽象方法。还有就是重写 Object 类里的方法不会导致函数式接口失效,如果一个接口声明了抽象方法,但该抽象方法重写了 Object 类里的一个公有方法,那么对于 Java 编译器来说,它并不会认为该方法符合函数式接口的抽象方法(即不把该方法当作函数式接口的抽象方法)。因为接口的实现类都会直接或间接继承 Object 这个根类,所以在函数式接口中定义与 Object 类中签名一样的方法是不会导致函数式接口失效的。
代码示例
下面通过几个例子来说明:
只有一个抽象方法的函数式接口:
/**
* @Author winston
* @Date 2021/9/11
*/
// 声明函数式接口的注解
@FunctionalInterface
public interface FunctionInterfaceExample {
/**
* 抽象方法
*/
public void demo01();
/**
* 接口中的静态方法,不属于抽象方法
*/
public static void staticMethod(){
System.out.println("静态方法");
}
/**
* 接口中的默认方法,不属于抽象方法
*/
default void defaultMethod(){
System.out.println("默认方法");
}
}
当我们在上面定义的函数式接口中再增加一个抽象方法时,如果加了@FunctionalInterface注解的话,实际上编译是会报错的,因为这就不属于函数式接口了,下面这个代码就会编译报错,大家可以试一下
/**
* @Author winston
* @Date 2021/9/11
*/
// 声明函数式接口的注解
@FunctionalInterface
public interface FunctionInterfaceExample {
/**
* 抽象方法
*/
public void demo01();
/**
* 抽象方法2
*/
public void demo02();
/**
* 接口中的静态方法
*/
public static void staticMethod(){
System.out.println("静态方法");
}
/**
* 接口中的默认方法
*/
default void defaultMethod(){
System.out.println("默认方法");
}
}
重写 Object 类里的方法不会导致函数式接口失效,下面这个代码表明上看上去是一个抽象方法,实际上它是重写了Object类里面的方法,因此是不会报错的
/**
* @Author winston
* @Date 2021/9/11
*/
// 声明函数式接口的注解
@FunctionalInterface
public interface FunctionInterfaceExample {
/**
* 抽象方法
*/
public void demo01();
/**
* 重写object中的方法
*/
public String toString();
/**
* 接口中的静态方法
*/
public static void staticMethod(){
System.out.println("静态方法");
}
/**
* 接口中的默认方法
*/
default void defaultMethod(){
System.out.println("默认方法");
}
}
Lambda表达式
Lambda表达式介绍
首先我们先了解一下什么是Lambda表达式,在JDK8中引入的一种新的语法元素和操作符,这个符号为->,该操作符被称为Lambda操作符或者叫箭头操作符,它将Lambd分为两个部分:
左侧:指定了Lambda表达式需要的参数列表
右侧:指定了Lambda体,是抽象方法的实现逻辑,也就是Lambda表达式要执行的功能
Lambda表达式的本质是作为函数式接口的实现
Lambda表达式语法
语法格式一:无参、无返回值
假设我们接口中有个抽象方法print
@FunctionalInterface
public interface LambdaDemo1 {
public void print();
}
传统使用匿名函数实现的方式:
public static void main(String[] args) {
LambdaDemo1 lambdaDemo1 = new LambdaDemo1() {
@Override
public void print() {
System.out.println("我是LambdaDemo1的print方法");
}
};
lambdaDemo1.print();
}
使用Lambda表达式的方式:
public static void main(String[] args) {
LambdaDemo1 lambdaDemo1 = ()->{
System.out.println("我是LambdaDemo1的print方法");
};
lambdaDemo1.print();
}
public static void main(String[] args) {
// 方法体只有一行代码时可以省略{}
LambdaDemo1 lambdaDemo1 = () -> System.out.println("我是LambdaDemo1的print方法");
lambdaDemo1.print();
}
语法格式二:有参、无返回值
@FunctionalInterface
public interface LambdaInterface1 {
public void print(String str);
}
传统使用匿名函数实现的方式:
public static void main(String[] args) {
LambdaInterface1 lambdaInterface1 = new LambdaInterface1() {
@Override
public void print(String str) {
System.out.println(str);
}
};
lambdaInterface1.print("哈哈哈");
}
使用Lambda表达式的方式:
public static void main(String[] args) {
LambdaInterface1 lambdaInterface1 = (String str) -> {
System.out.println(str);
};
lambdaInterface1.print("哈哈哈");
}
在上面Lambda表达式的基础上我们还可以将参数数据类型进行省略,因为参数类型可由编译器推断出来(因为LambdaInterface1是函数式接口有且只有一个抽象方法,因此实现的时候只能实现该方法,所以对应的参数也必然是该接口的print方法参数,所以可以省略参数类型)
public static void main(String[] args) {
// 省略方法参数类型
LambdaInterface1 lambdaInterface1 = (str) -> {System.out.println(str)};
lambdaInterface1.print("哈哈哈");
}
当方法参数只有一个时()可以省略,当方法体代码只有一句时{}可以省略,我们在上面例子的基础上进行改造
public static void main(String[] args) {
// 省略方法参数中的()和方法体的{}
LambdaInterface1 lambdaInterface1 = str -> System.out.println(str);
lambdaInterface1.print("哈哈哈");
}
语法格式三:Lambda需要两个或者两个以上的参数,多条执行语句,并且可能有返回值
我们以JDK自带的Comparator接口为例,传统写法:
public static void main(String[] args) {
Comparator<Integer> comparator = new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
System.out.println(o1);
System.out.println(o2);
return o1.compareTo(o2);
}
};
int result = comparator.compare(2, 3);
}
使用lambda表达式的写法:
public static void main(String[] args) {
// 不是一个参数的时候()不能省略,由于类型推断参数类型可以省略,方法体中只有一行代码时{}可以省略
Comparator<Integer> comparator = (o1, o2) -> {
System.out.println(o1);
System.out.println(o2);
return o1.compareTo(o2);
};
int result = comparator.compare(2, 3);
}
Lambda表达式使用小结
在使用Lambda的接口必须是函数式接口
在->左侧:Lambda形参列表的参数类型可以省略,如果Lambda形参列表只有一个参数,其()也可以省略
在->右侧:Lambda体应该使用一对{}包裹,如果Lambda体里面只有一句执行语句(可能是return语句)那么{}可以省略
JAVA内置四大核心函数式接口
在JDK8中为我们提供了几种类型的函数式接口,它们分别是消费型接口Consumer<T>
,供给型接口Supplier<T>
,函数型接口Function<T>
,断定型接口Predicate<T>
,下面分别介绍这几种接口的基本使用。
消费型接口Consumer
参数类型T,返回类型void,对类型T的对象应用操作,包含方法void accept(T t)
,简单来说就是接收一个T,然后将它进行消费。
Consumer接口
@FunctionalInterface
public interface Consumer<T> {
// 消费方法
void accept(T t);
// 这个是用来需要两个Consumer接口,可以把两个Consumer接口组合到一起,在对数据进行消费
default Consumer<T> andThen(Consumer<? super T> after) {
Objects.requireNonNull(after);
// 这一步就是相当于对accept方法的具体实现,返回一个consumer
return (T t) -> { accept(t); after.accept(t); };
}
}
使用accept方法
public class LambdaDemoTest {
public static void main(String[] args) {
Consumer<String> consumer = str -> System.out.println("打印信息:" + str);
consumer.accept("哈哈哈");
}
}
使用andThen方法组合消费
public static void main(String[] args) {
Consumer<String> consumer1 = str -> System.out.println("consumer1打印信息:" + str);
Consumer<String> consumer2 = str -> System.out.println("consumer2打印信息:" + str);
consumer1.andThen(consumer2).accept("哈哈哈");
}
效果:
consumer1打印信息:哈哈哈
consumer2打印信息:哈哈哈
供给型接口Supplier
无参,返回值类型T,简单来说就是会提供一个特定对象,Supplier接口包含方法T get()
Supplier接口
@FunctionalInterface
public interface Supplier<T> {
/**
* Gets a result.
*
* @return a result
*/
T get();
}
通过使用Lambda表达式来使用get方法
public static void main(String[] args) {
// 想要一个默认值是0的Double对象
Supplier<Double> supplier = ()-> new Double("0");
Double zeroDouble = supplier.get();
}
函数型接口Function
入参T,返回值R,简单来说就是将入参T转成结果R
Function接口(这里主要是R apply(T t)
方法,其他方法先省略)
@FunctionalInterface
public interface Function<T, R> {
...
R apply(T t);
....
}
通过Lambda表达式使用apply方法
public static void main(String[] args) {
// 想要将传入的Integer类型转成String类型
Function<Integer,String> function = (paramInteger) -> String.valueOf(paramInteger);
String strResult = function.apply(12);
}
断定型接口Predicate
入参T,返回值是boolean,简单来说就是根据入参做判断处理
Predicate接口(这里主要是boolean test(T t)
方法,其他方法先省略)
@FunctionalInterface
public interface Predicate<T> {
...
boolean test(T t);
....
}
这里我们举个例子,给定一个存有数字的List,我们要过滤掉小于10的数字
不使用lambda表达式的方式使用test方法进行过滤:
public static void main(String[] args) {
List<Integer> numList = new ArrayList<>();
numList.add(3);
numList.add(11);
numList.add(10);
numList.add(5);
numList.add(18);
numList.add(7);
numList.add(22);
/**
* 自定义方法,用来过滤获取想要的结果集
* 这里的第一个参数是要进行过滤的List
* 第二个参数是写过滤规则的Predicate接口,使用了匿名内部类的实现方式实现test方法
*/
filterNum(numList, new Predicate<Integer>() {
@Override
public boolean test(Integer num) {
if(10 < num){
return true;
}
return false;
}
});
}
/**
*
* @param numList 要过滤的Integer集合
* @param integerPredicate 过滤条件
* @return
*/
private static List<Integer> filterNum(List<Integer> numList, Predicate<Integer> integerPredicate) {
List<Integer> result = new ArrayList<>();
for (Integer num : numList) {
// 如果满足条件的就放到result集合中
boolean flag = integerPredicate.test(num);
if (flag) {
result.add(num);
}
}
return result;
}
使用lambda表达式的方式使用test方法进行过滤:
public static void main(String[] args) {
List<Integer> numList = new ArrayList<>();
numList.add(3);
numList.add(11);
numList.add(10);
numList.add(5);
numList.add(18);
numList.add(7);
numList.add(22);
/**
* 自定义方法,用来过滤获取想要的结果集
* 这里的第一个参数是要进行过滤的List
* 第二个参数是写过滤规则的Predicate接口,这里使用的是lambda表达式的方式
*/
filterNum(numList,num -> {
if(10 < num){
return true;
}
return false;
});
}
/**
*
* @param numList 要过滤的Integer集合
* @param integerPredicate 过滤条件
* @return
*/
private static List<Integer> filterNum(List<Integer> numList, Predicate<Integer> integerPredicate) {
List<Integer> result = new ArrayList<>();
for (Integer num : numList) {
// 如果满足条件的就放到result集合中
boolean flag = integerPredicate.test(num);
if (flag) {
result.add(num);
}
}
return result;
}
小结
刚开始接触Lambda表达式的话可能会觉得不太好理解,实际上Lambda表达式就是函数式接口的实现,通过上面的一些例子我们可以看出我们能够通过使用传统的匿名内部类来实现这些函数式接口,Lambda表达式通过一系列的简化操作减少了代码的数量,看起来简洁了很多但是对于他人的理解难度却是增加了不少,大家还是要慢慢学习Lambda表达式。其次就是JAVA内置四大核心函数式接口,当我们需要用到上面所说的功能时可以考虑使用而不是自己新建一个接口。