纵有疾风起,人生不言弃~|

夜色哪里都是美

园龄:2年9个月粉丝:4关注:2

JDK8新特性(一) --- Lambda表达式

Lambda表达式

Lambda是一个匿名函数,可以理解为一段可以传递的代码。
匿名内部类语法冗余,Lmabda是简化匿名内部类的简写。

Lambda的标准格式

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

(参数类型 参数名称) -> {
	代码体;
}

//(参数类型,参数名称):方法的参数列表
//代码体:方法体
-> :箭头,分割参数列表和方法体

1、无参无返回值的Lambda

先定义一个接口

interface Swimmable {
	public abstract void swimming();
}

创建匿名内部类

public class Demo02LambdaUse {
	public static void main(String[] args) {
		goSwimming(new Swimmable() {
			@Override
			public void swimming() {
				System.out.println("匿名内部类游泳");
			}
		});
		//Lambda表达式的实现方式
		goSwimming(() -> {
			System.out.println("Lambda游泳");
		});
	}
	
	public static void goSwimming(Swimmable swimmable) {
		swimmable.swimming();
	}
}

2、有参有返回值的Lambda

java.util.Comparator 接口的使用场景代码,其中的抽象方法定义为:
public abstract int compare(T o1, T o2);
当需要对一个对象集合进行排序时, Collections.sort 方法需要一个 Comparator
接口实例来指定排序的规则。

//定义需要排序的对象
public class Person {
	private String name;
	private int age;
	private int height;
	//省略
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;

public class Demo03LambdaUse {
	public static void main(String[] args) {
		ArrayList<Person> persons = new ArrayList<>();
		persons.add(new Person("AA", 58, 174));
		persons.add(new Person("BB", 58, 176));
		persons.add(new Person("CC", 54, 171));
		persons.add(new Person("DD", 53, 178));
		Collections.sort(persons,new Comparator<Person>){
			@Override
			public int compare(Person p1,Person p2){
				return p1.getAge() - p2.getAge();
			}
		});
		
		//lambda写法
		Collection.sort(persons,(Person p1,Person p2) ->{
			return p1.getAge() - p2.getAge();
		})
	}
}

3、对forEach()的简写

List<Integer> list = Arrays.asList(11, 22, 33, 44);
list.forEach(new Consumer<Integer>() {
	@Override
	public void accept(Integer integer) {
		System.out.println(integer);
	}
});
System.out.println("-----------------");
list.forEach((s) -> {
	System.out.println(s);
});

总结:
以后我们调用方法时,看到参数是接口就可以考虑使用Lambda表达式,
Lambda表达式相当于是对接口中抽象方法的重写

Lambda的实现原理

\\实现接口
@FunctionalInterface
interface Swimmable {
	public abstract void swimming();
}

使用匿名内部类实现

public class Demo04LambdaImpl {
	public static void main(String[] args) {
		goSwimming(new Swimmable() {
			@Override
			public void swimming() {
				System.out.println("使用匿名内部类实现游泳");
			}
		});
	}
	public static void goSwimming(Swimmable swimmable) {
		swimmable.swimming();
	}
}

运行后可以看到匿名内部类会在编译后产生一个类: Demo04LambdaImpl$1.class
image

//反编译后可以看到如下代码
import java.io.PrintStream;
// Swimmable, Demo04LambdaImpl
static class Demo04LambdaImpl$1 implements Swimmable {
	public void swimming()
	{
		System.out.println("使用匿名内部类实现游泳");
	}
	Demo04LambdaImpl$1() {
	}
}

使用Lambda表达式

public class Demo04LambdaImpl {
	public static void main(String[] args) {
		goSwimming(() -> {
			System.out.println("使用匿名内部类实现游泳");
		});
	}
	public static void goSwimming(Swimmable swimmable) {
		swimmable.swimming();
	}
}

运行程序后并没有出现一个新的类,使用了Lambda后使用XJad反编译工具也无法反编译。
我们使用JDK自带的一个工具: javap ,对字节码进行反汇编,查看字节码指令。

\\可以在DOS中输入命令进行反编译
javap -c -p 文件名.class
-c:表示对代码进行反汇编
-p:显示所有类和成员

