Java基础(九)——Java8之后重要的新特性

版本 说明 发布日期
1.0 发布文章第一版 2021-02-20

文章目录

  • 前言
  • Java8新特性
    • 函数式接口
    • Lambda表达式
    • 方法引用
      • 类的非静态方法引用
      • 构造器的引用
      • 数组的引用
    • Stream接口
      • Stream的常用方法
      • 案例
        • 打印出集合中所有成年人(测试filter和forEach)
        • 判断集合中是否都是成年人(测试noneMatch)
        • 将集合中所有人的年龄累加并打印(测试map和reduce)
    • Optional类
      • 来个栗子
  • Java9新特性
    • 模块化
      • 模块化的使用
    • 震惊!匿名内部类泛型还能优化?到底是怎么回事呢?让我们一起来看一看吧~
    • 集合工厂方法
    • 流拷贝方法
  • Java10新特性
    • 局部变量类型推断

前言

  • 这篇文章是我个人的学习笔记,可能无法做到面面俱到,也可能会有各种纰漏。如果任何疑惑的地方,欢迎一起讨论~
  • 如果想完整阅读这个系列的文章,欢迎关注我的专栏《Java基础系列文章》~

Java8新特性

  • Java8是一个重要版本。虽然该版本早于2014年发布,但是目前依然有很多企业(比如俺滴老东家)依然在使用。
  • 这个版本对包含语言、编译器、库、工具和JVM等在内的各个方面的增添了十多个新特性。尤其是围绕“函数式接口”,搞出了很多新鲜玩意儿。

函数式接口

  • 别看这个名字花里胡哨的,其实就是指有且仅有一个抽象方法的接口。这类接口在Java8之前其实也接触过一些,比如多线程常用的java.lang.Runnable以及集合中常用的“辅助”java.util.Comparator等。
  • Java8开始,java对函数式接口增加了一个预制注解@FunctionalInterface。当给接口加上了给注解,则在接口不满足函数式接口时,会出现报错。
  • 此外,Java8专门增加了一个包java.util.function。该包中包含了很多Java官方提供的函数式接口。
  • 下面列举一些常用的:
接口名称 接口方法 功能介绍
Consumer void accept(T t) 用于实现有一个参数,但没有返回值的方法。
Supplier T get() 用于实现没有参数和返回值的方法。
Function R apply(T t) 用于实现有参数和返回值的方法。
Predicate boolean test(T t) 用于实现有参数和返回值,并且返回值是布尔类型的方法。

Lambda表达式

  • Java8专门为函数式接口的实现和实例化提供了一个语法糖——Lambda表达式。当然,Lambda表达式并不是仅仅用于此的语法糖,也不是匿名内部类的语法糖。
  • 看过我之前文章的小伙伴应该对这个不陌生了,其写法是:接口名 变量名 = (参数列表) -> {方法体};
  • 举个栗子:
        Function<String, String> consumer = (String str) -> {
     
            return str;
        };
  • 此外,Lambda表达式在基础语法之上,还有些地方可以省略:
    • 参数列表中的变量类型始终可以省略。(因为重写接口方法,变量类型都是已知的啦);
    • 当参数列表中有且仅有一个参数时,()可以省略;
    • 当且仅当方法体只有一行代码时,{}可以省略;
    • 当且仅当方法体只有一行代码,且这一行代码是一个return时,{}和return关键字都可以省略。
  • 所以,上面的栗子可以简化成下面这样。emmm,可能看起来有点过分了,但却是就是可以这么简单。
        Function<String, String> consumer = str -> str;

方法引用

  • 这个也是针对函数式接口新增的语法糖。
  • 先说说这玩意儿的意义吧。在特定场景下,我们可以把各路英雄齐聚一堂,也就是把来自各个类的方法聚集在一起,提取到同一个接口下面,从而可以很方便地利用多态,来对这些方法进行调用。例如下面要讲的流(这个流指的是Java8新出的特性,并不是大家熟知的输入输出流哈)。
  • 所谓方法引用,就是当函数式接口的实现方法中,只有一个方法调用或对象创建,则可以通过方法引用来简化代码。
  • 说起来有点抽象,大概就是下面这种感觉:
