您的当前位置:首页正文

Lambda表达式原理及示例

2022-01-28 来源:步旅网
Lambda表达式原理及⽰例

Lambda表达式

  Lambda 表达式,也可称为闭包,它是推动 Java 8 发布的最重要新特性。Lambda 允许把函数作为⼀个⽅法的参数(函数作为参数传递进⽅法中)。使⽤ Lambda 表达式可以使代码变的更加简洁紧凑。

1. 需求分析

   创建⼀个新的线程,指定线程要执⾏的任务

public static void main(String[] args) { // 开启⼀个新的线程

new Thread(new Runnable() { @Override

public void run() {

System.out.println(\"新线程中执⾏的代码 : \"+Thread.currentThread().getName()); }

}).start();

System.out.println(\"主线程中的代码:\" + Thread.currentThread().getName()); }

代码分析:

1. Thread类需要⼀个Runnable接⼝作为参数,其中的抽象⽅法run⽅法是⽤来指定线程任务内容的核⼼2. 为了指定run⽅法体,不得不需要Runnable的实现类

3. 为了省去定义⼀个Runnable 的实现类,不得不使⽤匿名内部类

4. 必须覆盖重写抽象的run⽅法,所有的⽅法名称,⽅法参数,⽅法返回值不得不都重写⼀遍,⽽且不能出错,5. ⽽实际上,我们只在乎⽅法体中的代码

2.Lambda表达式初体验

  Lambda表达式是⼀个匿名函数,可以理解为⼀段可以传递的代码

new Thread(() -> { System.out.println(\"新线程Lambda表达式...\" +Thread.currentThread().getName()); }).start();

Lambda表达式的优点:简化了匿名内部类的使⽤,语法更加简单。

  匿名内部类语法冗余,体验了Lambda表达式后,发现Lambda表达式是简化匿名内部类的⼀种⽅式。

3. Lambda的语法规则

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

(参数类型 参数名称) -> {代码体;}格式说明:

(参数类型 参数名称):参数列表{代码体;} :⽅法体

-> : 箭头,分割参数列表和⽅法体

3.1 Lambda练习1

  练习⽆参⽆返回值的Lambda定义⼀个接⼝

public interface UserService { void show();}

然后创建主⽅法使⽤

public class Demo03Lambda {

public static void main(String[] args) { goShow(new UserService() { @Override

public void show() {

System.out.println(\"show ⽅法执⾏了...\"); } });

System.out.println(\"----------\");

goShow(() -> { System.out.println(\"Lambda show ⽅法执⾏了...\"); }); }

public static void goShow(UserService userService){ userService.show(); }}

输出:

show ⽅法执⾏了...----------Lambda show ⽅法执⾏了...

3.2 Lambda练习2

   完成⼀个有参且有返回值得Lambda表达式案例创建⼀个Person对象

@Data

@AllArgsConstructor@NoArgsConstructorpublic class Person { private String name; private Integer age; private Integer height;}

  然后我们在List集合中保存多个Person对象,然后对这些对象做根据age排序操作

public static void main(String[] args) { List list = new ArrayList<>(); list.add(new Person(\"周杰伦\ list.add(new Person(\"刘德华\ list.add(new Person(\"周星驰\ list.add(new Person(\"郭富城\

Collections.sort(list, new Comparator() { @Override

public int compare(Person o1, Person o2) { return o1.getAge()-o2.getAge(); } });

for (Person person : list) { System.out.println(person); } }

  我们发现在sort⽅法的第⼆个参数是⼀个Comparator接⼝的匿名内部类,且执⾏的⽅法有参数和返回值,那么我们可以改写为Lambda表达式

public static void main(String[] args) { List list = new ArrayList<>(); list.add(new Person(\"周杰伦\ list.add(new Person(\"刘德华\

list.add(new Person(\"周星驰\ list.add(new Person(\"郭富城\

/*Collections.sort(list, new Comparator() { @Override

public int compare(Person o1, Person o2) { return o1.getAge()-o2.getAge(); } });

for (Person person : list) { System.out.println(person); }*/

System.out.println(\"------\");

Collections.sort(list,(Person o1,Person o2) -> { return o1.getAge() - o2.getAge(); });

for (Person person : list) { System.out.println(person); } }

输出结果

Person(name=郭富城, age=23, height=170)Person(name=周杰伦, age=33, height=175)Person(name=周星驰, age=38, height=177)Person(name=刘德华, age=43, height=185)

4. @FunctionalInterface注解

  @FunctionalInterface是JDK8中新增加的⼀个函数式注解,表⽰该注解修饰的接⼝只能有⼀个抽象⽅法。

/**

* @FunctionalInterface

* 这是⼀个函数式注解,被该注解修饰的接⼝只能声明⼀个抽象⽅法 */

@FunctionalInterface

public interface UserService { void show();}

5. Lambda表达式的原理

  匿名内部类的本质是在编译时⽣成⼀个Class ⽂件。XXXXX$1.class

public class Demo01Lambda {

public static void main(String[] args) { // 开启⼀个新的线程

new Thread(new Runnable() { @Override

public void run() {

System.out.println(\"新线程中执⾏的代码 : \"+Thread.currentThread().getName()); }

}).start();

System.out.println(\"主线程中的代码:\" + Thread.currentThread().getName()); System.out.println(\"---------------\");

/*new Thread(() -> { System.out.println(\"新线程Lambda表达式...\" +Thread.currentThread().getName()); }) .start();*/ }}

  还可以通过反编译⼯具来查看⽣成的代码 XJad ⼯具来查看

static class Demo01Lambda$1 implements Runnable

{

public void run() {

System.out.println((new StringBuilder()).append(\"新线程中执⾏的代码 : \" ).append(Thread.currentThread().getName()).toString()); }

Demo01Lambda$1() { }}

  那么Lambda表达式的原理是什么呢?我们也通过反编译⼯具来查看

  

写的有Lambda表达式的class⽂件,我们通过XJad查看报错。这时我们可以通过JDK⾃带的⼀个⼯具:javap 对字节码进⾏反汇编操作。

javap -c -p ⽂件名.class

-c:表⽰对代码进⾏反汇编-p:显⽰所有的类和成员反汇编的结果:

E:\\workspace\\OpenClassWorkSpace\\JDK8Demo\arget\\classes\\com\\bobo\\jdk\\lambda>javap -c -p Demo03Lambda.classCompiled from \"Demo03Lambda.java\"

public class com.bobo.jdk.lambda.Demo03Lambda { public com.bobo.jdk.lambda.Demo03Lambda(); Code:

0: aload_0

1: invokespecial #1 // Method java/lang/Object.\"\":()V 4: return

public static void main(java.lang.String[]); Code:

0: invokedynamic #2, 0 // InvokeDynamic #0:show:()Lcom/bobo/jdk/lambda/service/UserService; 5: invokestatic #3 // Method goShow:(Lcom/bobo/jdk/lambda/service/UserService;)V 8: return

public static void goShow(com.bobo.jdk.lambda.service.UserService); Code:

0: aload_0

1: invokeinterface #4, 1 // InterfaceMethod com/bobo/jdk/lambda/service/UserService.show:()V 6: return

private static void lambda$main$0(); Code:

0: getstatic #5 // Field java/lang/System.out:Ljava/io/PrintStream; 3: ldc #6 // String Lambda show ⽅法执⾏了...

5: invokevirtual #7 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 8: return}

  在这个反编译的源码中我们看到了⼀个静态⽅法 lambda$main$0(),这个⽅法⾥⾯做了什么事情呢?我们通过debug的⽅式来查看下:

上⾯的效果可以理解为如下:

public class Demo03Lambda {

public static void main(String[] args) { .... }

private static void lambda$main$0();

System.out.println(\"Lambda show ⽅法执⾏了...\"); }}

为了更加直观的理解这个内容,我们可以在运⾏的时候添加 -Djdk.internal.lambda.dumpProxyClasses, 加上这个参数会将内部class码输出到⼀个⽂件中

java -Djdk.internal.lambda.dumpProxyClasses 要运⾏的包名.类名

命令执⾏

E:\\workspace\\OpenClassWorkSpace\\JDK8Demo\arget\\classes>java -Djdk.internal.lambda.dumpProxyClassescom.bobo.jdk.lambda.Demo03LambdaLambda show ⽅法执⾏了...反编译后的内容:

  可以看到这个匿名的内部类实现了UserService接⼝,并重写了show()⽅法。在show⽅法中调⽤了Demo03Lambda.lambda$main$0(),也就是调⽤了Lambda中的内容。

public class Demo03Lambda {

public static void main(String[] args) { goShow(new UserService() { @Override

public void show() {

Demo03Lambda.lambda$main$0(); } });

System.out.println(\"----------\"); }

public static void goShow(UserService userService){ userService.show(); }

private static void lambda$main$0();

System.out.println(\"Lambda show ⽅法执⾏了...\"); }}

⼩结:

匿名内部类在编译的时候会产⽣⼀个class⽂件。Lambda表达式在程序运⾏的时候会形成⼀个类。

1. 在类中新增了⼀个⽅法,这个⽅法的⽅法体就是Lambda表达式中的代码2. 还会形成⼀个匿名内部类,实现接⼝,重写抽象⽅法3. 在接⼝中重写⽅法会调⽤新⽣成的⽅法

6.Lambda表达式的省略写法

在lambda表达式的标准写法基础上,可以使⽤省略写法的规则为:

1. ⼩括号内的参数类型可以省略

2. 如果⼩括号内有且仅有⼀个参数,则⼩括号可以省略

3. 如果⼤括号内有且仅有⼀个语句,可以同时省略⼤括号,return 关键字及语句分号。

public class Demo05Lambda {

public static void main(String[] args) { goStudent((String name,Integer age)->{ return name+age+\" 6666 ...\"; });

// 省略写法

goStudent((name,age)-> name+age+\" 6666 ...\"); System.out.println(\"------\"); goOrder((String name)->{

System.out.println(\"--->\" + name); return 666;

});

// 省略写法

goOrder(name -> {

System.out.println(\"--->\" + name); return 666; });

goOrder(name -> 666); }

public static void goStudent(StudentService studentService){ studentService.show(\"张三\ }

public static void goOrder(OrderService orderService){ orderService.show(\"李四\"); } }

7.Lambda表达式的使⽤前提

  Lambda表达式的语法是⾮常简洁的,但是Lambda表达式不是随便使⽤的,使⽤时有⼏个条件要特别注意1. ⽅法的参数或局部变量类型必须为接⼝才能使⽤Lambda2. 接⼝中有且仅有⼀个抽象⽅法(@FunctionalInterface)

8.Lambda和匿名内部类的对⽐

  Lambda和匿名内部类的对⽐ 1.所需类型不⼀样

匿名内部类的类型可以是 类,抽象类,接⼝Lambda表达式需要的类型必须是接⼝ 2.抽象⽅法的数量不⼀样

匿名内部类所需的接⼝中的抽象⽅法的数量是随意的Lambda表达式所需的接⼝中只能有⼀个抽象⽅法 3.实现原理不⼀样

匿名内部类是在编译后形成⼀个class

Lambda表达式是在程序运⾏的时候动态⽣成class

到此这篇关于Lambda表达式原理及⽰例的⽂章就介绍到这了,更多相关Lambda表达式内容请搜索以前的⽂章或继续浏览下⾯的相关⽂章希望⼤家以后多多⽀持!

因篇幅问题不能全部显示,请点此查看更多更全内容