zoukankan      html  css  js  c++  java
  • jdk8系列一、jdk8 Lamda表达式语法、接口的默认方法和静态方法、supplier用法

    一、简介

    毫无疑问,Java 8是Java自Java 5(发布于2004年)之后的最重要的版本。这个版本包含语言、编译器、库、工具和JVM等方面的十多个新特性。

    在本文中我们将学习这些新特性,并用实际的例子说明在什么场景下适合使用。

    包含Java开发者经常面对的几类问题:

    • 语言
    • 编译器
    • 工具
    • 运行时(JVM)

    二、Lambda表达式和函数式接口

      Lambda表达式(也称为闭包)是Java 8中最大和最令人期待的语言改变。它允许我们将函数当成参数传递给某个方法,或者把代码本身当作数据处理:函数式开发者非常熟悉这些概念。

      很多JVM平台上的语言(Groovy、Scala等)从诞生之日就支持Lambda表达式,但是Java开发者没有选择,只能使用匿名内部类代替Lambda表达式。

      Lambda的设计耗费了很多时间和很大的社区力量,最终找到一种折中的实现方案,可以实现简洁而紧凑的语言结构。

    1、Lamda表达式语法

    首先不看Lambda本身的写法,可以发现,对于i值的访问,在Lambda中已经不需要声明i为final了。

    其次,要明白一个重要的道理:Lambda要求实现的接口中只有一个方法,像上面的Runnable接口就只有一个run方法,如果一个接口中有多于一个方法,则不能写成Lambda的形式。

    最简单的Lambda表达式可由逗号分隔的参数列表、->符号和语句块组成,例如:

    Arrays.asList( "a", "b", "d" ).forEach( e -> System.out.println( e ) );

    在上面这个代码中的参数e的类型是由编译器推理得出的,你也可以显式指定该参数的类型,例如:

    Arrays.asList( "a", "b", "d" ).forEach( ( String e ) -> System.out.println( e ) );

    2、Lamda表达式语句块

    如果Lambda表达式需要更复杂的语句块,则可以使用花括号将该语句块括起来,类似于Java中的函数体,例如:

        public static void main(String[] args) {
            String separator = ",";
            Arrays.asList("a","b","c").forEach((String e) -> {
                System.out.print(e + separator);
                System.out.println(e);
            });
        }

    Lambda表达式可以引用类成员和局部变量(会将这些变量隐式得转换成final的),例如下列两个代码块的效果完全相同:

    String separator = ",";
    Arrays.asList( "a", "b", "d" ).forEach( 
        ( String e ) -> System.out.print( e + separator ) );

    final String separator = ",";
    Arrays.asList( "a", "b", "d" ).forEach( 
        ( String e ) -> System.out.print( e + separator ) );

    3、处理返回值

    Lambda表达式有返回值,返回值的类型也由编译器推理得出。如果Lambda表达式中的语句块只有一行,则可以不用使用return语句,下列两个代码片段效果相同:

    Arrays.asList( "a", "b", "d" ).sort( ( e1, e2 ) -> e1.compareTo( e2 ) );

    Arrays.asList( "a", "b", "d" ).sort( ( e1, e2 ) -> {
        int result = e1.compareTo( e2 );
        return result;
    } );

    4、函数接口

      Lambda的设计者们为了让现有的功能与Lambda表达式良好兼容,考虑了很多方法,于是产生了函数接口这个概念。函数接口指的是只有一个函数的接口,这样的接口可以隐式转换为Lambda表达式。java.lang.Runnablejava.util.concurrent.Callable是函数式接口的最佳例子。在实践中,函数式接口非常脆弱:只要某个开发者在该接口中添加一个函数,则该接口就不再是函数式接口进而导致编译失败。为了克服这种代码层面的脆弱性,并显式说明某个接口是函数式接口,Java 8 提供了一个特殊的注解@FunctionalInterface(Java 库中的所有相关接口都已经带有这个注解了),举个简单的函数式接口的定义:

    @FunctionalInterface
    public interface Functional {
        void method();
    }

    不过有一点需要注意,默认方法和静态方法不会破坏函数式接口的定义,因此如下的代码是合法的。

    @FunctionalInterface
    public interface FunctionalDefaultMethods {
        void method();
    
        default void defaultMethod() {            
        }        
    }

    更多可以参考官方文档

    三、接口的默认方法和静态方法

    1、允许接口有默认实现

      Java 8使用两个新概念扩展了接口的含义:默认方法和静态方法。默认方法使得接口有点类似traits,不过要实现的目标不一样。默认方法使得开发者可以在 不破坏二进制兼容性的前提下,往现存接口中添加新的方法,即不强制那些实现了该接口的类也同时实现这个新加的方法。

      默认方法和抽象方法之间的区别在于抽象方法需要实现,而默认方法不需要。接口提供的默认方法会被接口的实现类继承或者覆写,例子代码如下:

    public interface Animal {

    default String getName(){
    return "animal";
    }
    }
     private static class DefaultableImpl implements Defaulable {
    }

    private static class OverridableImpl implements Defaulable {
        @Override
        public String notRequired() {
            return "Overridden implementation";
        }
    }

    2、接口静态方法

      Dog接口使用关键字Animal定义了一个默认方法getName()Cat类实现了这个接口,同时默认继承了这个接口中的默认方法;Dog类也实现了这个接口,但覆写了该接口的默认方法,并提供了一个不同的实现。

    Java 8带来的另一个有趣的特性是在接口中可以定义静态方法,例子代码如下:

    private interface AnimalFactory {
    // Interfaces now allow static methods
    static Animal create( Supplier<Animal> supplier ) {
    return supplier.get();
    }
    }

    下面的代码片段整合了默认方法和静态方法的使用场景:

    Animal dog = AnimalFactory.create(Dog::new);
    Animal cat = AnimalFactory.create(Cat::new);
    System.out.println(dog.getName());
    System.out.println(cat.getName());

    这段代码的输出结果如下:

    dog
    animal

      由于JVM上的默认方法的实现在字节码层面提供了支持,因此效率非常高。默认方法允许在不打破现有继承体系的基础上改进接口。该特性在官方库中的应用是:给java.util.Collection接口添加新方法,如stream()parallelStream()forEach()removeIf()等等。

      尽管默认方法有这么多好处,但在实际开发中应该谨慎使用:在复杂的继承体系中,默认方法可能引起歧义和编译错误。如果你想了解更多细节,可以参考官方文档

    四、supplier用法

    supplier也是是用来创建对象的,但是不同于传统的创建对象语法:new,看下面代码:

    public class TestSupplier {
        private int age;
        
        TestSupplier(){
            System.out.println(age);
        }
        public static void main(String[] args) {
            //创建Supplier容器,声明为TestSupplier类型,此时并不会调用对象的构造方法,即不会创建对象
            Supplier<TestSupplier> sup= TestSupplier::new;
            System.out.println("--------");
            //调用get()方法,此时会调用对象的构造方法,即获得到真正对象
            sup.get();
            //每次get都会调用构造方法,即获取的对象不同
            sup.get();
        }
    }
  • 相关阅读:
    笔记:C/C++字符函数的使用
    学习游戏基础编程3:地图编辑器
    学习游戏基础编程2:Win32分割窗口
    学习游戏基础编程1:Win32自定义控件
    [WebServer] Tomcat 配置访问限制:访问白名单和访问黑名单
    [WebServer] Windows操作系统下 Tomcat 服务器运行 PHP 的环境配置
    XSLT函数集合:数值函数、字符串函、节点集函数和布尔函数
    腾讯的一道JavaScript面试题
    【转】AES 进一步的研究
    MQTT-Client-FrameWork使用整理
  • 原文地址:https://www.cnblogs.com/wangzhuxing/p/10204436.html
Copyright © 2011-2022 走看看