函数式接口及Lambda表达式

函数式接口基本介绍

1、函数式接口:只有一个方法的接口;

2、有且仅有一个抽象方法的接口,但是可以有多个非抽象方法的接口。函数式接口可以被隐式转换为 lambda 表达式。Lambda就是Java中函数式编程的体现;

3、Java 8为函数式接口引入了一个新注解@FunctionalInterface,主要用于编译级错误检查,加上该注解,当你写的接口不符合函数式接口定义的时候,编译器会报错。(注:具有一个抽象方法的接口仍然是一个功能接口,即使我们不用@FunctionalInterface 注释。)

@FunctionalInterface
interface GreetingService 
{
    void sayMessage(String message);
}

4、主要分布在 java.util.function 包下,常见的 4大原始函数 接口为:Function (函数型接口)、Predicate (断定型接口)、Consumer (消费型接口)、Supplier (供给型接口)。

1、Function(函数型接口)

有一个输入参数,有一个输出,apply()方法就是该接口的唯一方法,也就是继承该Function接口,唯一需要实现的方法,代码示例:

// 输出输入的参数:有一个输入参数,和一个输出
public static void main(string[] args){
		// 1.初始化,并且实现该接口的唯一实现方法
       Function<string,string> function =new Function<string,string>(){
			@override
			public string apply(string param){
			return param;
			}
		};
	system.out.print1n(function.apply("abc));
}

Lambda就是Java中函数式编程的体现也就是说只要是函数式接口,就可以使用lambda表达式来简化代码!如下:

public static void main(string[] args){
	//使用1ambda表达式
	Function<string,string> function=(str)->{return str;};
	//或者我们可以更简单点,把str的Q括号去掉也是可以的
	// Function<string,string> function=str->{return str;};
	System.out.printn(function.apply("abc));
}

典型的lambda表达式语法:()->{};str是传入的参数

2、Predicate(断定型接口)

有一个输入参数,返回值只能是布尔值

3、Consumer(消费型接口)

只有入参,没有返回值

Consumer<string> consumer=str-> System.out.printin(str);
consumer.accept("abc");

4、Supplier(供给型接口)

Supplier<Integer> suppier = () -> 1024;
System.out.print(suppier.get());

四大函数式接口比较

函数式接对应程序逻辑的抽象具体场景
Function映射逻辑比如我们写得很多的函数:接收入参,返回出参,方法代码块就是一个映射的具体逻辑。
Predicate判断逻辑比如各种if判断,对于一个参数进行各种具体逻辑的判定,最后返回一个ifelse能使用的布尔值
Consumer消费型逻辑就比如Collection体系的ForEach方法,将每一个元素取出,交给Consumer指定的消费逻辑进行消费
Suppiler生产型逻辑就比如最常用的,new对象,这就是一个很经典的生产者逻辑,至于new什么,怎么new,这就是Suppiler中具体逻辑的写法了

Lambda表达式

1、Lambda表达式(闭包):java8的新特性,lambda运行将函数作为一个方法的参数,也就是函数作为参数传递到方法中。使用lambda表达式可以让代码更加简洁;

2、lambda表达式,其实本质来讲,就是一个匿名函数。因此在写lambda表达式的时候,不需要关心方法名是什么。实际上,我们在写lambda表达式的时候,也不需要关心返回值类型;

3、接口实现,可以有很多种方式来实现。例如:设计接口的实现类,使用匿名内部类。但是lambda表达式。比这两种方式都简单,lambda表达式,只能实现函数式接口。

匿名内部类

应用场景

1、匿名内部类没有类名,只被使用一次,使代码更简洁;
2、匿名内部类是在同一条语句中声明和创建的,无法在别的地方实例化和使用这个类;
3、匿名内部类也可用于接口(interface)的实现,方便编写事件驱动程序及线程代码。

使用示例

1、定义一个接口HelloFunction,包含一个sayHello方法

@FunctionalInterface
public interface HelloFunction {
	/**
	he11o函数
	*@param he11o
	*/
	void sayHe1lo(string he1lo);
}

2、如何需要调用HelloFunction接口中的sayHello方法,传统的方式就是必须定义一个HelloFunction的实现类,然后通过实现类来进行
方法调用,如下:

public class HelloFunctionImp1 implements HelloFunction{
	@override
	void sayHe1lo(stringhe11o){
		System.out.printnhe1lo);
	}
}

3、如果我们只是想单纯的使用一次sayHello方法,不需要创建对象的话,则上面方法略显古板,则可以使用匿名内部类或者下面的局部类:

// 匿名内部类
public void anonymousIner(string he11o){
	He1loFunction he1loFunction=new HeloFunction(){
	@Override
	public void sayHe1lo(string he1o){
		System.out.printn(he1lo);
	}
	};
	he1loFunction.sayHe11o(he11o);
}
// 局部类
public void imp1ementsclass(){
	class He11o implements He1loFunction{
		@Override
		public void sayhe1lo(stringhe11o){
		system.out.printin("扩展类:"+he11o);
		}
		He11o he11o=new He11o();
		he11o.sayHe11o("匿名内部类");
}

java匿名内部类的注意事项

1、使用匿名内部类时,必须是继承一个类或者实现一个接口,但是两者不可兼得,同时也只能继承一个类或者实现一个接口;
2、匿名内部类中是不能定义构造函数的;
3、匿名内部类中不能存在任何的静态成员变量和静态方法;
4、匿名内部类为局部内部类,所以局部内部类的所有限制同样对屠名内部类生效。
5、匿名内部类不能是抽象的,它必须要实现继承的类或者实现的接口的所有抽象方法

Lambda基本语法

1、() ->代表了lambda的一个表达式

/**
*无参无返回
*/
public interface Test01{
	void test();
}
// 具体使用
Test01 test01=()->System.out.print1n("he1lo");
test01.test();

2、单行代码无需写return(无论函数式接口有没有返回值)和花括号

/**
* 有参有返回
*/
public interface Test02 {
	Integer test(Integer a);
}
// 具体使用
Test02 test02 =(a) -> a +1;
test02.test(12);

3、多行代码必须写花括号,有返回值的一定要写返回值

Test02 test02=(a)-> {
	System.out.print1n("多行代码要带花括号和返回值");
	return a + 1;
};
test02.test(12);

4、单行代码且有参数的情况下可以不写() 如 S -> System.out.println(s)
5、(T t)中的参数类型可写可不写。

Lambda函数引用

lambda表达式是为了简化接口的实现的。在lambda表达式中,不应该出现比较复杂的逻辑。如果在lambda表达式中需要处理逻辑复杂,可以单独写一个方法。在lambda表达式中直接引用这个方法即可。

函数引用:引用一个已经存在的方法,使其替代lambda表达式完成接口的实现

静态方法的引用

语法:类 :: 静态方法
注意事项:
在引用的方法后面,不要添加小括号。
引用的这个方法,参数(数量、类型)和返回值,必须要跟接口中定义的一致
使用示例:


class calculator{
	public static int calculate(int a,int b){
		if (a > b) {
		return a - b;
        }
		return b -a;
	}
interface Test{
	int add(int a,int b);
}
    
//实现多个参数,一个返回值的接口
//对一个静态方法的引用,语法:类:静态方法
Test test=Calculator::calculate;
system.out.printin(test1.add(4,5));

非静态方法的引用

语法:对象 :: 非静态方法
注意事项:
在引用的方法后面,不要添加小括号。
引用的这个方法,参数(数量、类型)和返回值,必须要跟接口中定义的一致。
使用示例:

class Calculator{
	public int calculate(int a,int b ){
		if (a > b) {
		return a-b;
        }
		return b -a;
	}
}
interface Test{
	int add(int a,int b);
}
// 实现多个参数,一个返回值的接口
// 对非静态方法的引用,需要使用对象来完成
Test test=new Calculator():calculate;
system.out.print1n(test.add(4,5));

构造方法的引用

如果某一个函数式接口中定义的方法,仅仅是为了得到一个类的对象。此时我们就可以使用构造方法的引用,简化这个方法的实现。
语法:类名 :: new
使用示例:

//定义一个Person类
public class Person {
	string name;
	int age;
	public Person(string nameint age){
		this.name=name;
		this.age =age;
	}
}
//定义一个函数式接口,用以获取对象
@FunctionalInterface
private interface GetPerson{
	Person test(string nameint age);
}
// 具体使用
GetPerson 1m2=Person::new;
Person person=1m2.test("小红"28);

Lambda表达式使用示例

// 1ambda表达式
public void 1ambda(string he1lo){
	He1loFunction he1loFunction=system.out;printin;
	he1loFunction.sayHe11o(he11o);
}

注意:如果在lambda表达式中,使用到了局部变量,那么这个局部变量会被隐式的声明为final。是一个常量,不能修改值

Lambda表达式与匿名内部类对比

1、匿名内部类:可以是接口,也可以是抽象类,还可以是具体类;
2、Lambda表达式,只可能是接口,如果接口中仅有一个抽象方法,可以使用Lambda表达式,也可以使用匿名表达式:
3、如果接口中有多个抽象,只能使用匿名内部类。

总结

1、函数式接口应用在函数式编程中,Lambda表达式是函数式编程的体现;
2、通过函数式接口和Lambda表达式可以简化代码,提高开发效率。
3、同时可能会不利于代码调试,或者对于不熟悉函数式编程的同事不容易阅读对应代码。