JDK8新特性 Lambda表达式

一、接口的默认方法
二、Lambda 表达式
三、函数式接口
四、方法与构造函数引用
五、Lambda 作用域
六、访问局部变量
七、访问对象字段与静态变量
八、访问接口的默认方法
九、Date API
十、Annotation 注解:支持多重注解

一、接口的默认方法

Java8允许我们给接口添加一个非抽象的方法实现,只需要使用 default关键字即可,这个特征又叫做扩展方法,示例如下:

[java] view plain copy

  1. public interface Formula {
  2. double calculate(int a);
  3. // jdk8能给接口添加一个非抽象的方法实现
  4. default double sqrt(int a){
  5. return Math.sqrt(a);
  6. }
  7. }

Formula接口在拥有calculate方法之外同时还定义了sqrt方法,实现了Formula接口的子类只需要实现一个calculate方法,默认方法sqrt将在子类上可以直接使用。

[java] view plain copy

  1. @Test
  2. public void test1() {
  3. Formula formula = new Formula() {
  4. //这里只实现了一个方法,没有实现用default修饰的方法
  5. @Override
  6. public double calculate(int a) {
  7. return sqrt(a * 100);
  8. }
  9. };
  10. System.out.println(formula.calculate(100));  //100.0
  11. System.out.println(formula.sqrt(100));  //10.0
  12. }

1).如果一个类实现两个接口,这两个接口同时有相同的抽象方法,在类中只需要重写一次这个方法。
    2).如果接口中有default修饰的方法不需要重写。
    3).如果两个接口里的方法名相同都是default方法,里面的方法体不同,在类中需要重写该方法。
    4).如果两个接口中方法名,参数都相同的方法,一个接口是抽象方法,另一个是default修饰有方法体。这是该类也必须重写该方法。
    5).Lambda表达式中是无法访问到默认方法的

二、Lambda 表达式

