Java8新特性

Posted 我永远信仰

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java8新特性相关的知识,希望对你有一定的参考价值。

Java8新特性

面试问到你Java8的新特性,你能答上来吗

Java现在更新到了很高的版本,不过不建议它一更新我们就去了解、就去学。因为很少概率能用的到,现在很多公司或企业使用的版本都是比较低的。新版本需要经过岁月的考验,才能知道哪些特性是好用的哪些是不好用的。

Java8可以看成自Java5以来最具有革命性的版本,**非常推荐**学习Java8的新特性。

在学习的时候,会遇到的非常多听着、看着都一头雾水的概念。不要被劝退,看代码,带着疑问去学习。怎么去实现?它能用来干嘛?

第一遍学习完过后,只是给自己了解到,“哦,原来有这个东西,可以这么样子用”,想进一步掌握,还是需要多敲代码,多思考。

Java8新特性简介

说一说Java8的新特性?

1.lambda表达式:

  • 速度更快
  • 代码更少(增加了新的语法 )

2.强大的Stream API

  • 并行流和串行流

    并行流就是把一个内容分成多个数据块,并用不同的线程分别处理每个数据块的流。相比较串行的流,并行的流可以很大程度上提高程序的执行效率

    Java8中将并行进行了优化,我们可以很容易的对数据进行并行操作。Stream API可以声明性地通过parallel() 与 sequential()在并行流与顺序流之间进行切换。

  • 便于并行

3.Optional:

  • 最大化减少空指针异常
  • Nashorn引擎,允许在JVM上运行JS应用

4.时间和日期API

1、Lambda 表达式(重点)

关于Lambda 表达式和函数式接口,参考我的另一篇文章 Lambda表达式(重量级新特性)

2、函数式接口

1、使用lambda必须要具有接口,且要求接口中有且只有一个抽象方法。

无论是JDK内置的Runnable,Comparator接口还是自定义的接口,只有当接口中的抽象方法存在且唯一时,才能使用lambda。

2、使用lambda必须具有上下文推断。

也就是方法的参数或者局部变量类型必须为Lambda对应的接口类型,才能使用Lambda作为该接口的实例。

备注:有且仅有一个的抽象方法的接口,称为“函数式接口”。

//添加函数式接口的注解,表示限定该接口只能有一个方法体
@FunctionalInterface  
public interface Test01 {
  
  void test01();//方法体1

  //void test02();  
  //方法体2,如果添加了函数式接口注解,该接口有超过一个方法,将会编译报错
}

如何理解函数式接口

Java从诞生日起就是一直倡导“一切皆对象”,在Java里面面向对象(OOP)编程是一切。但是随着python、scala等语言的兴起和新技术的挑战,Java不得不做出调整以便支持更加广泛的技术要求,java不但可以支持OOP还可以支持OOF(面向函数编程)!

在函数式编程语言当中,函数被当做一等公民对待。在将函数作为一等公民的编程语言中,Lambda表达式的类型是函数。但是在Java8中,有所不同。在Java8中,Lambda表达式是对象,而不是函数,它们必须依附于一类特别的对象类型——函数式接口。

简单的说,在Java8中,Lambda表达式就是一个函数式接口的实例。这就是Lambda表达式和函数式接口的关系。也就是说,只要一个对象是函数式接口的实例,那么该对象就可以用Lambda表达式来表示。

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

2.1、Java内置四大函数式接口

函数式接口参数类型返回类型用途
Consumer消费型接口Tvoid对类型为T的对象应用操作,包含方法:void accept(T t)
Supplier
供给型接口
T返回类型为T的对象,包含方法: T get()
Function<T,R>
函数型接口
TR对类型为T的对象应用操作,并返回结果。结果是R类型的对象。包含方法: R apply(T t)
Predicate
断定型接口
Tboolean确定类型为T的对象是否满足某约束,并返回boolean值。包含方法:boolean test(T t)

下面用代码举两个例子

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.function.Consumer;
import java.util.function.Predicate;

public class Test02 {

    //1.消费型接口
    public void shopping(double money, Consumer<Double> con) {
        con.accept(money);
    }