public class LambdaTest {
     
    public static void main(String[] args) {
     
        //正常的Lambda表达式写法
        Consumer<String> consumer = str -> System.out.println(str);
        consumer.accept("Lambda写法");

        //方法引用写法
        Consumer<String> consumer1 = System.out::println;
        consumer1.accept("方法引用写法");
    }
}
  • 下面来具体列举一下各种情况。由此可见,上面这个例子,就是对象的非静态方法引用。因为out是一个打印流对象,而println是这个对象的一个成员方法。
种类 语法
对象的非静态方法引用 ObjectName :: MethodName
类的静态方法引用 ClassName :: StaticMethodName
类的非静态方法引用 ClassName :: MethodName
构造器的引用 ClassName :: new
数组的引用 TypeName[] :: new
  • 对于前两种,都很好理解。但后面三种怎么用?请待我徐徐道来~

类的非静态方法引用

  • 当我们重写的方法中的一个入参作为非静态方法的调用对象,则可以使用类的非静态方法引用。
  • 举例如下。运行结果不重要,就不放了。因为o1是Integer入参之一,并且在方法体中是方法的调用者,所以可以如下简化。
public class LambdaTest {
     
    public static void main(String[] args) {
     
        //这是最简单的匿名内部类写法
        Comparator<Integer> comparator = new Comparator<Integer>() {
     
            @Override
            public int compare(Integer o1, Integer o2) {
     
                return o1.compareTo(o2);
            }
        };
        System.out.println(comparator.compare(1, 2));

        //类的非静态方法引用。
        comparator = Integer::compare;
        System.out.println(comparator.compare(2, 1));
    }
}

构造器的引用

  • 当重写的方法中,仅有一行new对象的语句,则可以如下简化:
public class LambdaTest {
     
    public static void main(String[] args) {
     
        //这是最简单的匿名内部类写法
        Supplier<Person> supplier = new Supplier<Person>() {
     
            @Override
            public Person get() {
     
                return new Person();
            }
        };
        System.out.println(supplier.get());

        //通过构造器的引用实现
        supplier = Person::new;
        System.out.println(supplier.get());
    }
}

数组的引用

  • 当重写的方法中,仅有一行创建并返回数组对象的语句,则可以如下简化:
public class LambdaTest {
     
    public static void main(String[] args) {
     
        //这是最简单的匿名内部类写法
        Function<Integer, Integer[]> function = new Function<Integer, Integer[]>() {
     
            @Override
            public Integer[] apply(Integer integer) {
     
                return new Integer[integer];
            }
        };
        System.out.println(Arrays.toString(function.apply(5)));

        //简化后
        function = Integer[]::new;
        System.out.println(Arrays.toString(function.apply(2)));
    }
}

Stream接口

  • 位于java.util.stream
  • 该接口对集合的增强,可以对集合元素进行复杂的查找、过滤、筛选等操作。
  • Stream接口借助于Lambda表达式和方法引用,极大提高编程效率和程序可读性。同时它提供串行和并行两种模式进行汇聚操作,并发模式能够充分利用多核处理器的优势。
  • Stream的使用基本就固定的三个步骤:
    1. 使用一个数据源创建Stream对象;
    2. 对Stream进行各种处理;
    3. 对Stream调用终止操作,以返回结果。

Stream的常用方法

  • Stream的创建方式通常有以下三种:
    • 通过集合对象中新增的获取流的方法:Stream stream()
    • 通过数组工具类中的静态方法,例如:static IntStream stream(int[] array)
    • 通过Stream接口的静态方法:static Stream of(T... values)static Stream generate(Supplier s)等。
  • Stream的逻辑处理方法:
方法声明 功能介绍
Stream filter(Predicate predicate) 返回一个包含匹配元素的流
Stream distinct() 返回去重之后的流
Stream limit(long maxSize) 返回最多前指定数量个元素的流
Stream skip(long n) 返回跳过前n个元素后的流
Stream map(Function mapper) 返回对每个元素处理之后,新元素组成的流。也就是所谓的映射。
Stream sorted() 返回自然排序后的流
Stream sorted(Comparator comparator) 返回比较器排序后的流
  • Stream的终止操作:
方法声明 功能介绍
Optional findFirst() 返回该流的第一个元素
boolean allMatch(Predicate predicate) 判断所有元素是否匹配指定条件
boolean noneMatch(Predicate predicate) 判断所有元素是否不匹配指定条件
Optional max(Comparator comparator) 根据比较器返回最大元素
Optional min(Comparator comparator) 根据比较器返回最小元素
long count() 返回元素的个数
void forEach(Consumer action) 对流中每个元素执行指定操作
Optional reduce(BinaryOperator accumulator) 将集合中所有元素结合

案例

打印出集合中所有成年人(测试filter和forEach)

  • 我这里就不用传统的写法了,直接看看在Java8之后,我们可以如何实现这样的需求:
//首先构造一个测试用的Person类
public class Person {
     
    private int age;
    private String name;

    public Person(int age, String name) {
     
        this.age = age;
        this.name = name;
    }

    public int getAge() {
     
        return age;
    }

    public void setAge(int age) {
     
        this.age = age;
    }

    public String getName() {
     
        return name;
    }

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

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

//然后是测试方法
public class Starter {
     
    public static void main(String[] args) {
     
        getAdults();
    }

    public static void getAdults() {
     
        //创建用于测试的集合
        ArrayList<Person> list = new ArrayList<>();
        list.add(new Person(17, "猪猪侠"));
        list.add(new Person(20, "蜘蛛侠"));
        list.add(new Person(23, "咕咕侠"));
        list.add(new Person(30, "大侠"));

        //先用比较容易理解的匿名内部类来实现,便于理解。
        //首先获取list的流对象
        //然后调用filter方法,实现Predicate接口,
        list.stream().filter(new Predicate<Person>() {
     
            @Override
            public boolean test(Person person) {
     
                return person.getAge() >= 18;
            }
        }).forEach(new Consumer<Person>() {
     
            @Override
            public void accept(Person person) {
     
                System.out.println(person);
            }
        });

        //使用方法引用和Lambda表达式来简化
        System.out.println("====================");
        list.stream().filter(person -> person.getAge() >= 18).forEach(System.out::println);
    }
}
  • 运行结果如下。方法引用、Lambda表达式和Stream的强大之处就不需要我多言了吧~如果我们用传统的迭代器来实现,还需要for循环啦,各种blabla的操作。在Java8加持之下,只需要一行代码就能实现需求。一个字:爽!
Person{age=20, name='蜘蛛侠'}
Person{age=23, name='咕咕侠'}
Person{age=30, name='大侠'}
====================
Person{age=20, name='蜘蛛侠'}
Person{age=23, name='咕咕侠'}
Person{age=30, name='大侠'}

判断集合中是否都是成年人(测试noneMatch)

  • 这个案例呢就得用到noneMatch或者match方法了,但是实现步骤还是大同小异。代码如下,Person类一样的,就省略了。
    public static void isAdults() {
     
        //创建用于测试的集合
        ArrayList<Person> list = new ArrayList<>();
        list.add(new Person(17, "猪猪侠"));
        list.add(new Person(20, "蜘蛛侠"));
        list.add(new Person(23, "咕咕侠"));
        list.add(new Person(30, "大侠"));

        //先用比较容易理解的匿名内部类来实现,便于理解。
        boolean result = list.stream().noneMatch(new Predicate<Person>() {
     
            @Override
            public boolean test(Person person) {
     
                return person.getAge() < 18;
            }
        });
        System.out.println("都是成年人吗?" + result);

        //使用方法引用和Lambda表达式来简化
        System.out.println("====================");
        result = list.stream().noneMatch(person -> person.getAge() < 18);
        System.out.println("都是成年人吗?" + result);
    }
  • 运行结果如下。讲这个例子主要是怕小伙伴们没有理解noneMatch是什么意思。当集合中没有任何元素匹配判断条件时,noneMatch就会返回true,否则返回false。
都是成年人吗?false
====================
都是成年人吗?false

将集合中所有人的年龄累加并打印(测试map和reduce)