[java] view plain copy

  1. @Test
  2. public void test2() {
  3. // 如果用Lambda表达式,一定要写明泛型
  4. List<String> list = Arrays.asList("peter","anna","make");
  5. // ①.老版本的Java中是这样排列字符串的
  6. Collections.sort(list, new Comparator<String>() {
  7. @Override
  8. public int compare(String a, String b) {
  9. return a.compareTo(b);
  10. }
  11. });
  12. // ②.Java 8提供了更简洁的语法,lambda表达式:
  13. /*
  14. Collections.sort(list ,(String a,String b) -> {
  15. return a.compareTo(b);
  16. });
  17. // ③.还可以写得更短
  18. Collections.sort(list, (String a, String b) -> a.compareTo(b));
  19. // ④.还可以这么写
  20. Collections.sort(list, String::compareTo);
  21. System.out.println(Collections.singletonList(list));  //[[anna, make, peter]]
  22. }

1. 什么是λ表达式
λ表达式本质上是一个匿名方法。让我们来看下面这个例子:
    public int add(int x, int y) {
        return x + y;
    }
转成λ表达式后是这个样子:
    (int x, int y) -> x + y;
参数类型也可以省略,Java编译器会根据上下文推断出来:
    (x, y) -> x + y; //返回两数之和
或者
    (x, y) -> { return x + y; } //显式指明返回值
可见λ表达式有三部分组成:参数列表,箭头(->),以及一个表达式或语句块。
下面这个例子里的λ表达式没有参数,也没有返回值(相当于一个方法接受0个参数,返回void,其实就是Runnable里run方法的一个实现):
    () -> { System.out.println("Hello Lambda!"); }
如果只有一个参数且可以被Java推断出类型,那么参数列表的括号也可以省略:
    c -> { return c.size(); }

lambda包含3个部分:
(1)括弧包起来的参数
(2)一个箭头
(3)方法体,可以是单个语句,也可以是语句块
参数可以写类型,也可以不写,jvm很智能的,它能自己推算出来
方法可以有返回,也可以无返回,如果有多个语句,还要返回值,需要加上return 。
一个很有意思的事情:
之前我们说Object是一切类的父类,然而在加入了lambda以后,这种大一统的局面将不复存在:

[java] view plain copy

  1. @Test
  2. public void test33() {
  3. Object r = ()->System.out.println("hello,lambda");
  4. }

编译报错:incompatible types: Object is not a functional interface
    很显然,编译器会检查变量的引用类型里面是否真的是一个函数式接口。那么如何让这段代码通过编译呢?只需要加一个强制类型转换就可以了:

[java] view plain copy

  1. @Test
  2. public void test33() {
  3. Object r = (Runnable)()->System.out.println("hello,lambda");
  4. }

如果你认为lambda表达式仅仅是为了从语法上简化匿名内部类,那就太小看jdk8的lambda了!
lambda的定义:
 (1)lambda是方法的实现
 (2)lambda是延迟执行的

[java] view plain copy

  1. public static void main(String[] args) {
  2. // 正常实现
  3. Runnable runnable = new Runnable() {
  4. @Override
  5. public void run() {
  6. System.out.println("hello lambda!");
  7. }
  8. };
  9. runnable.run();
  10. // 用lambda实现如下(如果run方法体里只有一行数据可以省去{)
  11. Runnable runnable1 = () -> {
  12. System.out.println("hello lambda2!");
  13. System.out.println("2");
  14. };
  15. runnable1.run();
  16. }

lambda是如何做到的呢?可以反编译后查看字节码。(里面有一个叫做invokedynamic的指令。invokedynamic是从jdk7开始引入的,jdk8开始落地。)可以看出来lambda并不是语法糖,它不是像匿名内部类那样生成那种带有$的匿名类。简单的说,这里只是定义了一个方法调用点,具体调用那个方法要到运行时才能决定,这就是前面所说的:延迟执行。具体的细节请google:invokedynamic。
    为了配合lambda,jdk8引入了一个新的定义叫做:函数式接口(Functional interfaces)

三、函数式接口

(1)是一个接口
(2)只有一个待实现的方法 !!!!
    因为jdk8开始,接口可以有default方法,所以,函数式接口也是可以有default方法的,但是,只能有一个未实现的方法。
    与此对应,新引入了一个注解: @FunctionalInterface。这个注解只是起文档的作用,说明这个接口是函数式接口,编译器并不会使用这个注解来决定一个接口是不是函数式接口。不管加不加@FunctionalInterface这个注解,下面的接口都是函数式接口:
interface Something {
  public String doit(Integer i);
}

Lambda表达式是如何在Java的类型系统中表示的呢?每一个lambda表达式都对应一个类型,通常是接口类型。而“函数式接口”是指仅仅只包含一个抽象方法的接口,每一个该类型的lambda表达式都会被匹配到这个抽象方法。因为默认方法 不算抽象方法,所以你也可以给你的函数式接口添加默认方法。
    我们可以将lambda表达式当作任意只包含一个抽象方法的接口类型,确保你的接口一定达到这个要求,你只需要给你的接口添加 @FunctionalInterface 注解,编译器如果发现你标注了这个注解的接口有多于一个抽象方法的时候会报错的。

[java] view plain copy

  1. @FunctionalInterface
  2. public interface Converter<E,T> {
  3. // 如将字条串转为int类型
  4. T convert(E str);
  5. // 函数式接口只能有 一个 抽象方法
  6. //    T convert2(E str, E sre);
  7. }

[java] view plain copy

  1. @Test
  2. public void test3() {
  3. Converter<String,Integer> converter = (str) ->Integer.valueOf(str);
  4. //上面还可以通过静态方法引用来表示:
  5. //        Converter<String,Integer> converter = Integer::valueOf;
  6. Integer integer = converter.convert("123");
  7. System.out.println(integer);
  8. }

需要注意如果@FunctionalInterface如果没有指定,上面的代码也是对的。
    译者注 将lambda表达式映射到一个单方法的接口上,这种做法在Java 8之前就有别的语言实现,比如Rhino JavaScript解释器,

五、Lambda 作用域

在lambda表达式中访问外层作用域和老版本的匿名对象中的方式很相似。你可以直接访问标记了final的外层局部变量,或者实例的字段以及静态变量。

[java] view plain copy

  1. public class InnerClassExamples {
  2. public static void main(String... args) {
  3. Hello h = new Hello();
  4. h.r.run();
  5. }
  6. }
  7. class Hello {
  8. public Runnable r = new Runnable() {
  9. public void run() {
  10. // 这里的this指的是匿名类,而非Hello类。
  11. System.out.println("-->1 "+this);
  12. System.out.println("-->2 "+toString());
  13. // 想要引用Hello类需要Hello.this这样!!!
  14. System.out.println("++1  "+Hello.this);
  15. System.out.println("++2  "+Hello.this.toString());
  16. }
  17. };
  18. public String toString() {
  19. return "Hello‘s custom toString()";
  20. }
  21. }
-->1 [email protected]
-->2 [email protected]
++1  Hello‘s custom toString()
++2  Hello‘s custom toString()

System.out.println(this);这里的this指的是匿名类,而非Hello类。
    想要引用Hello类需要Hello.this这样:System.out.println(Hello.this);下面我们就来看一下伟大的lambda是什么样子的:

[java] view plain copy

  1. public class Test7{
  2. public static void main(String args[]){
  3. Hello2 h = new Hello2();
  4. h.r.run();
  5. }
  6. }
  7. class Hello2{
  8. public Runnable r = () -> {
  9. System.out.println(this);
  10. System.out.println(toString());
  11. };
  12. public String toString() {
  13. return "Hello‘s custom toString()";
  14. }
  15. }
Hello‘s custom toString()
Hello‘s custom toString()

六、访问局部变量

匿名内部类只能引用作用域外面的final的变量,在lambda中对这个限制做了削弱,只需要是“等价final”就可以,没必要用final关键字来标识。
    我们可以直接在lambda表达式中访问外层的局部变量:

[java] view plain copy

  1. public static void main(String args[]){
  2. String message = "Howdy, world!";//不需要是final的
  3. Runnable runnable = () -> System.out.println(message);
  4. runnable.run();
  5. }

“等效final”的意思是:事实上的final,所以,一旦赋值也是不可以改变的!比如

[java] view plain copy

  1. public static void main(String args[]){
  2. String message = "Howdy, world!";//不需要是final的
  3. Runnable runnable = () -> System.out.println(message);
  4. runnable.run();
  5. message = "change it";
  6. }

error: local variables referenced from a lambda expression must be final or effectively final

七、方法引用与构造函数引用

真正的大杀器出现了:
    现在我们要把多个Student对象进行排序,有时候是按照firstName来排,有时候是按照lastName或者是age来排,使用lambda可以这样来做:

[java] view plain copy

  1. public class Student {
  2. public String firstName;
  3. public String lastName;
  4. public int age;
  5. public Student (String firstName, String lastName, int age){
  6. this.firstName = firstName;
  7. this.lastName = lastName;
  8. this.age = age;
  9. }
  10. public String toString(){
  11. return firstName+","+lastName+","+age;
  12. }
  13. }

[java] view plain copy

  1. @Test
  2. public void test12() {
  3. Student[] students = new Student[]{
  4. new Student("Ted", "Neward", 41),
  5. new Student("Charlotte", "Neward", 41),
  6. new Student("Michael", "Neward", 19),
  7. new Student("Matthew", "Neward", 13)
  8. };
  9. //sort by firstName
  10. Arrays.sort(students, (lhs,rhs)->lhs.firstName.compareTo(rhs.firstName));
  11. System.out.println(Arrays.asList(students));
  12. }

我们可以把Comparator抽取出来,变成是Student对象的成员变量:

[java] view plain copy

  1. public class Student {
  2. public String firstName;
  3. public String lastName;
  4. public int age;
  5. public Student (String firstName, String lastName, int age){
  6. this.firstName = firstName;
  7. this.lastName = lastName;
  8. this.age = age;
  9. }
  10. public final static Comparator<Student> compareFirstName =
  11. (lhs, rhs) -> lhs.firstName.compareTo(rhs.firstName);
  12. public final static Comparator<Student> compareLastName =
  13. (lhs, rhs) -> lhs.lastName.compareTo(rhs.lastName);
  14. public final static Comparator<Student> compareAge =
  15. (lhs, rhs) -> lhs.age - rhs.age;
  16. public String toString(){
  17. return firstName+","+lastName+","+age;
  18. }
  19. }

[java] view plain copy

  1. @Test
  2. public void test12() {
  3. Student[] students = new Student[]{
  4. new Student("Ted", "Neward", 41),
  5. new Student("Charlotte", "Neward", 41),
  6. new Student("Michael", "Neward", 19),
  7. new Student("Matthew", "Neward", 13)
  8. };
  9. //sort by firstName
  10. Arrays.sort(students, (lhs,rhs)->lhs.firstName.compareTo(rhs.firstName));
  11. Arrays.sort(students, Student.compareFirstName);  //这里直接引用lambda
  12. System.out.println(Arrays.asList(students));
  13. }

能起到同样的作用,但是语法看上去很奇怪,因为之前我们都是创建一个满足Comparator签名的方法,然后直接调用,而非定义一个变量,
    然后引用这个变量!所以,还有这么一种调用方法: -----------------------------------

[java] view plain copy

  1. public class Student {
  2. public String firstName;
  3. public String lastName;
  4. public int age;
  5. public Student (String firstName, String lastName, int age){
  6. this.firstName = firstName;
  7. this.lastName = lastName;
  8. this.age = age;
  9. }
  10. public static int compareFirstName(Student lhs, Student rhs){
  11. return lhs.firstName.compareTo(rhs.firstName);
  12. }
  13. public static int compareLastName(Student lhs, Student rhs){
  14. return lhs.lastName.compareTo(rhs.lastName);
  15. }
  16. public String toString(){
  17. return firstName+","+lastName+","+age;
  18. }
  19. }

[java] view plain copy

  1. @Test
  2. public void test12() {
  3. Student[] students = new Student[]{
  4. new Student("Ted", "Neward", 41),
  5. new Student("Charlotte", "Neward", 41),
  6. new Student("Michael", "Neward", 19),
  7. new Student("Matthew", "Neward", 13)
  8. };
  9. //sort by firstName
  10. Arrays.sort(students, Student::compareFirstName);
  11. System.out.println(Arrays.asList(students));
  12. }

看Student::compareFirstName这种调用方式,
如果是static方法使用:类名::方法名
如果是instance方法:instance::方法名

但是,上面的代码还是不是很美观,因为Student只是一个数据对象,它不应该的对外提供compareFirstName或者是compareLastName这样的方法,我们需要的仅仅是根据某个字段排序而已!很幸运的是jdk的api帮我们做了这件事:

[java] view plain copy

  1. public class Student {
  2. public String firstName;
  3. public String lastName;
  4. public int age;
  5. public Student (String firstName, String lastName, int age){
  6. this.firstName = firstName;
  7. this.lastName = lastName;
  8. this.age = age;
  9. }
  10. // get方法
  11. public String getFirstName() {
  12. return firstName;
  13. }
  14. public String getLastName() {
  15. return lastName;
  16. }
  17. public String toString(){
  18. return firstName+","+lastName+","+age;
  19. }
  20. }

[java] view plain copy

  1. @Test
  2. public void test12() {
  3. Student[] students = new Student[]{
  4. new Student("Ted", "Neward", 41),
  5. new Student("Charlotte", "Neward", 41),
  6. new Student("Michael", "Neward", 19),
  7. new Student("Matthew", "Neward", 13)
  8. };
  9. //sort by firstName
  10. Arrays.sort(students, Comparator.comparing(Student::getFirstName));
  11. System.out.println(Arrays.asList(students));
  12. }

注意这里的Person::getFirstName,很显然getFirstName()并不是static的,这是jdk做了封装的缘故!
    假如我们的排序算法改为:先按照lastName,然后按照age排序呢?

[java] view plain copy

  1. @Test
  2. public void test12() {
  3. Student[] students = new Student[]{
  4. new Student("Ted", "Neward", 41),
  5. new Student("Charlotte", "Neward", 41),
  6. new Student("Michael", "Neward", 19),
  7. new Student("Matthew", "Neward", 13)
  8. };
  9. //sort by firstName
  10. /*        Collections.sort(Arrays.asList(students), (lhs, rhs)->{
  11. if(lhs.getLastName().equals(rhs.getLastName())){
  12. return lhs.getAge()-rhs.getAge();
  13. }else{
  14. return lhs.getLastName().compareTo(rhs.getLastName());
  15. }
  16. });
  17. */
  18. // 上面可以改为这样子
  19. Collections.sort(Arrays.asList(students),
  20. Comparator.comparing(Student::getLastName).thenComparing(Student::getAge));
  21. System.out.println(Arrays.asList(students));
  22. }

