java8的新特性


本文主要针对个人在工作中常用的java8新特性进行总结,希望共同进步,共同分享!

1、函数式接口@FunctionalInterface

简单来说,函数式接口是只包含一个方法的接口,像这样的接口可以被隐式转换为成为函数式接口。为保证方法数量不多不少,java8提供了一个专用注解@FunctionalInterface标记接口是函数接口,这样,当接口中声明的抽象方法多于或少于一个时就会报错。

java8通用的几类函数接口

类名方法类型说明
Consumervoid accept(T t);接受单个输入参数,无返回值
Function<T, R>R apply(T t);将此函数应用于给定参数
Predicateboolean test(T t);对给你定参数进行断言
SupplierT get();生产对象
Runnablepublic abstract void run();

说明:后面讲解的lambda表达式答题基于以上四种接口类型进行扩展

2、lambda表达式

只要听说过java8的新特性,大家都会想到lambda表达式吧,最主观的感受是,这玩意儿怎么还用到了箭头符号(->)来表示呢?那它到底是什么呢?要怎么使用呢?让我们一起看看

2.1、lambda表达式是什么

  • 1)lambda表达式本质上是一个匿名方法。
  • 2)Lambda允许把函数作为一个方法的参数(函数作为参数传递进 方法中)或者把代码看成数据。使用 Lambda 表达式可以使代码变 的更加简洁紧凑。
  • 3)在可以使用lambda表达式的地方,方法声明时必须包含一个函数式的接口。

2.2、lambda表达式语法

(parameters) -> expression
或
(parameters) ->{ statements; }

以下是lambda表达式的重要特征:

  • 可选类型声明:不需要声明参数类型,编译器可以统一识别参数值。
  • 可选的参数圆括号:一个参数无需定义圆括号,但多个参数需要定义圆括号。
  • 可选的大括号:如果主体包含了一个语句,就不需要使用大括号。
  • 可选的返回关键字:如果主体只有一个表达式返回值则编译器会自动返回值,大括号需要指定明表达式返回了一个数值。

2.2、示例

public class TestLambda {
    public static void runThreadUseLambda() {
        //Runnable是一个函数接口,只包含了有个无参数的,返回void的run方法;
        //所以lambda表达式左边没有参数,右边也没有return,只是单纯的打印一句话
        new Thread(() ->System.out.println("lambda实现的线程")).start();
    }

    public static void runThreadUseInnerClass() {
        //这种方式就不多讲了,以前旧版本比较常见的做法
        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("内部类实现的线程");
            }
        }).start();
    }
    
    public static void main(String[] args) {
        TestLambda.runThreadUseLambda();
        TestLambda.runThreadUseInnerClass();
    }
}

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

简单说,默认方法就是接口可以有实现方法,而且不需要实现类去实现其方法。
我们只需在方法名前面加个 default 关键字即可实现默认方法。

为什么要有这个特性?
首先,之前的接口是个双刃剑,好处是面向抽象而不是面向具体编程,缺陷是,当需要修改接口时候,需要修改全部实现该接口的类,目前的 java 8 之前的集合框架没有 foreach 方法,通常能想到的解决办法是在JDK里给相关的接口添加新的方法及实现。然而,对于已经发布的版本,是没法在给接口添加新方法的同时不影响已有的实现。所以引进的默认方法。他们的目的是为了解决接口的修改与现有的实现不兼容的问题。

2.1、默认方法

1)语法格式(以Functioin的默认函数为例):

    default <V> Function<V, R> compose(Function<? super V, ? extends T> before) {
        Objects.requireNonNull(before);
        return (V v) -> apply(before.apply(v));
    }
    

2)多个默认方法
一个接口有默认方法,考虑这样的情况,一个类实现了多个接口,且这些接口有相同的默认方法,以下实例说明了这种情况的解决方法:

  • 创建自己的默认方法,来覆盖重写接口的默认方法
  • 使用 super 来调用指定接口的默认方法

2.2、静态方法

