版本 说明 发布日期 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官方提供的函数式接口。 下面列举一些常用的:
接口名称 接口方法 功能介绍 Consumer void accept(T t) 用于实现有一个参数,但没有返回值的方法。 Supplier T get() 用于实现没有参数和返回值的方法。 Function<T,R> 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) {
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的使用基本就固定的三个步骤:
使用一个数据源创建Stream对象; 对Stream进行各种处理; 对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) 返回比较器排序后的流
方法声明 功能介绍 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之后,我们可以如何实现这样的需求:
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. 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) ;
}
} ) ;
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) ;
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 , "大侠" ) ) ;
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) ;
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< String> optionalS = Optional. ofNullable ( str) ;
System. out. println ( optionalS. map ( String: : length) . orElse ( 0 ) ) ;
}
}
运行结果如下。就两行代码就实现了,不服不行啊!顺道一提,Java11中String类也增加了orElse方法,用法是一模一样的。
8
0
Java9新特性
模块化
随着java的发展,一个项目的大小越来越大,导致启动或者运行的时候效率下降。例如多时候,一些代码之间联系性很高,而一些代码之间联系性很低,如果同一时间都将他们全部放在一起管理、运行,会导致效率的低下。 为了解决这个问题,java9引入了模块化。同一个项目下面可以有多个模块,不同模块之间是相互独立的,资源的加载也是分开的,从而减少内存的开销,提高项目的可维护性。
模块化的使用
对于IDEA编译器,可以通过在项目上右键直接创建模块
给模块更改名字和路径
建好之后,两个模块就分别会有自己的源路径src。默认情况下,不同的模块之间的类无法相互访问。那如果我们想访问另一个模块的类怎么办呢?这个时候就需要配置一个特殊的模块信息文件。
例如现在想在NewModule模块访问JavaPractice模块的类,则在两个模块中如下创建文件:
在JavaPractice模块信息中写入如下代码,将该包暴露给其他模块。我在这个包下面封装了一个Person类,待会儿用这个类进行测试。
module JavaPractice {
exports com. JavaSE. NewFeature. Java8. Stream;
}
在NewModule模块信息中写入如下代码,以引入JavaPractice模块。写好之后会报错,因为还没有在项目配置中依赖JavaPractice模块。根据修改意见add dependency就好。
module NewModule {
requires JavaPractice;
}
在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) ;
}
}
运行结果如下。可以看到这样就能使用其他模块的类了。
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;
}
} ;
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" ) ;
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 ( var str: list) {
}
for ( var i = 0 ; i < list. size ( ) ; i++ ) {
}
}
}