还有更多的诸如:andThen()这样的方法,可以查api。
构造函数引用

[java] view plain copy

  1. public class Person {
  2. String firstName;
  3. String lastName;
  4. public Person() {}
  5. public Person(String firstName, String lastName) {
  6. this.firstName = firstName;
  7. this.lastName = lastName;
  8. }
  9. }

接下来我们指定一个用来创建Person对象的对象工厂接口:

[java] view plain copy

  1. public interface PersonFactory<P extends Person> {
  2. P create(String firstName, String lastName);
  3. }

这里我们使用构造函数引用来将他们关联起来,而不是实现一个完整的工厂:

[java] view plain copy

  1. @Test
  2. public void test4() {
  3. //我们只需要使用 Person::new 来获取Person类构造函数的引用,
  4. // Java编译器会自动根据PersonFactory.create方法的签名来选择合适的构造函数。
  5. PersonFactory<Person> personFactory = Person::new;
  6. Person person = personFactory.create("Peter", "Parker");
  7. }

我们只需要使用 Person::new 来获取Person类构造函数的引用,Java编译器会自动根据PersonFactory.create方法的签名来选择合适的构造函数。

八、访问对象字段与静态变量

和本地变量不同的是,lambda内部对于实例的字段以及静态变量是即可读又可写。该行为和匿名对象是一致的:

