重学Java 8新特性 | 第5讲——函数式接口
Posted 李阿昀
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了重学Java 8新特性 | 第5讲——函数式接口相关的知识,希望对你有一定的参考价值。
我们都知道Lambda表达式需要接口的支持,并且接口中的抽象方法还不能多,只能有一个,不然就没法区分它实现的到底是哪个抽象方法了。一般,我们会称该接口为函数式接口,在上一讲中我就已经提到过了,大家还记得吧!这一讲我们就来具体唠唠它。
什么是函数式接口?
Lambda表达式可以很简洁的代替匿名内部类的代码编写,而匿名内部类往往是实现某一接口的一个抽象方法。所以,在使用Lambda表达式时,我们最应该关注的应该是接口的抽象方法,并且这个接口还必须只有一个抽象方法。我们称这种只有一个抽象方法的接口为函数式接口,而Lambda表达式恰好需要一个函数式接口的支持。
知道了函数式接口是什么之后,我们就可以通过Lambda表达式来创建这种接口的对象了,很显然,如果Lambda表达式抛出一个受检异常,那么该异常就需要在目标接口的抽象方法上声明出来。
当然,我们还可以在任意函数式接口上使用@FunctionalInterface
注解,这样做可以检查该接口是否是一个函数式接口,同时javadoc也会包含一条声明,说明这个接口是一个函数式接口。
之前,我们就创建过一个接口,叫MyPredicate,大家还记得吧!它里面就只有一个抽象方法,因此它就是一个函数式接口。既然是一个函数式接口,那么我们就可以使用@FunctionalInterface
注解来修饰了。
package com.meimeixia.java8;
/**
* @author liayun
* @create 2021-12-08 21:31
*/
@FunctionalInterface // 使用该注解修饰之后,说明这个接口必须是一个函数式接口,而且里面只能有一个抽象方法
public interface MyPredicate<T>
public boolean test(T t);
下面我用一个例子来说明一下函数式接口的用法,即简单使用一下Lambda表达式来对一个数进行运算,不管是啥运算都行。
由于我们是想使用Lambda表达式来完成,所以首先我们得创建一个函数式接口,例如MyFun。
package com.meimeixia.java8;
/**
* @author liayun
* @create 2021-12-09 20:56
*/
@FunctionalInterface // 使用该注解修饰之后,说明这个接口必须是一个函数式接口,而且里面只能有一个抽象方法
public interface MyFun
public Integer getValue(Integer num);
然后,编写一个对一个数进行运算的方法。
package com.meimeixia.java8;
import org.junit.Test;
import java.util.*;
import java.util.function.Consumer;
/*
*
* @author liayun
* @create 2021-12-09 1:02
*/
public class TestLambda2
public Integer operation(Integer num, MyFun mf)
return mf.getValue(num);
最后,调用以上operation方法做个测试。
从上可知,为了将Lambda表达式作为参数进行传递,接收Lambda表达式的参数类型必须是与该Lambda表达式兼容的函数式接口的类型。
实战演练
了解了函数式接口之后,接下来我们来做三道练习题,以巩固目前为止所学的知识。
练习题一
调用Collertions.sort()
方法,通过定制排序比较两个Employee(先按年龄比,年龄相同再按姓名比),并使用Lambda表达式作为参数传递。
这道练习题太简单了,唰一下就能做出来,这里我也不买关子了,直接给出代码吧!
package com.meimeixia.exer;
import com.meimeixia.java8.Employee;
import org.junit.Test;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
/**
* @author liayun
* @create 2021-12-09 21:42
*/
public class TestLambda
List<Employee> employees = Arrays.asList(
new Employee(100, "张三", 18, 9999.99),
new Employee(101, "李四", 38, 5555.99),
new Employee(102, "王五", 50, 6666.66),
new Employee(103, "赵六", 16, 3333.33),
new Employee(104, "田七", 8, 7777.77)
);
@Test
public void test1()
Collections.sort(employees, (e1, e2) ->
if (e1.getAge() == e2.getAge())
return e1.getName().compareTo(e2.getName());
else
return Integer.compare(e1.getAge(), e2.getAge());
);
for (Employee emp : employees)
System.out.println(emp);
其中,Employee实体类的代码如下所示。
package com.meimeixia.java8;
/**
* @author liayun
* @create 2021-12-08 20:52
*/
public class Employee
private int id;
private String name;
private int age;
private double salary;
public Employee()
public Employee(int id, String name, int age, double salary)
this.id = id;
this.name = name;
this.age = age;
this.salary = salary;
public int getId()
return id;
public void setId(int id)
this.id = id;
public String getName()
return name;
public void setName(String name)
this.name = name;
public int getAge()
return age;
public void setAge(int age)
this.age = age;
public double getSalary()
return salary;
public void setSalary(double salary)
this.salary = salary;
@Override
public String toString()
return "Employee" +
"id=" + id +
", name='" + name + '\\'' +
", age=" + age +
", salary=" + salary +
'';
此时,运行TestLambda类中的test1测试方法,你便可以在控制台中看到如下打印信息了。
是不已经按年龄从小到大进行排序了啊!
如果我们想按年龄逆序排序,那么又该怎么办呢?很简单,将test1测试方法修改成下面这个样子就行了。
@Test
public void test1()
Collections.sort(employees, (e1, e2) ->
if (e1.getAge() == e2.getAge())
return e1.getName().compareTo(e2.getName());
else
// return Integer.compare(e1.getAge(), e2.getAge());
// 如果我们想按年龄逆序排序,那么又该怎么办呢?想按年龄逆序排序,加一个-号就行了。
return -Integer.compare(e1.getAge(), e2.getAge());
);
for (Employee emp : employees)
System.out.println(emp);
再次运行以上test1测试方法,这时,你便可以在控制台中看到如下打印信息了。
练习题二
练习二是这样的:
- 声明一个函数式接口,并在接口中声明一个抽象方法,即
public String getValue(String str);
。 - 声明一个TestLambda类,在类中编写一个方法,该方法使用接口作为参数,然后将一个字符串转换成大写,并作为方法的返回值返回。
- 再将一个字符串的第2个和第4个索引位置进行截取子串。
要想完成该练习题,首先我们得创建一个如下所示的函数式接口。
package com.meimeixia.exer;
/**
* @author liayun
* @create 2021-12-10 17:02
*/
@FunctionalInterface
public interface MyFunction
public String getValue(String str);
然后,在TestLambda类中编写一个用于处理字符串的方法。
// 需求:用于处理字符串
public String strHandler(String str, MyFunction mf)
return mf.getValue(str);
最后,对以上方法进行测试。
@Test
public void test2()
String trimStr = strHandler("\\t\\t\\t 我李阿昀牛逼牛逼 ", (str) -> str.trim());
System.out.println(trimStr);
String upper = strHandler("abcdef", (str) -> str.toUpperCase());
System.out.println(upper);
String newStr = strHandler("我李阿昀牛逼牛逼", (str) -> str.substring(1, 4));
System.out.println(newStr);
练习题三
最后,再看一道练习题。
- 声明一个带两个泛型的函数式接口,泛型类型为
<T,R>
,其中T为参数,R为返回值。 - 接口中声明对应抽象方法。
- 在TestLambda类中声明一个方法,使用接口作为参数,计算两个Long型参数的和。
- 再计算两个Long型参数的乘积。
要想完成该练习题,首先我们得创建一个如下所示的函数式接口。
package com.meimeixia.exer;
/**
* @author liayun
* @create 2021-12-10 17:46
*/
@FunctionalInterface
public interface MyFunction2<T, R>
public R getValue(T t1, T t2);
然后,在TestLambda类中编写一个对两个Long型数据进行处理的方法。
// 需求:对两个Long型数据进行处理
public void op(Long l1, Long l2, MyFunction2<Long, Long> mf)
System.out.println(mf.getValue(l1, l2));
最后,对以上方法进行测试。
@Test
public void test3()
op(100L, 200L, (x, y) -> x + y);
op(100L, 200L, (x, y) -> x * y);
经过以上三道练习题的磨练,你有没有什么感想呢?
现在你应该体会到了一个非常不好的地方,那就是使用Lambda表达式往往离不开函数式接口的支持,但是如果每次使用Lambda表达式都要自定义一个函数式接口的话,那么Lambda表达式就没有完全起到简化代码的作用了,相反还变得更麻烦了。
实际上没有关系啊,人家Java 8已经给我们内置好了通常使用到的函数式接口,这些接口是不需要我们自己来建的,因为人家Java 8已经给我们提供好了。至于内置了哪些函数式接口,下面我会详细给大家讲解。
Java 8中内置的四大核心函数式接口
Java 8中内置了一些函数式接口来供开发者们调用,这样就不需要开发者们重复定义函数式接口了。Java 8中提供了四大核心函数式接口,它们分别如下。
下面我会一一介绍以上Java 8中提供的四大核心函数式接口。
Consumer<T>
:消费型接口
Java 8中消费型接口的定义如下所示。
何谓消费呢?消费就是有去无回。通过观察以下例子,你就应该知道该函数式接口该如何使用了。
package com.meimeixia.java8;
import org.junit.Test;
import java.util.function.Consumer;
/**
*
* @author liayun
* @create 2021-12-10 19:54
*/
public class TestLambda3
// Consumer<T>:消费型接口,消费就是有去无回
@Test
public void test1()
happy(10000, (m) -> System.out.println("咱们东哥喜欢大学生,每次消费:" + m + "元"));
public void happy(double money, Consumer<Double> con)
con.accept(money);
Supplier<T>
:供给型接口
Java 8中供给型接口的定义如下所示。
供给型接口主要就是给我们产生一些对象,并返回给我们。例如,产生指定个数的整数,并放入集合中。
package com.meimeixia.java8;
import org.junit.Test;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Consumer;
import java.util.function.Supplier;
/**
*
* @author liayun
* @create 2021-12-10 19:54
*/
public class TestLambda3
// Supplier<T>:供给型接口,就是给你产生一些对象,并返回给你
@Test
public void test2()
List<Integer> numList = getNumList(10, () -> (int) (Math.random() * 100)); // 随机产生10个整数放入到集合中
for (Integer num : numList)
System.out.println(num);
// 需求:产生指定个数的整数,并放入集合中
public List<Integer> getNumList(int num, Supplier<Integer> sup)
List<Integer> list = new ArrayList<>();
for (int i = 0; i < num; i++)
Integer n = sup.get();
list.add(n);
return list;
// Consumer<T>:消费型接口,消费就是有去无回
@Test
public void test1()
happy(10000, (m) -> System.out.println("咱们东哥喜欢大学生,每次消费:" + m + "元"));
public void happy(double money, Consumer<Double> con)
con.accept(money);
以上是关于重学Java 8新特性 | 第5讲——函数式接口的主要内容,如果未能解决你的问题,请参考以下文章
重学Java 8新特性 | 第2讲——Java 8新特性简介
重学Java 8新特性 | 第2讲——Java 8新特性简介
重学Java 8新特性 | 第2讲——Java 8新特性简介