版本 |
说明 |
发布日期 |
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) {
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++){
}
}
}