Java 8 的另一个特性是接口可以声明(并且可以提供实现)静态方法。例如:

3、新增方法引用格式

①方法引用通过方法的名字来指向一个方法。
②方法引用可以使语言的构造更紧凑简洁,减少冗余代码。
③方法引用使用一对冒号 :: 。

3.1、示例:

package com.runoob.main;
 
@FunctionalInterface
public interface Supplier<T> {
    T get();
}
 
class Car {
    //Supplier是jdk1.8的接口,这里和lamda一起使用了
    public static Car create(final Supplier<Car> supplier) {
        return supplier.get();
    }
 
    public static void collide(final Car car) {
        System.out.println("Collided " + car.toString());
    }
 
    public void follow(final Car another) {
        System.out.println("Following the " + another.toString());
    }
 
    public void repair() {
        System.out.println("Repaired " + this.toString());
    }
}
  • 构造器引用:它的语法是Class::new,或者更一般的Class< T >::new实例如下:
final Car car = Car.create( Car::new );
final List< Car > cars = Arrays.asList( car );
  • 静态方法引用:它的语法是Class::static_method,实例如下:
cars.forEach( Car::collide );
  • 特定类的任意对象的方法引用:它的语法是Class::method实例如下:
cars.forEach( Car::repair );
  • 特定对象的方法引用:它的语法是instance::method实例如下:
final Car police = Car.create( Car::new );
cars.forEach( police::follow );

4、新增Stream类

4.1、什么是Stream

Stream(流)是一个来自数据源的元素队列并支持聚合操作

  • 元素是特定类型的对象,形成一个队列。 Java中的Stream并不会存储元素,而是按需计算。
  • 数据源: 流的来源。 可以是集合,数组,I/O channel, 产生器generator 等。
  • 聚合操作: 类似SQL语句一样的操作, 比如filter, map, reduce, find, match, sorted等。

4.2、Stream两个基础的特征

  • Pipelining: 中间操作都会返回流对象本身。 这样多个操作可以串联成一个管道, 如同流式风格(fluent style)。 这样做可以对操作进行优化, 比如延迟执行(laziness)和短路( short-circuiting)。
  • 内部迭代: 以前对集合遍历都是通过Iterator或者For-Each的方式, 显式的在集合外部进行迭代, 这叫做外部迭代。 Stream提供了内部迭代的方式, 通过访问者模式(Visitor)实现。

4.3、使用流的基本操作


  • 创建Stream
  • 转换Stream,每次转换原有Stream对象不改变,返回一个新的 Stream对象(可以有多次转换)
  • 对Stream进行聚合操作,获取想要的结果

4.3.1、 流的生成方法

1.Collection接口的stream()或parallelStream()方法
2.静态的Stream.of()、Stream.empty()方法
3.Arrays.stream(array, from, to)
4.静态的Stream.generate()方法生成无限流,接受一个不包含引元的函数
5.静态的Stream.iterate()方法生成无限流,接受一个种子值以及一个迭代函数
6.Pattern接口的splitAsStream(input)方法
7.静态的Files.lines(path)、Files.lines(path, charSet)方法
8.静态的Stream.concat()方法将两个流连接起来

4.3.2、 流的Intermediate方法(中间操作)

 1.filter(Predicate) :将结果为false的元素过滤掉
 2.map(fun) :转换元素的值,可以用方法引元或者lambda表达式
 3.flatMap(fun) :若元素是流,将流摊平为正常元素,再进行元素转换
 4.limit(n) :保留前n个元素
 5.skip(n) :跳过前n个元素
 6.distinct() :剔除重复元素
 7.sorted() :将Comparable元素的流排序
 8.sorted(Comparator) :将流元素按Comparator排序
 9.peek(fun) :流不变,但会把每个元素传入fun执行,可以用作调试

4.3.3、 流的Terminal方法(终结操作)