[plain] view plain copy

  1. class Lambda4 {
  2. static int outerStaticNum;
  3. int outerNum;
  4. void testScopes() {
  5. Converter<Integer, String> stringConverter1 = (from) -> {
  6. outerNum = 23;
  7. return String.valueOf(from);
  8. };
  9. Converter<Integer, String> stringConverter2 = (from) -> {
  10. outerStaticNum = 72;
  11. return String.valueOf(from);
  12. };
  13. }
  14. }
时间: 2024-08-24 13:59:07

JDK8新特性 Lambda表达式的相关文章

Java 8 新特性 - Lambda表达式(一)

链接 Java8新特性——Lambda表达式(一) 原文地址:https://www.cnblogs.com/tonyq/p/8206131.html

JDK8的新特性——Lambda表达式

JDK8已经发布快4年的时间了,现在来谈它的新特性显得略微的有点“不合时宜”.尽管JDK8已不再“新”,但它的重要特性之一——Lambda表达式依然是不被大部分开发者所熟练运用,甚至不被开发者所熟知. 国内的开发环境大家都知道,有各种的老项目,有各种各样的发布风险,让公司以及项目组对新的技术往往望而却步,有公司甚至时至今日还在使用JDK6来进行项目开发,这导致了在很多技术的选择上受到了很大限制,进而不能跟随时代的脚步使得项目甚至公司一步一步走向衰落. 本文简单认识JDK8的重要新特性之一——La

