zoukankan      html  css  js  c++  java
  • Java8新特性

    Java8新特性

    Java8主要的新特性涵盖:函数式接口、Lambda 表达式、集合的流式操作、注解的更新、安全性的增强、IONIO 的改进、完善的全球化功能等。

    1、函数式接口

        Java 8 引入的一个核心概念是函数式接口(Functional Interfaces)。通过在接口里面添加一个抽象方法,这些方法可以直接从接口中运行。如果一个接口定义个唯一一个抽象方法,那么这个接口就成为函数式接口。同时,引入了一个新的注解:@FunctionalInterface。可以把他它放在一个接口前,表示这个接口是一个函数式接口。这个注解是非必须的,只要接口只包含一个方法的接口,虚拟机会自动判断,不过最好在接口上使用注解 @FunctionalInterface 进行声明。在接口中添加了 @FunctionalInterface 的接口,只允许有一个抽象方法,否则编译器也会报错。

    java.lang.Runnable 就是一个函数式接口。

    @FunctionalInterface
    public interface Runnable {
    public abstract void run();
    }

    2、Lambda 表达式

            函数式接口的重要属性是:我们能够使用 Lambda 实例化它们,Lambda 表达式让你能够将函数作为方法参数,或者将代码作为数据对待。Lambda 表达式的引入给开发者带来了不少优点:在 Java 8 之前,匿名内部类,监听器和事件处理器的使用都显得很冗长,代码可读性很差,Lambda 表达式的应用则使代码变得更加紧凑,可读性增强;Lambda 表达式使并行操作大集合变得很方便,可以充分发挥多核 CPU 的优势,更易于为多核处理器编写代码;

    Lambda 表达式由三个部分组成:第一部分为一个括号内用逗号分隔的形式参数,参数是函数式接口里面方法的参数;第二部分为一个箭头符号:->;第三部分为方法体,可以是表达式和代码块。语法如下:

    1. 方法体为表达式,该表达式的值作为返回值返回。

    (parameters) -> expression

    2. 方法体为代码块,必须用 {} 来包裹起来,且需要一个 return 返回值,但若函数式接口里面方法返回值是 void,则无需返回值。

    (parameters) -> { statements; }
    例如,下面是使用匿名内部类和 Lambda 表达式的代码比较。

    下面是用匿名内部类的代码:

    button.addActionListener(new ActionListener() {
    @Override
    public void actionPerformed(ActionEvent e) {
    System.out.print("Helllo Lambda in actionPerformed");
    }
    });
    下面是使用 Lambda 表达式后:
    button.addActionListener(
    \actionPerformed 有一个参数 e 传入,所以用 (ActionEvent e)
    (ActionEvent e)-> 
    System.out.print("Helllo Lambda in actionPerformed")
    );

    上面是方法体包含了参数传入 (ActionEvent e),如果没有参数则只需 ( ),例如 Thread 中的 run 方法就没有参数传入,当它使用 Lambda 表达式后:

    Thread t = new Thread(
    \run 没有参数传入,所以用 (), 后面用 {} 包起方法体
    () -> {
     System.out.println("Hello from a thread in run");
    }
    );
    通过上面两个代码的比较可以发现使用 Lambda 表达式可以简化代码,并提高代码的可读性。
    为了进一步简化 Lambda 表达式,可以使用方法引用。例如,下面三种分别是使用内部类,使用 Lambda 表示式和使用方法引用方式的比较:
    //1. 使用内部类
    Function<Integer, String> f = new Function<Integer,String>(){
    @Override
    public String apply(Integer t) {
    return null;
    }
    };
    //2. 使用 Lambda 表达式
    Function<Integer, String> f2 = (t)->String.valueOf(t); 
    //3. 使用方法引用的方式
    Function<Integer, String> f1 = String::valueOf;

    要使用 Lambda 表达式,需要定义一个函数式接口,这样往往会让程序充斥着过量的仅为 Lambda 表达式服务的函数式接口。为了减少这样过量的函数式接口,Java 8 在 java.util.function 中增加了不少新的函数式通用接口。例如:

    Function<T, R>:将 T 作为输入,返回 R 作为输出,他还包含了和其他函数组合的默认方法。

    Predicate<T> :将 T 作为输入,返回一个布尔值作为输出,该接口包含多种默认方法来将 Predicate 组合成其他复杂的逻辑(与、或、非)。

    Consumer<T> :将 T 作为输入,不返回任何内容,表示在单个参数上的操作。

    例如,People 类中有一个方法 getMaleList 需要获取男性的列表,这里需要定义一个函数式接口 PersonInterface:
    interface PersonInterface {
     public boolean test(Person person);
    }
    public class People {
     private List<Person> persons= new ArrayList<Person>();
     public List<Person> getMaleList(PersonInterface filter) {
     List<Person> res = new ArrayList<Person>();
     persons.forEach(
     (Person person) -> 
     {
     if (filter.test(person)) {//调用 PersonInterface 的方法
     res.add(person);
     }
     }
     );
     return res;
     }
    }
    为了去除 PersonInterface 这个函数式接口,可以用通用函数式接口 Predicate 替代如下:
    class People{
     private List<Person> persons= new ArrayList<Person>();
     public List<Person> getMaleList(Predicate<Person> predicate) {
     List<Person> res = new ArrayList<Person>();
     persons.forEach(
     person -> {
     if (predicate.test(person)) {//调用 Predicate 的抽象方法 test
     res.add(person);
     }
     });
     return res;
     }
    }

    3、接口的增强

            Java 8 对接口做了进一步的增强。在接口中可以添加使用 default 关键字修饰的非抽象方法。还可以在接口中定义静态方法。如今,接口看上去与抽象类的功能越来越类似了。

    默认方法

    Java 8 还允许我们给接口添加一个非抽象的方法实现,只需要使用 default 关键字即可,这个特征又叫做扩展方法。在实现该接口时,该默认扩展方法在子类上可以直接使用,它的使用方式类似于抽象类中非抽象成员方法。但扩展方法不能够重载 Object 中的方法。例如:toString、equals、 hashCode 不能在接口中被重载。

    例如,下面接口中定义了一个默认方法 count(),该方法可以在子类中直接使用。

    public interface DefaultFunInterface {
    //定义默认方法 countdefault int count(){
    return 1;
    }
    }
    public class SubDefaultFunClass implements DefaultFunInterface {
    public static void main(String[] args){
    //实例化一个子类对象,改子类对象可以直接调用父接口中的默认方法 count
     SubDefaultFunClass sub = new SubDefaultFunClass();
    sub.count();
    }
    }

    静态方法

    在接口中,还允许定义静态的方法。接口中的静态方法可以直接用接口来调用。

    例如,下面接口中定义了一个静态方法 find,该方法可以直接用 StaticFunInterface .find() 来调用。

    public interface StaticFunInterface {public static int find(){
    return 1;
    }
    }
    public class TestStaticFun {
    public static void main(String[] args){
    //接口中定义了静态方法 find 直接被调用
    StaticFunInterface.fine();
    }
    }

    4、集合之流式操作

    Stream 和 Collection 集合的区别:Collection 是一种静态的内存数据结构,而 Stream 是有关计算的。前者是主要面向内存,存储在内存中,后者主要是面向 CPU,通过 CPU 实现计算。

    串行和并行的流

    流有串行和并行两种,串行流上的操作是在一个线程中依次完成,而并行流则是在多个线程上同时执行。并行与串行的流可以相互切换:通过 stream.sequential() 返回串行的流,通过 stream.parallel() 返回并行的流。相比较串行的流,并行的流可以很大程度上提高程序的执行效率。

    下面是分别用串行和并行的方式对集合进行排序。

    串行排序:

    List<String> list = new ArrayList<String>();
    for(int i=0;i<1000000;i++){
    double d = Math.random()*1000;
    list.add(d+"");
    }
    long start = System.nanoTime();//获取系统开始排序的时间点int count= (int) ((Stream) list.stream().sequential()).sorted().count();long end = System.nanoTime();//获取系统结束排序的时间点
    long ms = TimeUnit.NANOSECONDS.toMillis(end-start);//得到串行排序所用的时间
    System.out.println(ms+”ms”);

    并行排序:

    List<String> list = new ArrayList<String>();
    for(int i=0;i<1000000;i++){
    double d = Math.random()*1000;
    list.add(d+"");
    }
    long start = System.nanoTime();//获取系统开始排序的时间点int count = (int)((Stream) list.stream().parallel()).sorted().count();long end = System.nanoTime();//获取系统结束排序的时间点
    long ms = TimeUnit.NANOSECONDS.toMillis(end-start);//得到并行排序所用的时间
    System.out.println(ms+”ms”);
    串行输出为 1200ms,并行输出为 800ms。可见,并行排序的时间相比较串行排序时间要少不少。

    中间操作

    该操作会保持 stream 处于中间状态,允许做进一步的操作。它返回的还是的 Stream,允许更多的链式操作。常见的中间操作有:

    filter():对元素进行过滤;

    sorted():对元素排序;

    map():元素的映射;

    distinct():去除重复元素;

    subStream():获取子 Stream 等。

    例如,下面是对一个字符串集合进行过滤,返回以“s”开头的字符串集合,并将该集合依次打印出来:

    list.stream()
    .filter((s) -> s.startsWith("s"))
    .forEach(System.out::println);

    这里的 filter(...) 就是一个中间操作,该中间操作可以链式地应用其他 Stream 操作。

    终止操作

    该操作必须是流的最后一个操作,一旦被调用,Stream 就到了一个终止状态,而且不能再使用了。常见的终止操作有:

    forEach():对每个元素做处理;

    toArray():把元素导出到数组;

    findFirst():返回第一个匹配的元素;

    anyMatch():是否有匹配的元素等。

    例如,下面是对一个字符串集合进行过滤,返回以“s”开头的字符串集合,并将该集合依次打印出来:

    list.stream() //获取列表的 stream 操作对象
    .filter((s) -> s.startsWith("s"))//对这个流做过滤操作
    .forEach(System.out::println);

    这里的 forEach(...) 就是一个终止操作,该操作之后不能再链式的添加其他操作了。

    5、IO/NIO的改进

    Java 8 对 IO/NIO 也做了一些改进。主要包括:改进了 java.nio.charset.Charset 的实现,使编码和解码的效率得以提升,也精简了 jre/lib/charsets.jar 包;优化了 String(byte[],*) 构造方法和 String.getBytes() 方法的性能;还增加了一些新的 IO/NIO 方法,使用这些方法可以从文件或者输入流中获取流(java.util.stream.Stream),通过对流的操作,可以简化文本行处理、目录遍历和文件查找。

    新增的 API 如下:

    BufferedReader.line(): 返回文本行的流 Stream<String>

    File.lines(Path, Charset):返回文本行的流 Stream<String>

    File.list(Path): 遍历当前目录下的文件和目录

    File.walk(Path, int, FileVisitOption): 遍历某一个目录下的所有文件和指定深度的子目录

    File.find(Path, int, BiPredicate, FileVisitOption... ): 查找相应的文件

    下面就是用流式操作列出当前目录下的所有文件和目录:

    Files.list(new File(".").toPath())
     .forEach(System.out::println);

    6、全球化功能

    Java 8 版本还完善了全球化功能:支持新的 Unicode 6.2.0 标准,新增了日历和本地化的 API,改进了日期时间的管理等。

    Java 的日期与时间 API 问题由来已久,Java 8 之前的版本中关于时间、日期及其他时间日期格式化类由于线程安全、重量级、序列化成本高等问题而饱受批评。Java 8 吸收了 Joda-Time 的精华,以一个新的开始为 Java 创建优秀的 API。新的 java.time 中包含了所有关于时钟(Clock),本地日期(LocalDate)、本地时间(LocalTime)、本地日期时间(LocalDateTime)、时区(ZonedDateTime)和持续时间(Duration)的类。历史悠久的 Date 类新增了 toInstant() 方法,用于把 Date 转换成新的表示形式。这些新增的本地化时间日期 API 大大简化了了日期时间和本地化的管理。

    例如,下面是对 LocalDate,LocalTime 的简单应用:

    //LocalDate
    LocalDate localDate = LocalDate.now(); //获取本地日期
    localDate = LocalDate.ofYearDay(2014, 200); // 获得 2014 年的第 200 天 
    System.out.println(localDate.toString());//输出:2014-07-19
    localDate = LocalDate.of(2014, Month.SEPTEMBER, 10); //2014 年 9 月 10 日 
    System.out.println(localDate.toString());//输出:2014-09-10
    //LocalTime
    LocalTime localTime = LocalTime.now(); //获取当前时间
    System.out.println(localTime.toString());//输出当前时间
    localTime = LocalTime.of(10, 20, 50);//获得 10:20:50 的时间点
    System.out.println(localTime.toString());//输出: 10:20:50
    //Clock 时钟
    Clock clock = Clock.systemDefaultZone();//获取系统默认时区 (当前瞬时时间 )
    long millis = clock.millis();//
    -------------------------------------------------------------------------------------------------
    写点代码,写点科技。
    微信公众号「软件开发资讯」,遇见了不妨就关注看看。

    
    
    
  • 相关阅读:
    SpringBoot快速入门(三)
    分布式解决方案
    计算机网络-自顶向下方法第六章
    计算机网络-自顶向下方法第四章
    springcloud记录
    计算机网络-自顶向下方法第三章
    springboot整合kafka
    计算机网络-自顶向下方法第二章
    计算机网络-自顶向下方法第一章
    Spring常用注解【经典总结】
  • 原文地址:https://www.cnblogs.com/chengJAVA/p/6113649.html
Copyright © 2011-2022 走看看