  • 通过map方法,可以重新映射出一个由年龄组成的流,而reduce方法可以对集合中所有元素进行二元处理(比如累加)。
    public static void sumAge(){
     
        //创建用于测试的集合
        ArrayList<Person> list = new ArrayList<>();
        list.add(new Person(17, "猪猪侠"));
        list.add(new Person(20, "蜘蛛侠"));
        list.add(new Person(23, "咕咕侠"));
        list.add(new Person(30, "大侠"));

        //先用比较容易理解的匿名内部类来实现,便于理解。
        //先将集合映射为年龄集合。因为年龄是整数,所以需要使用Integer来存放
        //再用reduce来累加年龄
        Optional<Integer> result = list.stream().map(new Function<Person, Integer>() {
     
            @Override
            public Integer apply(Person person) {
     
                return person.getAge();
            }
        }).reduce(new BinaryOperator<Integer>() {
     
            @Override
            public Integer apply(Integer integer, Integer integer2) {
     
                return integer + integer2;
            }
        });
        System.out.println(result);

        //使用方法引用和Lambda表达式来简化
        System.out.println("====================");
        result = list.stream().map(Person::getAge).reduce(Integer::sum);
        System.out.println(result);
    }
  • 执行结果如下。
    • 至于Optional类是什么个东西,下面马上会讲。
    • BinaryOperator是BiFunction的子接口,也是一个函数式接口,用于将两个同类型入参转换为一个同类型的返回值。
    • 所以reduce是什么作用呢,也就可见一斑了。就是通过二元处理,来将集合中所有的元素,处理为一个元素。
Optional[90]
====================
Optional[90]

Optional类

  • 位于java.util。是一个容器,主要用于处理可能为空的对象,以简化传统的通过if/else判空代码。
  • 常用的方法如下:
方法声明 功能介绍
static Optional ofNullable(T value) 根据参数创建对象。
Optional map(Function mapper) 将Optional对象映射为另一种Optional对象。
T orElse(T other) 返回对象中的值,否则返回other指定的值。

来个栗子

  • 提供一个方法,打印字符串的长度,如果字符串为空,则长度视为0。
  • 如果使用传统的判断,就不够简洁。java8之后我们就可以用Optional来搞:
public class OptionalTest {
     
    public static void main(String[] args) {
     
        StringLength("12341324");
        StringLength(null);
    }

    public static void StringLength(String str){
     
        //创建Optional对象
        Optional<String> optionalS = Optional.ofNullable(str);

        //映射为字符串长度,并且获取返回值
        System.out.println(optionalS.map(String::length).orElse(0));
    }
}
  • 运行结果如下。就两行代码就实现了,不服不行啊!顺道一提,Java11中String类也增加了orElse方法,用法是一模一样的。
8
0

Java9新特性

模块化

  • 随着java的发展,一个项目的大小越来越大,导致启动或者运行的时候效率下降。例如多时候,一些代码之间联系性很高,而一些代码之间联系性很低,如果同一时间都将他们全部放在一起管理、运行,会导致效率的低下。
  • 为了解决这个问题,java9引入了模块化。同一个项目下面可以有多个模块,不同模块之间是相互独立的,资源的加载也是分开的,从而减少内存的开销,提高项目的可维护性。

模块化的使用

  1. 对于IDEA编译器,可以通过在项目上右键直接创建模块

Java基础(九)——Java8之后重要的新特性_第1张图片

  1. 给模块更改名字和路径

Java基础(九)——Java8之后重要的新特性_第2张图片

  1. 建好之后,两个模块就分别会有自己的源路径src。默认情况下,不同的模块之间的类无法相互访问。那如果我们想访问另一个模块的类怎么办呢?这个时候就需要配置一个特殊的模块信息文件。
    • 例如现在想在NewModule模块访问JavaPractice模块的类,则在两个模块中如下创建文件:

Java基础(九)——Java8之后重要的新特性_第3张图片

  1. 在JavaPractice模块信息中写入如下代码,将该包暴露给其他模块。我在这个包下面封装了一个Person类,待会儿用这个类进行测试。
module JavaPractice {
     
    //想要暴露给其他模块的包使用exports关键字来实现
    exports com.JavaSE.NewFeature.Java8.Stream;
}
  1. 在NewModule模块信息中写入如下代码,以引入JavaPractice模块。写好之后会报错,因为还没有在项目配置中依赖JavaPractice模块。根据修改意见add dependency就好。
module NewModule {
     
