Java:函数式(Functional)接口

avatar
作者
筋斗云
阅读量:1

文章目录

1 什么是函数式接口

  • 只包含一个抽象方法(Single Abstract Method,简称SAM)的接口,称为函数式接口。当然该接口可以包含其他非抽象方法。
  • 你可以通过 Lambda 表达式来创建该接口的对象。(若 Lambda 表达式抛出一个受检异常(即:非运行时异常),那么该异常需要在目标接口的抽象方法上进行声明)。
  • 我们可以在一个接口上使用 @FunctionalInterface 注解,这样做可以检查它是否是一个函数式接口。同时 javadoc 也会包含一条声明,说明这个接口是一个函数式接口。
  • java.util.function包下定义了Java 8 的丰富的函数式接口

2 如何理解函数式接口

  • Java从诞生日起就是一直倡导“一切皆对象”,在Java里面面向对象(OOP)编程是一切。但是随着python、scala等语言的兴起和新技术的挑战,Java不得不做出调整以便支持更加广泛的技术要求,即Java不但可以支持OOP还可以支持OOF(面向函数编程)
    • Java8引入了Lambda表达式之后,Java也开始支持函数式编程。
    • Lambda表达式不是Java最早使用的。目前C++,C#,Python,Scala等均支持Lambda表达式。
  • 面向对象的思想:
    • 做一件事情,找一个能解决这个事情的对象,调用对象的方法,完成事情。
  • 函数式编程思想:
    • 只要能获取到结果,谁去做的,怎么做的都不重要,重视的是结果,不重视过程。
  • 在函数式编程语言当中,函数被当做一等公民对待。在将函数作为一等公民的编程语言中,Lambda表达式的类型是函数。但是在Java8中,有所不同。在Java8中,Lambda表达式是对象,而不是函数,它们必须依附于一类特别的对象类型——函数式接口。
  • 简单的说,在Java8中,Lambda表达式就是一个函数式接口的实例。这就是Lambda表达式和函数式接口的关系。也就是说,只要一个对象是函数式接口的实例,那么该对象就可以用Lambda表达式来表示。

3 举例

举例1:
在这里插入图片描述
举例2:
在这里插入图片描述
作为参数传递 Lambda 表达式:
在这里插入图片描述

作为参数传递 Lambda 表达式:为了将 Lambda 表达式作为参数传递,接收Lambda 表达式的参数类型必须是与该 Lambda 表达式兼容的函数式接口的类型。

Java 内置函数式接口

1 之前的函数式接口

之前学过的接口,有些就是函数式接口,比如:

- java.lang.Runnable   - public void run() - java.lang.Iterable<T>   - public Iterator<T> iterate() - java.lang.Comparable<T>   - public int compareTo(T t) - java.util.Comparator<T>   - public int compare(T t1, T t2) 

2 四大核心函数式接口

函数式接口称谓参数类型用途
Consumer<T> 消费型接口T对类型为T的对象应用操作,包含方法: void accept(T t)
Supplier<T> 供给型接口返回类型为T的对象,包含方法:T get()
Function<T, R> 函数型接口T对类型为T的对象应用操作,并返回结果。结果是R类型的对象。包含方法:R apply(T t)
Predicate<T> 判断型接口T确定类型为T的对象是否满足某约束,并返回 boolean 值。包含方法:boolean test(T t)
3.4.3 其它接口

类型1:消费型接口

消费型接口的抽象方法特点:有形参,但是返回值类型是void

接口名抽象方法描述
BiConsumer<T,U>void accept(T t, U u)接收两个对象用于完成功能
DoubleConsumervoid accept(double value)接收一个double值
IntConsumervoid accept(int value)接收一个int值
LongConsumervoid accept(long value)接收一个long值
ObjDoubleConsumervoid accept(T t, double value)接收一个对象和一个double值
ObjIntConsumervoid accept(T t, int value)接收一个对象和一个int值
ObjLongConsumervoid accept(T t, long value)接收一个对象和一个long值

类型2:供给型接口

这类接口的抽象方法特点:无参,但是有返回值

接口名抽象方法描述
BooleanSupplierboolean getAsBoolean()返回一个boolean值
DoubleSupplierdouble getAsDouble()返回一个double值
IntSupplierint getAsInt()返回一个int值
LongSupplierlong getAsLong()返回一个long值
类型3:函数型接口

这类接口的抽象方法特点:既有参数又有返回值