(1)约简操作

 1.reduce(fun) :从流中计算某个值,接受一个二元函数作为累积器,从前两个元素开始持续应用它,累积器的中间结果作为第一个参数,流元素作为第二个参数
 2.reduce(a, fun) :a为幺元值,作为累积器的起点
 3.reduce(a, fun1, fun2) :与二元变形类似,并发操作中,当累积器的第一个参数与第二个参数都为流元素类型时,可以对各个中间结果也应用累积器进行合并,但是当累积器的第一个参数不是流元素类型而是类型T的时候,各个中间结果也为类型T,需要fun2来将各个中间结果进行合并

(2)收集操作

    1.iterator():
    2.forEach(fun):
    3.forEachOrdered(fun) :可以应用在并行流上以保持元素顺序
    4.toArray():
    5.toArray(T[] :: new) :返回正确的元素类型
    6.collect(Collector):
    7.collect(fun1, fun2, fun3) :fun1转换流元素;fun2为累积器,将fun1的转换结果累积起来;fun3为组合器,将并行处理过程中累积器的各个结果组合起来

(3)查找与收集操作

  1.max(Comparator):返回流中最大值
  2.min(Comparator):返回流中最小值
  3.count():返回流中元素个数
  4.findFirst() :返回第一个元素
  5.findAny() :返回任意元素
  6.anyMatch(Predicate) :任意元素匹配时返回true
  7.allMatch(Predicate) :所有元素匹配时返回true
  8.noneMatch(Predicate) :没有元素匹配时返回true

4.4、示例

  • 集合的List转化为Map(一)
public class TestLambda {
    public static void main(String args[]) {
        Student student1 = Student.create(Student::new);
        student1.setId("11");
        student1.setName("haha");

        Student student2 = new Student("22", "test");
        Student student3 = new Student("11", "test333");
        List<Student> students = Arrays.asList(student1, student2,student3);

        Map<String, List<Student>> classes = students.stream().collect(Collectors.toMap(Student::getId,
                p->{
                        List<Student> list = new ArrayList<>();
                        list.add(p);
                        return list; },
                (List<Student> s1, List<Student> s2)->{
                         s1.addAll(s2);
                         return s1;}
                         ));
        System.out.println("classes==" + classes);

    }
}
  • 集合的List转化为Map(二)-推荐
public static void main(String args[]) {
        Student student1 = Student.create(Student::new);
        student1.setId("11");
        student1.setName("haha");

        Student student2 = new Student("22", "test");
        Student student3 = new Student("11", "test333");
        List<Student> students = Arrays.asList(student1, student2,student3);
        //使用groupBy进行List转map更简洁
        Map<String, List<Student>> classes1 =students.stream().collect(Collectors.groupingBy(Student::getId));
        System.out.println("classes1==" + classes1);
    }

公共代码:

 class Student{
        private String id;
        private String name;

        public Student(){}

        public static Student create(final Supplier<Student> supplier) {
            return supplier.get();
        }

        public Student(String id, String name){
            this.id=id;
            this.name=name;
        }

        public void setId(String id) {
            this.id = id;
        }

        public void setName(String name) {
            this.name = name;
        }

        public String getId() {
            return id;
        }

        public String getName() {
            return name;
        }

        @Override
        public String toString() {
            return "Student{" +
                    "id='" + id + '\'' +
                    ", name='" + name + '\'' +
                    '}';
        }
    }


class classNo {
        private List<Student> students;
        private String className;

        public List<Student> getStudents() {
            return students;
        }

        public void setStudents(List<Student> students) {
            this.students = students;
        }

        public String getClassName() {
            return className;
        }

        public void setClassName(String className) {
            this.className = className;
        }
    }

5、引入Optional

Optional类实际上是个容器,它可以保存类型T的值,或者仅仅保存null。Optional 类的引入很好的解决空指针异常。Optional提供很多有用的方法,这样我们就不用显式进行空值检测。
TIP:尽量避免在程序中直接调用Optional对象的get()和isPresent()方法,避免使用Optional类型声明实体类的属性。

  • optional常用的方法

