JDK8新特性详解(上)

简介: JDK8新特性详解

01、Java8新特性简介


Java 8 (又称为 jdk 1.8) 是 Java 语言开发的一个主要版本。


Java 8 是 oracle 公司于 2014 年 3 月发布,可以 看成是自 Java 5 以来最具革命性的版本 。 Java 8 为 Java 语言、编译器、类库、开发工具与 JVM 带来了大量新特性。


image.png


Java8新特性的好处


速度更快

代码更少(增加了新的语法:Lambda 表达式)

强大的Stream API

便于并行

最大化减少空指针异常:Optional

Nashorn引擎,允许在JVM上运行JS应用


02、Lambda表达式


Lambda 是一个匿名函数,我们可以把Lambda 表达式理解为是一段可以传递的代码(将代码像数据一样进行传递)。使用它可以写出更简洁、更灵活的代码。作为一种更紧凑的代码风格,使Java的语言表达能力得到了提升。


格式:


-> :lambda操作符 或 箭头操作符

->左边:lambda形参列表 (其实就是接口中的抽象方法的形参列表)

->右边:lambda体 (其实就是重写的抽象方法的方法体)

如何使用


a88d5e2dc42849920ee254de18ec2c61.png


b646c0704e71686a3a37b412905c6692.png


代码演示

/**
 * Lambda表达式的使用
 *
 * 1.举例: (o1,o2) -> Integer.compare(o1,o2);
 * 2.格式:
 *      -> :lambda操作符 或 箭头操作符
 *      ->左边:lambda形参列表 (其实就是接口中的抽象方法的形参列表)
 *      ->右边:lambda体 (其实就是重写的抽象方法的方法体)
 *
 * 3. Lambda表达式的使用:(分为6种情况介绍)
 *
 *    总结:
 *    ->左边:lambda形参列表的参数类型可以省略(类型推断);如果lambda形参列表只有一个参数,其一对()也可以省略
 *    ->右边:lambda体应该使用一对{}包裹;如果lambda体只有一条执行语句(可能是return语句),省略这一对{}和return关键字
 *
 * 4.Lambda表达式的本质:作为函数式接口的实例
 *
 * 5. 如果一个接口中,只声明了一个抽象方法,则此接口就称为函数式接口。我们可以在一个接口上使用 @FunctionalInterface 注解,
 *   这样做可以检查它是否是一个函数式接口。
 *
 * 6. 所以以前用匿名实现类表示的现在都可以用Lambda表达式来写。
 *
 * @author 物竞天择适者生存
 *
 */
public class LambdaTest {
    //语法格式一:无参,无返回值
    @Test
    public void test1(){
        Runnable r1 = new Runnable() {
            @Override
            public void run() {
                System.out.println("我爱北京天安门");
            }
        };
        r1.run();
        System.out.println("***********************");
        Runnable r2 = () -> {
            System.out.println("我爱北京故宫");
        };
        r2.run();
    }
    //语法格式二:Lambda 需要一个参数,但是没有返回值。
    @Test
    public void test2(){
        Consumer <String> con = new Consumer<String>() {
            @Override
            public void accept(String s) {
                System.out.println(s);
            }
        };
        con.accept("谎言和誓言的区别是什么?");
        System.out.println("*******************");
        Consumer<String> con1 = (String s) -> {
            System.out.println(s);
        };
        con1.accept("一个是听得人当真了,一个是说的人当真了");
    }
    //语法格式三:数据类型可以省略,因为可由编译器推断得出,称为“类型推断”
    @Test
    public void test3(){
        Consumer<String> con1 = (String s) -> {
            System.out.println(s);
        };
        con1.accept("一个是听得人当真了,一个是说的人当真了");
        System.out.println("*******************");
        Consumer<String> con2 = (s) -> {
            System.out.println(s);
        };
        con2.accept("一个是听得人当真了,一个是说的人当真了");
    }
    @Test
    public void test4(){
        ArrayList <String> list = new ArrayList<>();//类型推断
        int[] arr = {1,2,3};//类型推断
    }
    //语法格式四:Lambda 若只需要一个参数时,参数的小括号可以省略
    @Test
    public void test5(){
        Consumer<String> con1 = (s) -> {
            System.out.println(s);
        };
        con1.accept("一个是听得人当真了,一个是说的人当真了");
        System.out.println("*******************");
        Consumer<String> con2 = s -> {
            System.out.println(s);
        };
        con2.accept("一个是听得人当真了,一个是说的人当真了");
    }
    //语法格式五:Lambda 需要两个或以上的参数,多条执行语句,并且可以有返回值
    @Test
    public void test6(){
        Comparator <Integer> com1 = new Comparator<Integer>() {
            @Override
            public int compare(Integer o1, Integer o2) {
                System.out.println(o1);
                System.out.println(o2);
                return o1.compareTo(o2);
            }
        };
        System.out.println(com1.compare(12,21));
        System.out.println("*****************************");
        Comparator<Integer> com2 = (o1,o2) -> {
            System.out.println(o1);
            System.out.println(o2);
            return o1.compareTo(o2);
        };
        System.out.println(com2.compare(12,6));
    }
    //语法格式六:当 Lambda 体只有一条语句时,return 与大括号若有,都可以省略
    @Test
    public void test7(){
        Comparator<Integer> com1 = (o1,o2) -> {
            return o1.compareTo(o2);
        };
        System.out.println(com1.compare(12,6));
        System.out.println("*****************************");
        Comparator <Integer> com2 = (o1, o2) -> o1.compareTo(o2);
        System.out.println(com2.compare(12,21));
    }
    @Test
    public void test8(){
        Consumer <String> con1 = s -> {
            System.out.println(s);
        };
        con1.accept("一个是听得人当真了,一个是说的人当真了");
        System.out.println("*****************************");
        Consumer<String> con2 = s -> System.out.println(s);
        con2.accept("一个是听得人当真了,一个是说的人当真了");
    }
}

