Lambda、函数式接口、方法引用学习笔记

Lambda表达式,匿名函数

一、标准格式

Lambda省去面向对象的条条框框,格式由3个部分组成 :

  • 一些参数
  • 一个箭头
  • 一段代码

Lambda表达式的标准格式为︰

(参数类型 参数名称)->{代码语句}

格式说明∶

  • 小括号内的语法与传统方法参数列表一致︰无参数则留空;多个参数则用逗号分隔。
  • -> 是新引入的语法格式,代表指向动作。
  • 大括号内的语法与传统方法体要求基本一致。

例子:不使用Lambda表达式

//使用匿名内部类的方式,实现多线程
new Thread(
    new Runnable(){
        @override
        public void run(){
            System.out.println( Thread.currentThread().getName()+”新线程创建了");
         }
	}
).start();

例子:使用Lambda表达式

//使用Lambda表达式,实现多线程
new Thread(
    ()->{
		system.out.println(Thread.currentThread().getName()+”新线程创建了");
 	}
).start();

Lambda表达式 : 是可推导,可以省略

凡是根据上下文推导出来的内容,都可以省略书写可以省略的内容:

  1. (参数列表):括号中参数列表的数据类型,可以省略不写
  2. (参数列表):括号中的参数如果只有一个,那么类型和()都可以省略
  3. {一些代码} : 如果{ }中的代码只有一行,无论是否有返回值,都可以省略({ }, return,分号)
    注意:要省略{ },return,分号必须一起省略

例子:使用优化省略Lambda表达式

//优化省略Lambda
new Thread(
	()->System.out.println(Thread.currentThread( ).getName()+”新线程创建了")
).start()

二、 Lambda的使用前提

Lambda的语法非常简洁,完全没有面向对象复杂的束缚。但是使用时有几个问题需要特别注意:

  1. 使用lambda必须具有接口,且要求接口中有且仅有一个抽象方法。

    无论是JDK内置的 Runnable、comparator接口还是自定义的接口,只有当接口中的抽象方法存在且唯一时,才可以使用Lambda。

  2. 使用Lambda必须具有上下文推断。

    也就是方法的参数或局部变量类型必须为Lambda对应的接口类型,才能使用Lambda作为该接口的实例。

备注:有且仅有一个抽象方法的接口,称为"函数式接口”。

三、函数式接口

1、概念

函数式接口在Java中是指: 有且仅有一个抽象方法的接口

函数式接口,即适用于函数式编程场景的接口。而Java中的函数式编程体现就是Lambda,所以函数式接口就是可以适用于Lambda使用的接口。只有确保接口中有且仅有一个抽象方法,Java中的Lambda才能顺利地进行推导。

备注∶"语法糖"是指使用更加方便,但是原理不变的代码语法。例如在遍历集合时使用的for-each语法,其实底层的实现原理仍然是迭代器,这便是"语法糖"。从应用层面来讲,Java中的Lambda可以被当做是匿名内部类的“语法糖”,但是二者在原理上是不同的。

2、格式

只要确保接口中有且仅有一个抽象方法即可,当然接口中可以包含其他的方法(默认,静态,私有)。且接口当中抽象方法的 public abstract 是可以省略的。

@FunctionalInterface注解 作用 : 可以检测接口是否是一个函数式接口
是 : 编译成功 否 : 编译失败

修饰符 interface 接口名称{
	public abstract 返回值类型 方法名称(可选参数信息);
	//其他非抽象方法内容
}

扩展∶实际上使用内部类也可以达到同样的效果,只是将代码操作延迟到了另外一个对象当中通过调用方法来完成。而是否调用其所在方法是在条件判断之后才执行的。

3、使用Lambda作为参数和返回值

如果抛开实现原理不说,Java中的Lambda表达式可以被当作是匿名内部类的替代品。如果方法的参数是一个函数式接口类型,那么就可以使用Lambda表达式进行替代。使用Lambda表达式作为方法参数,其实就是使用函数式接口作为方法参数。

例如java.lang.Runnable 接口就是一个函数式接口,假设有一个startThread方法使用该接口作为参数,那么就可以使用Lambda进行传参。这种情况其实和 Thread类的构造方法参数为Runnable没有本质区别。

public class Demo04Runnable {
	private static void startThread( Runnable task) {
		new Thread(task). start( );
	}
		public static void main(String[] args) {
			startThread( () -> system.out.println("线程任务执行!"));
	}
}

类似地,如果一个方法的返回值类型是一个函数式接口,那么就可以直接返回一个Lambda表达式。当需要通过一个方法来获取一个java.util.Comparator 接口类型的对象作为排序器时,就可以调该方法获取。

public class Demo06Comparator {
    private static comparator<String> newComparator() {
    	return (a, b) -> b.length() - a.length();
    }
   
    public static void main(string[ ] args) {
   		 String[]array = { "abc", "ab", "abcd”};
   		 system.out.println(Arrays.tostring( array ) );
   		 Arrays.sort( array,newComparator());
    	 system.out.println(Arrays.tostring(array));
    }
}

4、常用函数式接口

JDK提供了大量常用的函数式接口以丰富Lambda的典型使用场景,它们主要在java.util.function包中被提供。下面是最简单的几个接口及使用示例。

泛型、枚举、反射

lambda表达式、链式编程、函数式接口、Stream流式计算

超级多 FunctionalInterface

作用:简化编程模型,在新版本的框架底层大量应用!

foreach(消费者类的函数式接口)

在这里插入图片描述

4.1 Supplier接口 --供给者接口

没有输入,只有返回值,返回一个 泛型指定的数据类型。

在这里插入图片描述

**java.util.function.Supplier接口仅包含一个无参的方法 : T get( )。用来 获取(生成)一个泛型参数指定类型的对象数据。由于这是一个函数式接口,这也就意味着对应的Lambda表达式需要"对外提供"一个符合泛型类型的对象数据。

4.2 Consumer接口 --消费者接口

只有输入,没有返回值,使用泛型的数据类型。

在这里插入图片描述

==java.util.function.Consumer==接口则正好与Supplier接口相反,它不是生产一个数据,而是消费(使用)一个数据,其数据类型由泛型决定。

  • 抽象方法 void accept( T t )

  • 意为消费一个指定泛型的数据。

  • 默认方法 : andThen( consumer con )

    • 需要两个consumer接口,可以把两个Consumer接口组合到一起,再对数据进行消费。
    consumer<String> con1;
    consumer<String> con2;
    String s = "hetlo";
    con1.accept(s);
    con2.accept(s);
    //等价于 连接两个consumer接口再进行消费
    con1.andThen( con2 ).accept(s);
    
    • 谁写前边谁先消费。
4.3 Predicate接口 --断定型接口

有一个输入参数,返回值为布尔值。

在这里插入图片描述

有时候我们需要对某种类型的数据进行判断,从而得到一个boolean值结果。这时可以使用==java.util.function.Predicate==接口。

  • 抽象方法 : boolean test( T t )
    • 用于条件判断的场景
  • 默认方法 : and
    • 既然是条件判断,就会存在与、或、非三种常见的逻辑关系。其中将两个Predicate条件使用"与"逻辑连接起来实现的效果。
  • 默认方法 : or
    • 方法 or 实现逻辑关系中的
  • 默认方法 : negate
    • 方法 negate 实现逻辑关系中的
4.4 Function接口 --改造型接口

传入 T数据类型,返回 R 数据类型

在这里插入图片描述

java.util.function.Function<T,R>接口用来根据一个类型的数据得到另一个类型的数据,前者称为前置条件,后者称为后置条件。

  • 抽象方法 : R apply( T t )
    • 根据类型T的参数获取类型R的结果。使用的场景例如∶将String类型转换为Integer类型
  • 默认方法 : andThen( Function fun)
    • 用来进行组合操作

四、方法引用

在使用Lambda表达式的时候,我们实际上传递进去的代码就是一种解决方案∶拿什么参数做什么操作。那么考虑一种情况 : 如果我们在Lambda中所指定的操作方案,已经有地方存在相同方案,那是否还有必要再写重复逻辑 ?

4.1冗余的Lambda场景

来看一个简单的函数式接口以应用Lambda表达式︰

@FunctionalInterface
public interface Printable {
	void print(string str);
}

在 Printable接口 当中唯一的 抽象方法 prin 接收一个字符串参数,目的就是为了打印显示它。那么通过Lambda来使用它的代码很简单∶

public class Demo01Printsimple {
	private static void printstring(Printable data) {
		data.print("Hello,world!");
	}
	public static void main(String[] args) {
		printstring(s -> system.out.println(s));
	}
}

其中 printString 方法只管调用 Printable 接口的 print( )方法,而并不管 print( ) 方法的具体实现逻辑会将字符串打印到什么地方去。而main方法通过Lambda表达式指定了函数式接口 Printable的具体操作方案为 : 拿到 String(类型可推导,所以可省略)数据后,在控制台中输出它。

4.2问题分析

这段代码的问题在于,对字符串进行控制台打印输出的操作方案,明明已经有了现成的实现,那就是.System .out对象中的 println(String)方法。既然Lambda希望做的事情就是调用println (String)方法,那何必自己手动调用呢?

4.3、用方法引用改进代码

能.否省去 Lambda的语法格式(尽管它已经相当简洁)呢 ? 只要“引用”过去就好了︰

public class Demo02PrintRef {
    private static void printstring(Printable data) {
    	data.print( "Hello, world! ");
    }
    public static void main( String[] args) {
   	 	printstring(system.out :: println);
    }
}

请注意其中的双冒号 ::写法,这被称为 方法引用,而双冒号是一种新的语法。

4.4万法引用符

双冒号 :: 为 引用运算符,而它所在的表达式被称为方法引用。如果Lambda要表达的函数方案已经存在于某个方法的实现中,那么则可以通过双冒号来引用该方法作为Lambda的替代者。

语义分析

例如上例中,System.out对象中有一个重载的==println(String)==方法恰好就是我们所需要的。那么对于printstring方法的函数式接口参数,对比下面两种写法,完全等效︰

  • Lambda表达式写法 : s -> system.out.println(s);
  • 方法引用写法: System.out : : println

第一种语义是指 : 拿到参数之后经Lambda之手,继而传递给 System.out.println方法去处理。

第二种等效写法的语义是指∶直接让system .out 中的 println( ) 方法来取代Lambda。

两种写法的执行效果完全一样,而第二种方法引用的写法复用了已有方案,更加简洁。

注 : Lambda中传递的参数一定是方法引用中的那个方法可以接收的类型,否则会抛出异常

推导与省略

如果使用Lambda,那么根据 "可推导就是可省略”的原则,无需指定参数类型,也无需指定的重载形式――它们都将被自动推导。而如果使用方法引用,也是同样可以根据上下文进行推导。

函数式接口是Lambda的基础,而方法引用是Lambda的孪生兄弟。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值