(1)Optional.of(T t) : 创建一个 Optional 实例
(2)Optional.empty() : 创建一个空的 Optional 实例
(3)Optional.ofNullable(T t):若 t 不为 null,创建 Optional 实例,否则创建空实例
(4)isPresent() : 判断是否包含值 orElse(T t) : 如果调用对象包含值,返回该值,否则返回t
(5)orElseGet(Supplier s) :如果调用对象包含值,返回该值,否则返回 s 获取的值
(6)map(Function f): 如果有值对其处理,并返回处理后的Optional,否则返回Optional.empty()
(7)flatMap(Function mapper):与 map 类似,要求返回值必须是Optional

6、新的日期API

Java 8通过发布新的Date-Time API (JSR 310)来进一步加强对日期与时间的处理。

1. 在旧版的 Java 中,日期时间 API 存在诸多问题,其中有:

  • 1)非线程安全 − java.util.Date 是非线程安全的,所有的日期类都是可变的,这是Java日期类最大的问题之一。
  • 2)设计很差 −Java的日期/时间类的定义并不一致,在java.util和java.sql的包中都有日期类,此外用于格式化和解析的类在java.text包中定义。java.util.Date同时包含日期和时间,而java.sql.Date仅包含日期,将其纳入java.sql包并不合理。另外这两个类都有相同的名字,这本身就是一个非常糟糕的设计。
  • 3)时区处理麻烦 −日期类并不提供国际化,没有时区支持,因此Java引入了java.util.Calendar和java.util.TimeZone类,但他们同样存在上述所有的问题。

2. Java 8 在 java.time 包下提供了很多新的 API。以下为两个比较重要的 API:

  • Local(本地) − 简化了日期时间的处理,没有时区的问题。
  • Zoned(时区) − 通过制定的时区处理日期时间。

6.1、本地化时间API

import java.time.LocalDate;
import java.time.LocalTime;
import java.time.LocalDateTime;
import java.time.Month;
 
public class Java8Tester {
   public static void main(String args[]){
      Java8Tester java8tester = new Java8Tester();
      java8tester.testLocalDateTime();
   }
    
   public void testLocalDateTime(){
    
      // 获取当前的日期时间
      LocalDateTime currentTime = LocalDateTime.now();
      System.out.println("当前时间: " + currentTime);
        
      LocalDate date1 = currentTime.toLocalDate();
      System.out.println("date1: " + date1);
        
      Month month = currentTime.getMonth();
      int day = currentTime.getDayOfMonth();
      int seconds = currentTime.getSecond();
        
      System.out.println("月: " + month +", 日: " + day +", 秒: " + seconds);
        
      LocalDateTime date2 = currentTime.withDayOfMonth(10).withYear(2012);
      System.out.println("date2: " + date2);
        
      // 12 december 2014
      LocalDate date3 = LocalDate.of(2014, Month.DECEMBER, 12);
      System.out.println("date3: " + date3);
        
      // 22 小时 15 分钟
      LocalTime date4 = LocalTime.of(22, 15);
      System.out.println("date4: " + date4);
        
      // 解析字符串
      LocalTime date5 = LocalTime.parse("20:15:30");
      System.out.println("date5: " + date5);
   }
}

6.2、本地化时间API

import java.time.ZonedDateTime;
import java.time.ZoneId;
 
public class Java8Tester {
   public static void main(String args[]){
      Java8Tester java8tester = new Java8Tester();
      java8tester.testZonedDateTime();
   }
    
   public void testZonedDateTime(){
    
      // 获取当前时间日期
      ZonedDateTime date1 = ZonedDateTime.parse("2015-12-03T10:15:30+05:30[Asia/Shanghai]");
      System.out.println("date1: " + date1);
        
      ZoneId id = ZoneId.of("Europe/Paris");
      System.out.println("ZoneId: " + id);
        
      ZoneId currentZone = ZoneId.systemDefault();
      System.out.println("当期时区: " + currentZone);
   }
}
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值