    //2.断定型。根据给定的规则,过滤集合中的字符串。规则在Predicate制定
    public List<String> filterString(List<String> list, Predicate<String> pre) {
        List<String> filterString = new ArrayList<>();
        for (String s : list) {
            if (pre.test(s)) {  //这里只是判断,在使用的时候再创建规则传过来,灵活。
                filterString.add(s);
            }
        }

        return filterString;
    }

    //main方法
    public static void main(String[] args) {
        Test02 test02 = new Test02();

        //1.测试消费型接口
        //普通写法
        test02.shopping(400, new Consumer<Double>() {
            @Override
            public void accept(Double aDouble) {
                System.out.println("买了一个鸡肉卷,花了" + aDouble);
            }
        });

        //Lambda表达式
        test02.shopping(500, (money) -> System.out.println("买了一个汉堡包,花了" + money));


        //2.测试断定型接口
        List<String> list = Arrays.asList("冬瓜", "西瓜", "南瓜", "北瓜", "种豆", "得瓜");

        //普通写法
        List<String> list1 = test02.filterString(list, new Predicate<String>() {
            @Override
            public boolean test(String s) {
                //制定规则,如果s里面存在 瓜 ,返回true
                return s.contains("瓜");
            }
        });
        System.out.println(list1);

        //Lambda表达式
        List<String> list2 = test02.filterString(list, (s) -> !s.contains("瓜"));//这里是不存在瓜,返回true
        System.out.println(list2);
    }
}

还有非常多的内置函数接口,这只是用得比较多的四个。

学习这里的时候,觉得,哎好像我直接定义一个方法就能实现同样的功能了吖,为什么还要这么复杂,还要多写这些函数式接口。

后面发现,使用这些接口,能非常的灵活。比如断定型,假如有多种过滤规则,列如上面的,一个过滤含有“瓜”的字符串,一个过滤不含有的。如果我们不使用函数式接口的话,那就是写两个方法,而且代码大部分都是一样的,造成了冗余,如果是有几十个几百个规则呢?

而函数式接口就是提供了一个模板,我们只需要在使用的时候,顺便将规则传进来就可以了。并不需要修改源码。

3、方法引用和构造器引用(Stream API中用到)

3.1、方法引用

方法引用(Metod References)

  • 当要传递给Lambda体的操作,已经有实现的方法了,可以使用方法引用!
  • 方法引用可以看做是Lambda表达式深层次的表达。换句话说,方法引用就是Lambda表达式,也就是函数式接口的一个实例,通过方法的名字来指向一个方法,可以认为是Lambda表达式的一个语法。
  • 要求:实现接口的抽象方法的参数列表和返回值类型,必须与方法引用的方法的参数列表和返回值类型保持一致!
  • 格式:使用操作符::将类(或对象)与方法名分隔开来。
  • 如下三种主要使用情况:
    • 对象::实例方法名
    • 类::静态方法名
    • 类::实例方法名

看不懂。往下看代码,待会再回来看。

用代码实现三种使用情况,将会用到上面的四个内置函数式接口。

先建一个实体类Employee

import java.util.Objects;
//定义一个员工实体类
public class Employee {
    private int id;
    private String name;
    private int age;
    private double salary;

    public Employee() {
        System.out.println("调用了无参构造");
    }

    public Employee(int id) {
        this.id = id;
        System.out.println("调用了id参数构造");
    }

    public Employee(int id, String name) {
        this.id = id;
        this.name = name;
        System.out.println("调用了双参构造");
    }

    public Employee(int id, String name, int age, double salary) {
        this.id = id;
        this.name = name;
        this.age = age;
        this.salary = 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;
    }

    @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;
        return id == employee.id &&
                age == employee.age &&
                Double.compare(employee.salary, salary) == 0 &&
                Objects.equals(name, employee.name);
    }

    @Override
    public int hashCode() {
        return Objects.hash(id, name, age, salary);
    }
}

提供测试数据类:

package com.yong.method;

import java.util.ArrayList;
import java.util.List;

/**
 * @Author cyh
 * @Date 2021/8/23 16:52
 */

/**
 * 提供测试数据
 */