Java核心技术之Java8新特性-Lambda表达式

1 总体说明 Java8新特性概述 函数式接口 Lambda表达式(闭包) 2 Java8新特性概述 Oracle公司于2014年3月发布了Java8正式版,该版本是自JDK5.0以来最具革命性的版本. Java8为Java语言.编译器.类库和JVM带来了大量的新特性.接下来的内容将会详细说明Java8在Java语言方面的新特性以及它们的使用场景. 3 函数式接口 Java8引入的一个核心概念是函数式接口(Functional Interfaces):如果一个接口定义一个唯一的抽象方法,那么这个

java8新特性——Lambda表达式

上文中简单介绍了一下java8得一些新特性,与优点,也是为本次学习java8新特性制定一个学习的方向,后面几篇会根据上文中得新特性一一展开学习.本文就从java8新特性中比较重要的Lambda表达式开始学学习. 一.为什么要使用Lambda表达式 Lambda是一个匿名函数,我们可以baLambda表达式理解为是一段可以传递的代码(将代码像数据一样进行传递).可以写出更简洁,更灵活的代码.作为一种更紧凑得代码风格,使得java得语言表达能力得到提升.Lambda表达式需要函数式接口的支持,接口中

Java8新特性 - Lambda 表达式

 是什么? Lambda 表达式,也可称为闭包,它是推动 Java 8 发布的最重要新特性. Lambda 属于函数式编程思想,允许把函数作为一个方法的参数(函数作为参数传递进方法中). 怎么使用? 使用前提: 必须支持上下文推导,要能够推导出来 Lambda 表达式表示的是哪个接口中的内容. 可以使用接口当做参数,然后传递 Lambda 表达式(常用),也可以将 Lambda 表达式赋值给一个接口类型的变量. Lambda 表达式的省略规则: 小括号中的参数类型可以省略. 如果小括号中只有一个