03、函数式(Functional)接口

3.1 什么是函数式 ( 接口)


只包含一个抽象方法的接口,称为 函数式接口

你可以通过 Lambda 表达式来创建该接口的对象。(若 Lambda 表达式

抛出一个受检异常 即:非运行时异常 )),那么该异常需要在目标接口的抽

象方法上进行声明)

我们可以在一个接口上使用 @FunctionalInterface 注解,这样做可以检

查它是否是一个函数式接口。同时 javadoc 也会包含一条声明,说明这个

接口是一个函数式接口

在 java.util.function 包下定义 了 Java 8 的丰富的函数式 接口


3.2 如何理解函数式接口


Java 从诞生日起就是一直倡导“一切皆对象”, 在 Java 里面面向对象 (

编程是一切。但是随着 python 、 scala 等语言的兴起和新技术的挑战 Java 不

得不做出调整以便支持更加广泛的技术要求,也即 java 不但可以支持 OOP 还

可以支持 OOF (面向函数编程)

在函数式编程语言当中,函数被当做一等公民对待。在将函数作为一等公民的

编程语言中, Lambda 表达式的类型是函数。但是在 Java8 中,有所不同。在

Java8 中, Lambda 表达式是对象,而不是函数,它们必须依附于一类特别的

对象类型–函数式接口

简单的说,在 Java8 中, Lambda 表达式就是一个函数式接口的实例。 这就是

Lambda 表达式和函数式接口的关系。也就是说,只要一个对象是函数式接口

的实例,那么该对象就可以用 Lambda 表达式来表示

所以以前用 匿名实现类 表示的现在都可以用 Lambda 表达式来写


3.3 函数式接口举例

804a03fa5bbd9cae520ce009b50245fd.png

/**
 * 自定义函数式接口
 * @author shkstart
 * @create 2019 下午 2:20
 */
@FunctionalInterface
public interface MyInterface {
    void method1();
}


3.4 Java内置的函数式接口介绍及使用举例


image.png

image.png

/**
 * java内置的4大核心函数式接口
 *
 * 消费型接口 Consumer<T>     void accept(T t)
 * 供给型接口 Supplier<T>     T get()
 * 函数型接口 Function<T,R>   R apply(T t)
 * 断定型接口 Predicate<T>    boolean test(T t)
 *
 * @version 1.0
 * @Description
 * @date 2022/7/18 14:43
 */
public class LambdaTest2 {
    @Test
    public void test1(){
        happyTime(500, new Consumer <Double>() {
            @Override
            public void accept(Double aDouble) {
                System.out.println("学习太累了,去天上人间买了瓶矿泉水,价格为"+aDouble);
            }
        });
        System.out.println("*********************");
        happyTime(400,money -> {
            System.out.println("学习太累了,去天上人间买了瓶矿泉水,价格为"+money);
        });
    }
    public void happyTime(double money,Consumer<Double> consumer){
        consumer.accept(money);
    }
    @Test
    public void test2(){
        List <String> list = Arrays.asList("北京", "南京", "天津", "东京", "吴京", "普京", "牛津");
        List <String> filterString1 = filterString(list, new Predicate <String>() {
            @Override
            public boolean test(String s) {
                return s.contains("京");
            }
        });
        System.out.println(filterString1);
        System.out.println("*****************************");
        List <String> filterString = filterString(list, s -> s.contains("京"));
        System.out.println(filterString);
    }
    //根据给定的规则,过滤集合中的字符串。此规则由Predicate的方法决定
    public List <String> filterString(List<String> list, Predicate <String> pre){
        List <String> filterList = new ArrayList <>();
        for (String s : list) {
            if(pre.test(s)){
                filterList.add(s);
            }
        }
        return filterList;
    }
}

04、方法引用与构造器引用

4.1 方法引用的使用

当要传递给Lambda体的操作,已经有实现的方法了,可以使用方法引用!

方法引用可以看做是Lambda表达式深层次的表达。换句话说,方法引用就是Lambda表达式

也就是函数式接口的一个实例,通过方法的名字来指向一个方法,可以认为是Lambda表达式的一个语法糖。

要求:实现接口的抽象方法的参数列表和返回值类型,必须与方法引用的方法的参数列表和返回值类型保持一致!

格式:使用操作符“::” 将类(或对象) 与方法名分隔开来。

如下三种主要使用情况:

对象::实例方法名

类::静态方法名

类::实例方法名

代码演示


Employee类

/**
 * @author shkstart 邮箱:shkstart@126.com
 */
public class Employee {
  private int id;
  private String name;
  private int age;
  private double salary;
  public int getId() {
    return id;
  }
  public void setId(int id) {
    this.id = id;
  }
  public String getName() {
    return name;
  }
  public void setName(String name) {
    this.name = name;
  }
  public int getAge() {
    return age;
  }
  public void setAge(int age) {
    this.age = age;
  }
  public double getSalary() {
    return salary;
  }
  public void setSalary(double salary) {
    this.salary = salary;
  }
  public Employee() {
    System.out.println("Employee().....");
  }
  public Employee(int id) {
    this.id = id;
    System.out.println("Employee(int id).....");
  }
  public Employee(int id, String name) {
    this.id = id;
    this.name = name;
  }
  public Employee(int id, String name, int age, double salary) {
    this.id = id;
    this.name = name;
    this.age = age;
    this.salary = salary;
  }
  @Override
  public String toString() {
    return "Employee{" + "id=" + id + ", name='" + name + '\'' + ", age=" + age + ", salary=" + salary + '}';
  }
  @Override
  public boolean equals(Object o) {
    if (this == o)
      return true;
    if (o == null || getClass() != o.getClass())
      return false;
    Employee employee = (Employee) o;
    if (id != employee.id)
      return false;
    if (age != employee.age)
      return false;
    if (Double.compare(employee.salary, salary) != 0)
      return false;
    return name != null ? name.equals(employee.name) : employee.name == null;
  }
  @Override
  public int hashCode() {
    int result;
    long temp;
    result = id;
    result = 31 * result + (name != null ? name.hashCode() : 0);
    result = 31 * result + age;
    temp = Double.doubleToLongBits(salary);
    result = 31 * result + (int) (temp ^ (temp >>> 32));
    return result;
  }
}
  • EmployeeData
/**
 * 提供用于测试的数据
 * 
 * @author shkstart 邮箱:shkstart@126.com
 *
 */
public class EmployeeData {
  public static List<Employee> getEmployees(){
    List<Employee> list = new ArrayList<>();
    list.add(new Employee(1001, "马化腾", 34, 6000.38));
    list.add(new Employee(1002, "马云", 12, 9876.12));
    list.add(new Employee(1003, "刘强东", 33, 3000.82));
    list.add(new Employee(1004, "雷军", 26, 7657.37));
    list.add(new Employee(1005, "李彦宏", 65, 5555.32));
    list.add(new Employee(1006, "比尔盖茨", 42, 9500.43));
    list.add(new Employee(1007, "任正非", 26, 4333.32));
    list.add(new Employee(1008, "扎克伯格", 35, 2500.32));
    return list;
  }
}

方法引用的测试类

/**
 * 方法引用的使用
 *
 * 1.使用情境:当要传递给Lambda体的操作,已经有实现的方法了,可以使用方法引用!
 *
 * 2.方法引用,本质上就是Lambda表达式,而Lambda表达式作为函数式接口的实例。所以
 *   方法引用,也是函数式接口的实例。
 *
 * 3. 使用格式:  类(或对象) :: 方法名
 *
 * 4. 具体分为如下的三种情况:
 *    情况1     对象 :: 非静态方法
 *    情况2     类 :: 静态方法
 *
 *    情况3     类 :: 非静态方法
 *
 * 5. 方法引用使用的要求:要求接口中的抽象方法的形参列表和返回值类型与方法引用的方法的
 *    形参列表和返回值类型相同!(针对于情况1和情况2)
 *
 * Created by shkstart.
 */
public class MethodRefTest {
  // 情况一:对象 :: 实例方法
  //Consumer中的void accept(T t)
  //PrintStream中的void println(T t)
  @Test
  public void test1() {
    Consumer<String> consumer1 = str-> System.out.println(str);
    consumer1.accept("北京");
    System.out.println("*******************");
    PrintStream ps = System.out;
    Consumer<String> consumer2 = ps::println;
    consumer2.accept("北京");
  }
  //Supplier中的T get()
  //Employee中的String getName()
  @Test
  public void test2() {
    Employee emp = new Employee(1001, "Tom", 23, 5600);
    Supplier<String> sup1 = ()->emp.getName();
    System.out.println(sup1.get());
    System.out.println("*********************");
    Supplier<String> sup2 = emp::getName;
    System.out.println(sup2.get());
  }
  // 情况二:类 :: 静态方法
  //Comparator中的int compare(T t1,T t2)
  //Integer中的int compare(T t1,T t2)
  @Test
  public void test3() {
    Comparator<Integer> com1 = (t1,t2)->Integer.compare(t1,t2);
    System.out.println(com1.compare(10,54));
    System.out.println("*********************");
    Comparator<Integer> com2 = Integer::compare;
    System.out.println(com2.compare(54,13));
  }
  //Function中的R apply(T t)
  //Math中的Long round(Double d)
  @Test
  public void test4() {
    Function<Double,Long> func = new Function <Double, Long>() {
      @Override
      public Long apply(Double aDouble) {
        return Math.round(aDouble);
      }
    };
    System.out.println(func.apply(10.1));
    System.out.println("*******************");
    Function<Double,Long> func2 = a->Math.round(a);
    System.out.println(func2.apply(10.5));
    System.out.println("*******************");
    Function<Double,Long> func3 = Math::round;
    System.out.println(func3.apply(10.8));
  }
  // 情况三:类 :: 实例方法 
  // Comparator中的int comapre(T t1,T t2)
  // String中的int t1.compareTo(t2)
  @Test
  public void test5() {
    Comparator<String> com1 = (s1,s2)->s1.compareTo(s2);
    System.out.println(com1.compare("abc","abd"));
    System.out.println("*******************");
    Comparator<String> com2 = String::compareTo;
    System.out.println(com2.compare("abd","abm"));//第一个参数当成函数的调用者
  }
  //BiPredicate中的boolean test(T t1, T t2);
  //String中的boolean t1.equals(t2)
  @Test
  public void test6() {
    BiPredicate<String,String> pre1 = (s1,s2)->s1.equals(s2);
    System.out.println(pre1.test("abc","abc"));
    System.out.println("************************");
    BiPredicate<String,String> pre2 = String::equals;
    System.out.println(pre2.test("abc","abd"));
  }
  // Function中的R apply(T t)
  // Employee中的String getName();
  @Test
  public void test7() {
    Employee employee = new Employee(1001, "张三", 20, 6600);
    Function<Employee,String> fun1 = e -> e.getName();
    System.out.println(fun1.apply(employee));
    System.out.println("***************");
    Function<Employee,String> fun2 = Employee::getName;
    System.out.println(fun2.apply(employee));//传入的参数employee当成方法调用者
  }
}

4.2 构造器引用与数组引用的使用

格式:ClassName::new //构造器引用
格式:type[] :: new  //数组引用

与函数式接口相结合,自动与函数式接口中方法兼容。

可以把构造器引用赋值给定义的方法,要求构造器参数列表要与接口中抽象方法的参数列表一致!且方法的返回值即为构造器对应类的对象

/**
 * 一、构造器引用
 *      和方法引用类似,函数式接口的抽象方法的形参列表和构造器的形参列表一致。
 *      抽象方法的返回值类型即为构造器所属的类的类型
 *
 * 二、数组引用
 *     大家可以把数组看做是一个特殊的类,则写法与构造器引用一致。
 *
 * Created by shkstart
 */
public class ConstructorRefTest {
  //构造器引用
    //Supplier中的T get()
    //Employee的空参构造器:Employee()
    @Test
    public void test1(){
        Supplier<Employee> sup = new Supplier<Employee>() {
            @Override
            public Employee get() {
                return new Employee();
            }
        };
        System.out.println("*******************");
        Supplier<Employee>  sup1 = () -> new Employee();
        System.out.println(sup1.get());
        System.out.println("*******************");
        Supplier<Employee>  sup2 = Employee :: new;
        System.out.println(sup2.get());
    }
  //Function中的R apply(T t)
    @Test
    public void test2(){
        Function<Integer,Employee> func1 = id -> new Employee(id);
        Employee employee = func1.apply(1001);
        System.out.println(employee);
        System.out.println("*******************");
        Function<Integer,Employee> func2 = Employee :: new;
        Employee employee1 = func2.apply(1002);
        System.out.println(employee1);
    }
  //BiFunction中的R apply(T t,U u)
    @Test
    public void test3(){
        BiFunction<Integer,String,Employee> func1 = (id,name) -> new Employee(id,name);
        System.out.println(func1.apply(1001,"Tom"));
        System.out.println("*******************");
        BiFunction<Integer,String,Employee> func2 = Employee :: new;
        System.out.println(func2.apply(1002,"Tom"));
    }
  //数组引用
    //Function中的R apply(T t)
    @Test
    public void test4(){
        Function<Integer,String[]> func1 = length -> new String[length];
        String[] arr1 = func1.apply(5);
        System.out.println(Arrays.toString(arr1));
        System.out.println("*******************");
        Function<Integer,String[]> func2 = String[] :: new;
        String[] arr2 = func2.apply(10);
        System.out.println(Arrays.toString(arr2));
    }
}
相关文章
|
4天前
|
Java API
JDK8到JDK25版本升级的新特性问题之使用Collectors.teeing()来计算一个列表中学生的平均分和总分如何操作
JDK8到JDK25版本升级的新特性问题之使用Collectors.teeing()来计算一个列表中学生的平均分和总分如何操作
|
4天前
|
Java API Apache
JDK8到JDK24版本升级的新特性问题之在Java中,HttpURLConnection有什么局限性,如何解决
JDK8到JDK24版本升级的新特性问题之在Java中,HttpURLConnection有什么局限性,如何解决
|
4天前
|
Oracle Java 关系型数据库
JDK8到JDK29版本升级的新特性问题之未来JDK的升级是否会成为必然趋势,如何理解
JDK8到JDK29版本升级的新特性问题之未来JDK的升级是否会成为必然趋势,如何理解
|
4天前
|
Oracle 安全 Java
JDK8到JDK28版本升级的新特性问题之在Java 15及以后的版本中,密封类和密封接口是怎么工作的
JDK8到JDK28版本升级的新特性问题之在Java 15及以后的版本中,密封类和密封接口是怎么工作的
|
5天前
|
Java 编译器 API
JDK版本特性问题之在 JDK 17 中,想声明一个密封类,如何解决
JDK版本特性问题之在 JDK 17 中,想声明一个密封类,如何解决
|
4天前
|
Java API 开发者
JDK8到JDK17版本升级的新特性问题之SpringBoot选择JDK17作为最小支持的Java lts版本意味着什么
JDK8到JDK17版本升级的新特性问题之SpringBoot选择JDK17作为最小支持的Java lts版本意味着什么
JDK8到JDK17版本升级的新特性问题之SpringBoot选择JDK17作为最小支持的Java lts版本意味着什么
|
4天前
|
算法 Java iOS开发
JDK8到JDK27版本升级的新特性问题之JDK 17中G1在资源占用方面有何变化
JDK8到JDK27版本升级的新特性问题之JDK 17中G1在资源占用方面有何变化
|
4天前
|
XML JSON Java
JDK8到JDK26版本升级的新特性问题之在JDK 13中,字符串文本块改进字符串嵌入是如何实现的
JDK8到JDK26版本升级的新特性问题之在JDK 13中,字符串文本块改进字符串嵌入是如何实现的
|
4天前
|
Java 编译器 开发者
JDK8到JDK23版本升级的新特性问题之编写一个简单的module-info.java文件,如何实现
JDK8到JDK23版本升级的新特性问题之编写一个简单的module-info.java文件,如何实现
|
4天前
|
缓存 Oracle Java
JDK8到JDK22版本升级的新特性问题之在JDK17中,日志的刷新如何操作
JDK8到JDK22版本升级的新特性问题之在JDK17中,日志的刷新如何操作