public class EmployeeData {
    public static List<Employee> getEmployees() {
        List<Employee> list = new ArrayList<>();
        list.add(new Employee(1001,"貂蝉",30,9808));
        list.add(new Employee(1002,"西施",32,5132));
        list.add(new Employee(1003,"杨玉环",18,4567));
        list.add(new Employee(1004,"王昭君",32,7845));
        list.add(new Employee(1005,"西门庆",22,6954));
        list.add(new Employee(1006,"菠萝吹雪",62,4521));
        list.add(new Employee(1007,"韩信",27,9874));
        list.add(new Employee(1008,"王大锤",48,2135));

        return list;
    }
}

测试:

import java.io.PrintStream;
import java.util.Comparator;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Supplier;

/**
 * 方法引用的使用
 * 1.对象::实例方法名
 * 2.类::静态方法名
 * 3.类::实例方法名
 */
public class Demo01 {
    public static void main(String[] args) {
        Demo01 method = new Demo01();

        //1
        method.test01();
        method.test01_1();

        //2
        method.test02();

        //3
        method.test03();
        method.test03_01();
    }

    /**
     * 1.当要传递给Lambda体的操作,已经有实现的方法了,可以使用方法引用!
     * 对象::实例方法
     */
    public void test01() {
        //lambda写法
        Consumer<String> con1 = s -> System.out.println(s);//打印s的值,s的值哪里来
        con1.accept("乌鸦坐飞机");//s的值从这里来,accept方法接收一个值给s


        /**方法引用写法
         *
         * System是一个类
         * System.out: out是PrintStream类型的一个常量
         * void println:println是PrintStream的一个方法
         * 如果使用这个,那么这种情况就是lambda已经有实现的方法了,下面使用方法引用
         * 对象::实例方法名
         */
        PrintStream ps = System.out;
        Consumer<String> con2 = ps::println;  //Consumer无返回值
        con2.accept("老鹰捉小鸡");

        /**运行结果
         * 乌鸦坐飞机
         * 老鹰捉小鸡
         */
    }

    public void test01_1() {
        //再举一个例子说明,带返回值的使用Supplier
        Employee employee = new Employee(1100, "张三", 45, 2255);

        //lambda
        Supplier<String> sup1 = () -> employee.getName(); //注意泛型和返回类型保持一致
        System.out.println(sup1.get());  //输出:张三

        //方法引用
        Supplier<String> sup2 = employee::getName;
        System.out.println(sup2.get());  //输出:张三
    }

    /**
     *  2.类::静态方法名
     *  Integer中的compare是静态方法,小于返回-1,等于返回0,大于返回1
     *  static int compare(int x, int y)
     */
    public void test02(){
        //lambda
        Comparator<Integer> com1 = (t1, t2) -> Integer.compare(t1, t2);
        System.out.println(com1.compare(33,44));  // -1

        //类::静态方法名
        Comparator<Integer> com2 = Integer::compare;
        System.out.println(com2.compare(63,44));  // 1
    }

    /**
     * 3.类::实例方法名
     * String中的compareTo是一个实例方法
     * int compareTo(String anotherString)
     */
    public void test03(){
        //lambda
        Comparator<String> com1 = (s1, s2) -> s1.compareTo(s2);
        System.out.println(com1.compare("abc", "abc"));//返回的是比较的第一个不同的ASCII值的差值,相同返回0

        //类::实例方法名
        Comparator<String> com2 = String::compareTo;
        System.out.println(com2.compare("ccc", "cbb"));
    }

    public void test03_01(){
        Employee employee = new Employee(1001, "李四", 22, 8888);

        //lambda
        Function<Employee, String> fun1 = employee1 -> employee.getName();
        System.out.println(fun1.apply(employee));//李四

        //类::实例方法名
        Function<Employee, Integer> fun2 = Employee::getId;  //注意这里是类Employee,而不是对象employee
        System.out.println(fun2.apply(employee));//1001

    }
}

3.2、构造器引用

一、构造器引用

和方法引用类似,函数式接口的抽象方法的形参列表和构造器的形参列表一致。抽象方法的返回值类型即为构造器所属的类的类型。

直接看代码

import java.util.function.BiFunction;
import java.util.function.Function;
import 以上是关于Java8新特性的主要内容,如果未能解决你的问题,请参考以下文章

2020了你还不会Java8新特性?Java 8新特性介绍

java8新特性——Lambda表达式

Java8新特性

java8新特性总结

Java8新特性

Java8新特性-官方库新特性