接口名抽象方法描述
UnaryOperatorT apply(T t)接收一个T类型对象,返回一个T类型对象结果
DoubleFunctionR apply(double value)接收一个double值,返回一个R类型对象
IntFunctionR apply(int value)接收一个int值,返回一个R类型对象
LongFunctionR apply(long value)接收一个long值,返回一个R类型对象
ToDoubleFunctiondouble applyAsDouble(T value)接收一个T类型对象,返回一个double
ToIntFunctionint applyAsInt(T value)接收一个T类型对象,返回一个int
ToLongFunctionlong applyAsLong(T value)接收一个T类型对象,返回一个long
DoubleToIntFunctionint applyAsInt(double value)接收一个double值,返回一个int结果
DoubleToLongFunctionlong applyAsLong(double value)接收一个double值,返回一个long结果
IntToDoubleFunctiondouble applyAsDouble(int value)接收一个int值,返回一个double结果
IntToLongFunctionlong applyAsLong(int value)接收一个int值,返回一个long结果
LongToDoubleFunctiondouble applyAsDouble(long value)接收一个long值,返回一个double结果
LongToIntFunctionint applyAsInt(long value)接收一个long值,返回一个int结果
DoubleUnaryOperatordouble applyAsDouble(double operand)接收一个double值,返回一个double
IntUnaryOperatorint applyAsInt(int operand)接收一个int值,返回一个int结果
LongUnaryOperatorlong applyAsLong(long operand)接收一个long值,返回一个long结果
BiFunction<T,U,R>R apply(T t, U u)接收一个T类型和一个U类型对象,返回一个R类型对象结果
BinaryOperatorT apply(T t, T u)接收两个T类型对象,返回一个T类型对象结果
ToDoubleBiFunction<T,U>double applyAsDouble(T t, U u)接收一个T类型和一个U类型对象,返回一个double
ToIntBiFunction<T,U>int applyAsInt(T t, U u)接收一个T类型和一个U类型对象,返回一个int
ToLongBiFunction<T,U>long applyAsLong(T t, U u)接收一个T类型和一个U类型对象,返回一个long
DoubleBinaryOperatordouble applyAsDouble(double left, double right)接收两个double值,返回一个double结果
IntBinaryOperatorint applyAsInt(int left, int right)接收两个int值,返回一个int结果
LongBinaryOperatorlong applyAsLong(long left, long right)接收两个long值,返回一个long结果

类型4:判断型接口

这类接口的抽象方法特点:有参,但是返回值类型是boolean结果。

接口名抽象方法描述
BiPredicate<T,U>boolean test(T t, U u)接收两个对象
DoublePredicateboolean test(double value)接收一个double值
IntPredicateboolean test(int value)接收一个int值
LongPredicateboolean test(long value)接收一个long值

内置接口代码演示

举例1:

import java.util.Arrays; import java.util.List;  public class TestConsumer {     public static void main(String[] args) {         List<String> list = Arrays.asList("java","c","python","c++","VB","C#");         //遍历Collection集合,并将传递给action参数的操作代码应用在每一个元素上。         list.forEach(s -> System.out.println(s));     } } 

举例2:

import java.util.function.Supplier;  public class TestSupplier {     public static void main(String[] args) {         Supplier<String> supplier = () -> "小小水";         System.out.println(supplier.get());     } } 

举例3:

import java.util.ArrayList;  public class TestPredicate {     public static void main(String[] args) {         ArrayList<String> list = new ArrayList<>();         list.add("hello");         list.add("java");         list.add("hi");         list.add("ok");         list.add("yes");          System.out.println("删除之前:");         list.forEach(t-> System.out.println(t)); 		         //用于删除集合中满足filter指定的条件判断的。         //删除包含o字母的元素         list.removeIf(s -> s.contains("o"));          System.out.println("删除包含o字母的元素之后:");         list.forEach(t-> System.out.println(t));     } } 

举例4:

import java.util.function.Function;  public class TestFunction {     public static void main(String[] args) {         //使用Lambda表达式实现Function<T,R>接口,可以实现将一个字符串首字母转为大写的功能。         Function<String,String> fun = s -> s.substring(0,1).toUpperCase() + s.substring(1);         System.out.println(fun.apply("hello"));     } } 

5 练习

练习1:无参无返回值形式

假如有自定义函数式接口Call如下:

public interface Call {     void shout(); } 

在测试类中声明一个如下方法:

public static void callSomething(Call call){ 		call.shout(); } 

在测试类的main方法中调用callSomething方法,并用Lambda表达式为形参call赋值,可以喊出任意你想说的话。

public class TestLambda { 	public static void main(String[] args) { 		callSomething(()->System.out.println("回家吃饭")); 		callSomething(()->System.out.println("我爱你")); 		callSomething(()->System.out.println("滚蛋")); 		callSomething(()->System.out.println("回来")); 	} 	public static void callSomething(Call call){ 		call.shout(); 	} } interface Call {     void shout(); } 

练习2:消费型接口

代码示例:Consumer接口

在JDK1.8中Collection集合接口的父接口Iterable接口中增加了一个默认方法:

public default void forEach(Consumer<? super T> action) 遍历Collection集合的每个元素,执行“xxx消费型”操作。

在JDK1.8中Map集合接口中增加了一个默认方法:

public default void forEach(BiConsumer<? super K,? super V> action)遍历Map集合的每对映射关系,执行“xxx消费型”操作。

案例:

(1)创建一个Collection系列的集合,添加一些字符串,调用forEach方法遍历查看

(2)创建一个Map系列的集合,添加一些(key,value)键值对,调用forEach方法遍历查看

示例代码:

	@Test 	public void test1(){ 		List<String> list = Arrays.asList("hello","java","hi","atguigu"); 		list.forEach(s -> System.out.println(s));     } 	@Test 	public void test2(){ 		HashMap<Integer,String> map = new HashMap<>(); 		map.put(1, "hello"); 		map.put(2, "java"); 		map.put(3, "lambda"); 		map.put(4, "hi"); 		map.forEach((k,v) -> System.out.println(k+"->"+v)); 	} 

练习3:供给型接口

代码示例:Supplier接口

在JDK1.8中增加了StreamAPI,java.util.stream.Stream是一个数据流。这个类型有一个静态方法:

public static <T> Stream<T> generate(Supplier<T> s)可以创建Stream的对象。而又包含一个forEach方法可以遍历流中的元素:public void forEach(Consumer<? super T> action)

案例:

现在请调用Stream的generate方法,来产生一个流对象,并调用Math.random()方法来产生数据,为Supplier函数式接口的形参赋值。最后调用forEach方法遍历流中的数据查看结果。

	@Test 	public void test2(){ 		Stream.generate(() -> Math.random()).forEach(num -> System.out.println(num)); 	} 

练习4:功能型接口

代码示例:Function<T,R>接口

在JDK1.8时Map接口增加了很多方法,例如:

public default void replaceAll(BiFunction<? super K,? super V,? extends V> function) 按照function指定的操作替换map中的value。

public default void forEach(BiConsumer<? super K,? super V> action)遍历Map集合的每对映射关系,执行“xxx消费型”操作。

案例:

(1)声明一个Employee员工类型,包含编号、姓名、薪资。

(2)添加n个员工对象到一个HashMap<Integer,Employee>集合中,其中员工编号为key,员工对象为value。

(3)调用Map的forEach遍历集合

(4)调用Map的replaceAll方法,将其中薪资低于10000元的,薪资设置为10000。

(5)再次调用Map的forEach遍历集合查看结果

Employee类:

class Employee{ 	private int id; 	private String name; 	private double salary; 	public Employee(int id, String name, double salary) { 		super(); 		this.id = id; 		this.name = name; 		this.salary = salary; 	} 	public Employee() { 		super(); 	} 	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 double getSalary() { 		return salary; 	} 	public void setSalary(double salary) { 		this.salary = salary; 	} 	@Override 	public String toString() { 		return "Employee [id=" + id + ", name=" + name + ", salary=" + salary + "]"; 	} 	 } 

测试类:

import java.util.HashMap;  public class TestLambda { 	public static void main(String[] args) { 		HashMap<Integer,Employee> map = new HashMap<>(); 		Employee e1 = new Employee(1, "张三", 8000); 		Employee e2 = new Employee(2, "李四", 9000); 		Employee e3 = new Employee(3, "王五", 10000); 		Employee e4 = new Employee(4, "赵六", 11000); 		Employee e5 = new Employee(5, "钱七", 12000); 		 		map.put(e1.getId(), e1); 		map.put(e2.getId(), e2); 		map.put(e3.getId(), e3); 		map.put(e4.getId(), e4); 		map.put(e5.getId(), e5); 		 		map.forEach((k,v) -> System.out.println(k+"="+v)); 		System.out.println(); 		 		map.replaceAll((k,v)->{ 			if(v.getSalary()<10000){ 				v.setSalary(10000); 			} 			return v; 		}); 		map.forEach((k,v) -> System.out.println(k+"="+v)); 	} } 

练习5:判断型接口

代码示例:Predicate接口

JDK1.8时,Collecton接口增加了一下方法,其中一个如下:

public default boolean removeIf(Predicate<? super E> filter) 用于删除集合中满足filter指定的条件判断的。

public default void forEach(Consumer<? super T> action) 遍历Collection集合的每个元素,执行“xxx消费型”操作。

案例:

(1)添加一些字符串到一个Collection集合中

(2)调用forEach遍历集合

(3)调用removeIf方法,删除其中字符串的长度<5的

(4)再次调用forEach遍历集合

import java.util.ArrayList;  public class TestLambda { 	public static void main(String[] args) { 		ArrayList<String> list = new ArrayList<>(); 		list.add("hello"); 		list.add("java"); 		list.add("hi"); 		list.add("ok"); 		list.add("yes"); 		 		list.forEach(str->System.out.println(str)); 		System.out.println(); 		 		list.removeIf(str->str.length()<5); 		list.forEach(str->System.out.println(str)); 	} } 

练习6:判断型接口

案例:

(1)声明一个Employee员工类型,包含编号、姓名、性别,年龄,薪资。

(2)声明一个EmployeeSerice员工管理类,包含一个ArrayList集合的属性all,在EmployeeSerice的构造器中,创建一些员工对象,为all集合初始化。

(3)在EmployeeSerice员工管理类中,声明一个方法:ArrayList get(Predicate p),即将满足p指定的条件的员工,添加到一个新的ArrayList 集合中返回。

(4)在测试类中创建EmployeeSerice员工管理类的对象,并调用get方法,分别获取:

  • 所有员工对象
  • 所有年龄超过35的员工
  • 所有薪资高于15000的女员工
  • 所有编号是偶数的员工
  • 名字是“张三”的员工
  • 年龄超过25,薪资低于10000的男员工

示例代码:

Employee类:

public class Employee{ 	private int id; 	private String name; 	private char gender; 	private int age; 	private double salary; 	 	public Employee(int id, String name, char gender, int age, double salary) { 		super(); 		this.id = id; 		this.name = name; 		this.gender = gender; 		this.age = age; 		this.salary = salary; 	} 	public Employee() { 		super(); 	} 	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 double getSalary() { 		return salary; 	} 	public void setSalary(double salary) { 		this.salary = salary; 	} 	@Override 	public String toString() { 		return "Employee [id=" + id + ", name=" + name + ", gender=" + gender + ", age=" + age + ", salary=" + salary 				+ "]"; 	} } 

员工管理类:

class EmployeeService{ 	private ArrayList<Employee> all; 	public EmployeeService(){ 		all = new ArrayList<Employee>(); 		all.add(new Employee(1, "张三", '男', 33, 8000)); 		all.add(new Employee(2, "翠花", '女', 23, 18000)); 		all.add(new Employee(3, "无能", '男', 46, 8000)); 		all.add(new Employee(4, "李四", '女', 23, 9000)); 		all.add(new Employee(5, "老王", '男', 23, 15000)); 		all.add(new Employee(6, "大嘴", '男', 23, 11000)); 	} 	public ArrayList<Employee> get(Predicate<Employee> p){ 		ArrayList<Employee> result = new ArrayList<Employee>(); 		for (Employee emp : result) { 			if(p.test(emp)){ 				result.add(emp); 			} 		} 		return result; 	} } 

测试类:

public class TestLambda { 	public static void main(String[] args) { 		EmployeeService es = new EmployeeService(); 		 		es.get(e -> true).forEach(e->System.out.println(e)); 		System.out.println(); 		es.get(e -> e.getAge()>35).forEach(e->System.out.println(e)); 		System.out.println(); 		es.get(e -> e.getSalary()>15000 && e.getGender()=='女').forEach(e->System.out.println(e)); 		System.out.println(); 		es.get(e -> e.getId()%2==0).forEach(e->System.out.println(e)); 		System.out.println(); 		es.get(e -> "张三".equals(e.getName())).forEach(e->System.out.println(e)); 		System.out.println(); 		es.get(e -> e.getAge()>25 && e.getSalary()<10000 && e.getGender()=='男').forEach(e->System.out.println(e)); 	} } 

广告一刻

为您即时展示最新活动产品广告消息,让您随时掌握产品活动新动态!