    //需要依赖的模块使用requires关键字来实现
    requires JavaPractice;
}
  1. 在NewModule中创建一个测试类,如下。
package com;

import com.JavaSE.NewFeature.Java8.Stream.Person;

public class Starter {
     
    public static void main(String[] args) {
     
        Person person = new Person(12, "帅");
        System.out.println(person);
    }
}
  1. 运行结果如下。可以看到这样就能使用其他模块的类了。
Person{age=12, name='帅'}

震惊!匿名内部类泛型还能优化?到底是怎么回事呢?让我们一起来看一看吧~

public class GenericityOptimization {
     
    public static void main(String[] args) {
     
        //以前的写法
        Function<String, String> function = new Function<String, String>() {
     
            @Override
            public String apply(String s) {
     
                return null;
            }
        };

        //java9的优化
        function = new Function<>() {
     
            @Override
            public String apply(String s) {
     
                return null;
            }
        };
    }
}
  • 看到区别了么?其实就是实例化语句的泛型可以省略了(因为必须得一样嘛)。所以其实就是很小的一个优化,一看就是老营销号了。

集合工厂方法

  • Java9给集合增加了静态工厂方法of,该方法创建的集合对象的元素是不可新增、删除和修改的,并且集合中的元素不能为null。
  • 简单测试一下:
public class CollectionTest {
     
    public static void main(String[] args) {
     
        List<String> list = List.of("null", "123");
        System.out.println(list);

        //假如想添加一个元素
        list.add("哇哦");
    }
}
  • 运行结果如下。当添加的元素为null或者想对集合中的元素进行修改的时候,编译不会报错,但是运行时会抛出异常。
[null, 123]
Exception in thread "main" java.lang.UnsupportedOperationException
	at java.base/java.util.ImmutableCollections.uoe(ImmutableCollections.java:71)
	at java.base/java.util.ImmutableCollections$AbstractImmutableCollection.add(ImmutableCollections.java:75)
	at JavaPractice/com.JavaSE.NewFeature.Java9.CollectionTest.main(CollectionTest.java:11)

流拷贝方法

  • 看过我之前讲流的小伙伴,肯定会对流拷贝的例子有印象。好消息!Java9把这个方法封装啦!到时候我们直接一个方法调用就完事儿~这个方法就是InputStream的transferTo。
  • 来看个例子就懂啦~流的copy so easy!
public class StreamTest {
     
    public static void main(String[] args) throws IOException {
     
        InputStream inputStream = new FileInputStream("D:\\吉他谱\\这一生关于你的风景\\这一生关于你的风景1.png");
        OutputStream outputStream = new FileOutputStream("D:\\吉他谱\\这一生关于你的风景\\test.png");

        //以前的话我们得在这儿用缓冲流写一堆拷贝的方法,现在直接一个方法调用就OK
        inputStream.transferTo(outputStream);
        
        inputStream.close();
        outputStream.close();
    }
}

Java10新特性

  • Java10其实是一个比较小的“大版本”。其中有两个比较关键的变更:局部变量类型推断和垃圾回收器的增强。不过垃圾回收涉及到JVM了,我还不太了解,所以这里就不讲啦。

局部变量类型推断

  • 有时候变量类型写起来会觉得好麻烦,于是Java10看不下去了,就整了个新活。
  • Java10可以使用var来代替变量类型,仅适用于有初始值的局部变量、增强for循环的索引和传统for循环的循环变量。不能使用于方法形参、方法返回类型、catch形参或任何其他类型的变量声明。
  • var不是关键字,意味着var可以继续用于方法名、包名,但var不能作为类或则接口的名字。
  • 给个栗子:
public class VarTest {
     
    public static void main(String[] args) {
     
        //代替局部变量类型
        var list = new ArrayList<String>();

        //代替增强for循环索引
        for(var str:list){
     

        }

        //代替传统for循环的循环变量
        for(var i = 0;i < list.size();i++){
     
            
        }
    }
}

你可能感兴趣的:(Java基础系列文章,java,intellij,idea)