可以看到在类中多出了一个私有的静态方法 lambda$main$0 。对代码进行debug,
可以确认 lambda$main$0 里面放的就是Lambda中的内容.

image

我们可以这么理解 lambda$main$0 方法:

//关于这个方法 lambda$main$0 的命名:
//以lambda开头,因为是在main()函数里使用了lambda表达式,
//所以带有$main表示,因为是第一个,所以$0。

public class Demo04LambdaImpl {
	public static void main(String[] args) {
		...
	}
	private static void lambda$main$0() {
		System.out.println("Lambda游泳");
	}
}
//Lambda在运行的时候会生成一个内部类,调用该方法。
//为了验证是否生成内部类,可以在DOS界面运行java文件时加上 -Djdk.internal.lambda.dumpProxyClasses 
//加上这个参数后,运行时会将生成的内部类class码输出到一个文件中。
//在DOS命令行输入以下命令
C:\Users\>java -Djdk.internal.lambda.dumpProxyClasses
com.lhc.demo01lambda.Demo04LambdaImpl
Lambda游泳

执行完毕生成了一个新的类:
image
反编译Demo04LambdaImpl$$Lambda$1.class字节码文件

//可以看到这个匿名内部类实现了 Swimmable 接口,并且重写了 swimming 方法, 
//swimming方法调用Demo04LambdaImpl.lambda$main$0(),也就是调用Lambda中的内容。

// Referenced classes of package com.lhc.demo01lambda:
// Swimmable, Demo04LambdaImpl
final class Demo04LambdaImpl$$Lambda$1 implements Swimmable {
	public void swimming()
	{
		Demo04LambdaImpl.lambda$main$0();
	}
	private Demo04LambdaImpl$$Lambda$1()
	{
	}
}

所以可以理解为:

public class Demo04LambdaImpl {
	public static void main(String[] args) {
		goSwimming(new Swimmable() {
			public void swimming() {
				Demo04LambdaImpl.lambda$main$0();
			}
		});
	}
	private static void lambda$main$0() {
		System.out.println("Lambda表达式游泳");
	}
	public static void goSwimming(Swimmable swimmable) {
		swimmable.swimming();
	}
}

//Lambda在程序运行的时候形成一个类
//1. 在类中新增一个方法,这个方法的方法体就是Lambda表达式中的代码
//2. 还会形成一个匿名内部类,实现接口,重写抽象方法
//3. 在接口的重写方法中会调用新生成的方法.

Lambda省略格式

在Lambda标准格式的基础上,使用省略写法的规则为:

  1. 小括号内参数的类型可以省略
  2. 如果小括号内有且仅有一个参数,则小括号可以省略
  3. 如果大括号内有且仅有一个语句,可以同时省略大括号、return关键字及语句分号
//省略前
(int a) -> {
	return new Person();
}
//省略后
a -> new Person();

Lambda的前提条件

使用Lambda需要满足两个条件:

  1. 方法的参数或局部变量类型必须为接口才能使用Lambda
  2. 接口中有且仅有一个抽象方法
    否则使用的时候会报错

函数式接口

接口中有且仅有一个抽象方法的接口---函数时接口
在JDK1.8中引入了一个新的注解:@FunctionalInterface,可以标记在一个接口上表明是一个函数式接口,编译器将会强制检查该接口是否确实有且仅有一个抽象方法,否则将会报错。

Lambda和匿名内部类的对比

1. 所需的类型不一样
匿名内部类,需要的类型可以是类,抽象类,接口
Lambda表达式,需要的类型必须是接口
2. 抽象方法的数量不一样
匿名内部类所需的接口中抽象方法的数量随意
Lambda表达式所需的接口只能有一个抽象方法
3. 实现原理不同
匿名内部类是在编译后会形成class
Lambda表达式是在程序运行的时候动态生成class

本文作者:夜色哪里都是美

本文链接:https://www.cnblogs.com/evenIer/p/16411944.html

版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。

posted @   夜色哪里都是美  阅读(65)  评论(0编辑  收藏  举报
点击右上角即可分享
微信分享提示
评论
收藏
关注
推荐
深色
回顶
收起