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

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

前言

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

Java8新特性

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

函数式接口

  • 别看这个名字花里胡哨的,其实就是指有且仅有一个抽象方法的接口。这类接口在Java8之前其实也接触过一些,比如多线程常用的java.lang.Runnable以及集合中常用的“辅助”java.util.Comparator等。
  • Java8开始,java对函数式接口增加了一个预制注解@FunctionalInterface。当给接口加上了给注解,则在接口不满足函数式接口时,会出现报错。
  • 此外,Java8专门增加了一个包java.util.function。该包中包含了很多Java官方提供的函数式接口。
  • 下面列举一些常用的:
接口名称接口方法功能介绍
Consumervoid accept(T t)用于实现有一个参数,但没有返回值的方法。
SupplierT get()用于实现没有参数和返回值的方法。
Function<T,R>R apply(T t)用于实现有参数和返回值的方法。
Predicateboolean 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<? extends T> s)等。
  • Stream的逻辑处理方法:
方法声明功能介绍
Stream filter(Predicate<? super T> predicate)返回一个包含匹配元素的流
Stream distinct()返回去重之后的流
Stream limit(long maxSize)返回最多前指定数量个元素的流
Stream skip(long n)返回跳过前n个元素后的流
Stream map(Function<? super T,? extends R> mapper)返回对每个元素处理之后,新元素组成的流。也就是所谓的映射。
Stream sorted()返回自然排序后的流
Stream sorted(Comparator<? super T> comparator)返回比较器排序后的流
  • Stream的终止操作:
方法声明功能介绍
Optional findFirst()返回该流的第一个元素
boolean allMatch(Predicate<? super T> predicate)判断所有元素是否匹配指定条件
boolean noneMatch(Predicate<? super T> predicate)判断所有元素是否不匹配指定条件
Optional max(Comparator<? super T> comparator)根据比较器返回最大元素
Optional min(Comparator<? super T> comparator)根据比较器返回最小元素
long count()返回元素的个数
void forEach(Consumer<? super T> 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<? super T,? extends U> 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编译器,可以通过在项目上右键直接创建模块

创建模块

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

模块名

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

创建模块信息文件

  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++){
            
        }
    }
}
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值