Java基础知识总结之1.8新特性lambda表达式

函数式接口 函数式接口(functional interface 也叫功能性接口,其实是同一个东西).简单来说,函数式接口是只包含一个方法的接口.比如Java标准库中的java.lang.Runnable和 java.util.Comparator都是典型的函数式接口.java 8提供 @FunctionalInterface作为注解,这个注解是非必须的,只要接口符合函数式接口的标准(即只包含一个方法的接口),虚拟机会自动判断, 但 最好在接口上使用注解@FunctionalInterface进

jdk1.8新特性Lambda表达式方法引用

前言 在之前我们接触了JDK1.8引入的新特新lambda表达式没在某种程度上,它可以简化我们的代码,帮助我们快速的编写代码,但在这其中我们之前的编写方式并不是lambda表达式最简洁的方式,而在头屑情况下我们可以使用lambda表达式的方法引用是代码进一步简洁化. 一.方法引用: 在java中方法引用主要是用来替代lambda表达式进一步简化代码,方法引用符号的写法是两个冒号“::”,其中方法引用的用法有一下几种. 1.对象名称::成员方法 如果一个对象中有一个成员方法,就好就是lambda表

java8新特性-Lambda表达式(二)

Java8新增了java.util.funcion包,里面包含常用的函数接口,这是Lambda表达式的基础,Java集合框架也新增部分接口,以便与Lambda表达式对接. Collections中的常用函数接口 Java集合框架的接口继承结构: 上图中蓝色标记和橙色标记的接口类,表示在Java8中加入了新的接口方法,由于继承关系,他们相应的子类也会继承这些方法. 下面用一张表列举这些方法 接口名 Java8新加入的方法 Collection removeIf() spliterator() st

Java8新特性-Lambda表达式

1.  什么是Lambda表达式? Lambda表达式就是可以把函数作为参数传递,或者说把代码作为数据传递给函数. 2. Lambda表达式的语法格式 基本语法格式如下: 基本语法下多个变体的说明: 1). 多个参数中间用逗号分隔: 1 (int m,int n)=>{int result=m*n; Console.WriteLine(result);} 2). 参数类型可以省略: 1 (m,n)=>{int result=m*n; Console.WriteLine(result);} 3)