我们都知道Lambda表达式需要接口的支持,并且接口中的抽象方法还不能多,只能有一个,不然就没法区分它实现的到底是哪个抽象方法了。一般,我们会称该接口为函数式接口,在上一讲中我就已经提到过了,大家还记得吧!这一讲我们就来具体唠唠它。
什么是函数式接口?Lambda表达式可以很简洁的代替匿名内部类的代码编写,而匿名内部类往往是实现某一接口的一个抽象方法。所以,在使用Lambda表达式时,我们最应该关注的应该是接口的抽象方法,并且这个接口还必须只有一个抽象方法。我们称这种只有一个抽象方法的接口为函数式接口,而Lambda表达式恰好需要一个函数式接口的支持。
知道了函数式接口是什么之后,我们就可以通过Lambda表达式来创建这种接口的对象了,很显然,如果Lambda表达式抛出一个受检异常,那么该异常就需要在目标接口的抽象方法上声明出来。
当然,我们还可以在任意函数式接口上使用@FunctionalInterface注解,这样做可以检查该接口是否是一个函数式接口,同时javadoc也会包含一条声明,说明这个接口是一个函数式接口。
之前,我们就创建过一个接口,叫MyPredicate,大家还记得吧!它里面就只有一个抽象方法,因此它就是一个函数式接口。既然是一个函数式接口,那么我们就可以使用@FunctionalInterface注解来修饰了。
package com.meimeixia.java8; @FunctionalInterface // 使用该注解修饰之后,说明这个接口必须是一个函数式接口,而且里面只能有一个抽象方法 public interface MyPredicate{ public boolean test(T t); }
下面我用一个例子来说明一下函数式接口的用法,即简单使用一下Lambda表达式来对一个数进行运算,不管是啥运算都行。
由于我们是想使用Lambda表达式来完成,所以首先我们得创建一个函数式接口,例如MyFun。
package com.meimeixia.java8;
@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;
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;
public class TestLambda {
List 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;
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;
@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("ttt 我李阿昀牛逼牛逼 ", (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为返回值。 - 接口中声明对应抽象方法。
- 在TestLambda类中声明一个方法,使用接口作为参数,计算两个Long型参数的和。
- 再计算两个Long型参数的乘积。
要想完成该练习题,首先我们得创建一个如下所示的函数式接口。
package com.meimeixia.exer; @FunctionalInterface public interface MyFunction2{ public R getValue(T t1, T t2); }
然后,在TestLambda类中编写一个对两个Long型数据进行处理的方法。
// 需求:对两个Long型数据进行处理 public void op(Long l1, Long l2, MyFunction2mf) { 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中提供的四大核心函数式接口。
ConsumerJava 8中消费型接口的定义如下所示。
何谓消费呢?消费就是有去无回。通过观察以下例子,你就应该知道该函数式接口该如何使用了。
package com.meimeixia.java8;
import org.junit.Test;
import java.util.function.Consumer;
public class TestLambda3 {
// Consumer:消费型接口,消费就是有去无回
@Test
public void test1() {
happy(10000, (m) -> System.out.println("咱们东哥喜欢大学生,每次消费:" + m + "元"));
}
public void happy(double money, Consumer con) {
con.accept(money);
}
}
SupplierJava 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;
public class TestLambda3 {
// Supplier:供给型接口,就是给你产生一些对象,并返回给你
@Test
public void test2() {
List numList = getNumList(10, () -> (int) (Math.random() * 100)); // 随机产生10个整数放入到集合中
for (Integer num : numList) {
System.out.println(num);
}
}
// 需求:产生指定个数的整数,并放入集合中
public List getNumList(int num, Supplier sup) {
List list = new ArrayList<>();
for (int i = 0; i < num; i++) {
Integer n = sup.get();
list.add(n);
}
return list;
}
// Consumer:消费型接口,消费就是有去无回
@Test
public void test1() {
happy(10000, (m) -> System.out.println("咱们东哥喜欢大学生,每次消费:" + m + "元"));
}
public void happy(double money, Consumer con) {
con.accept(money);
}
}
FunctionJava 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.Function;
import java.util.function.Supplier;
public class TestLambda3 {
// Function:函数型接口
@Test
public void test3() {
String newStr = strHandler("ttt 我李阿昀牛逼牛逼 ", (str) -> str.trim());
System.out.println(newStr);
String subStr = strHandler("我李阿昀牛逼牛逼", (str) -> str.substring(1, 4));
System.out.println(subStr);
}
// 需求:用于处理字符串
public String strHandler(String str, Function fun) {
return fun.apply(str);
}
// Supplier:供给型接口,就是给你产生一些对象,并返回给你
@Test
public void test2() {
List numList = getNumList(10, () -> (int) (Math.random() * 100)); // 随机产生10个整数放入到集合中
for (Integer num : numList) {
System.out.println(num);
}
}
// 需求:产生指定个数的整数,并放入集合中
public List getNumList(int num, Supplier sup) {
List list = new ArrayList<>();
for (int i = 0; i < num; i++) {
Integer n = sup.get();
list.add(n);
}
return list;
}
// Consumer:消费型接口,消费就是有去无回
@Test
public void test1() {
happy(10000, (m) -> System.out.println("咱们东哥喜欢大学生,每次消费:" + m + "元"));
}
public void happy(double money, Consumer con) {
con.accept(money);
}
}
PredicateJava 8中断言型接口的定义如下所示。
断言型接口主要是用于做一些判断操作的。例如处理字符串时,将满足指定条件的字符串放入到集合中去。
package com.meimeixia.java8;
import org.junit.Test;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.function.Supplier;
public class TestLambda3 {
// Predicate:断言型接口
@Test
public void test4() {
List list = Arrays.asList("Hello", "meimeixia", "Lambda", "www", "ok");
List strList = filterStr(list, (s) -> s.length() > 3);
for (String str : strList) {
System.out.println(str);
}
}
// 需求:将满足条件的字符串放入集合中
public List filterStr(List list, Predicate pre) {
List strList = new ArrayList<>();
for (String str : list) {
if (pre.test(str)) {
strList.add(str);
}
}
return strList;
}
// Function:函数型接口
@Test
public void test3() {
String newStr = strHandler("ttt 我李阿昀牛逼牛逼 ", (str) -> str.trim());
System.out.println(newStr);
String subStr = strHandler("我李阿昀牛逼牛逼", (str) -> str.substring(1, 4));
System.out.println(subStr);
}
// 需求:用于处理字符串
public String strHandler(String str, Function fun) {
return fun.apply(str);
}
// Supplier:供给型接口,就是给你产生一些对象,并返回给你
@Test
public void test2() {
List numList = getNumList(10, () -> (int) (Math.random() * 100)); // 随机产生10个整数放入到集合中
for (Integer num : numList) {
System.out.println(num);
}
}
// 需求:产生指定个数的整数,并放入集合中
public List getNumList(int num, Supplier sup) {
List list = new ArrayList<>();
for (int i = 0; i < num; i++) {
Integer n = sup.get();
list.add(n);
}
return list;
}
// Consumer:消费型接口,消费就是有去无回
@Test
public void test1() {
happy(10000, (m) -> System.out.println("咱们东哥喜欢大学生,每次消费:" + m + "元"));
}
public void happy(double money, Consumer con) {
con.accept(money);
}
}
以上就是Java 8中内置的四大核心函数式接口,现在大家应该都学会了吧!
其他接口如果你觉得以上Java 8中内置的四大核心函数式接口不够用,那么可以试试下面这些其他的接口,相信应该够你用了。
从上可以看出很多接口都是Java 8中内置的四大核心函数式接口的子接口。既然我们都已经会使用四大核心函数式接口了,那这些子接口还不是分分钟就会用了,是吧!



