Java基础-Java8新特性
1|0一、Lambda 表达式
在了解 Lambda 之前,首先回顾以下Java
的方法。
Java
的方法分为实例方法,例如:Integer
的equals()
方法:
还有静态方法,例如:Integer
的parseInt()
的方法:
无论是实例方法还是静态方法,本质上都相当于过程式语言的函数。例如 C 函数:
只不过 Java 的实例方法隐含地传入了一个this
变量,即实例方法总是有一个隐含参数this
。
函数式编程(Functional Programming)是把函数作为基本运算单元,函数可以作为变量,可以接收函数,还可以返回函数。历史上研究函数式编程的理论是 Lambda 演算,所以我们经常把支持函数式编程的编码风格称为 Lambda 表达式。
1|11. Lambda 表达式及优点
Lambda 是一个匿名函数,我们可以把 Lambda 表达式理解为是一段可以传递的代码(将代码像数据一样进行传递)。使用它可以写出更简洁、更灵活的代码。作为一种更紧凑的代码风格,使 Java 的语言表达能力得到了提升。
在Java
程序中,我们遇到了许多单方法的接口,即:一个接口只定义了一个方法:
Comparator
Comsumer
Runnable
Callable
以Runnable
为例,当我们想要创建新的线程,或者要调用run()
方法时,需要创建Runnable
实例,以匿名类方式编写如下:
// 匿名内部类
Runnable r1 = new Runnable(){
@Override
public void run(){
System.out.println("Hello World!");}
};
上方写法比较繁琐,将单方法的接口转换成Lambda表达式
,为:
1|22. Lambda 语法
在 Java 8 语言中引入的一种新的语法元素和操作符。这个操作符为 ->
, 该操作符被称为Lambda 操作符或箭头操作符。它将 Lambda
分为两个部分:
->
左侧:指定了Lambda
表达式需要的参数列表 (接口中的抽象方法的形参列表)。->
右侧:指定了Lambda
体,是抽象方法的实现逻辑,也即Lambda
表达式要执行的功能。(其实就是重写抽象方法的方法体)
1|0语法格式
-
没有参数、没有返回值
-
需要一个参数、但是没有返回值
-
数据类型可以省略,因为可以有编译器推断出,称为
类型推断
。 -
若只需要一个参数时,参数的小括号可以省略
-
若需要两个或两个以上的参数,多条执行语句,并且有返回值
-
Lambda
体只有一条语句时,return
和{}
若有,都省略
Lambda
表达式的本质:作为接口的实例(失去了接口就失去了意义)。而且这个接口只有一个抽象方法,叫做函数式接口(单方法接口)
1|33. 类型推断
上述Lambda表达式
中的参数类型都是由编译器推断得出的。
Lambda 表达式中无需指定类型,程序依然可以编译,这是因为javac
根据程序的上下文,在后台推断出了参数的类型。
Lambda 表达式的类型依赖于上下文环境,是由编译器推断出来的。这就是所谓的类型推断
。
2|0二、函数式接口
2|1什么是函数式接口
只包含一个抽象方法的接口,称为函数式接口。
- 你可以通过
Lambda
表达式来创建该接口的对象。(若Lambda
表达式抛出一个受检异常 (即:非运行时异常),那么该异常需要在目标接口的抽象方法上进行声明)。 - 我们可以在一个接口上使用
@FunctionalInterface
注解,这样做可以检查它是否是一个函数式接口。同时javadoc
也会包含一条声明,说明这个接口是一个函数式接口。
不加
@FunctionalInterface
,实际上也是函数式接口,但是当显式的加
@FunctionalInterface
之后,就不可以声明两个或两个以上个方法。
在java.util.function
包下定义了 Java 8 的丰富的函数式接口
2|2如何理解函数式接口
简单说:在 Java8 中,Lambda
表达式就是一个函数式接口的实例。
这就是Lambda
表达式和函数式接口的关系,也就是说,只要一个对象是函数式接口的实例,那么该对象接可以用Lambda
表达式来表示。
所以以前用匿名实现类表示的现在都可以用 Lambda 表达式来写。
@FunctionalInterface
public interface Runnable{
public abstract void run();
}
2|3自定义函数式接口
@FunctionalInterface
public interface MyNumber{
public double getValue();
}
在函数式接口中使用泛型:
@FunctionalInterface
public interface MyFunction<T>{
public T getValue(T t);
}
2|4Lambda 参数作为参数传递
转换成Lambda
表达式的形式:
public static void main(String[] args){
String abcd = "abcd";
String string = method(str -> str.toUpperCase(), abcd);}
作为参数传递 Lambda 表达式:为了将 Lambda 表达式作为参数传递,接收 Lambda 表达式的参数类型必须是与该 Lambda 表达式兼容的函数式接口的类型。
2|5Java 内置的函数式接口
简单的两个例子:
其余的函数式接口,相当于是以上四种的变种:
3|0三、方法引用和构造器引用
3|1方法引用
1|0对方法引用的理解
方法引用可以看作是Lambda
表达式深层次的表达。
换句话说:方法引用本质上就是Lambda
表达式,也就是函数式接口的一个实例,通过方法的名字来指向一个方法,可以认为是Lambda
表达式的另一种写法。
具体来说:什么时候可以使用方法引用?
- 当要传递给
Lambda
体的操作,已经有实现的方法了,可以使用方法引用。 - 所谓方法引用,是指如果某个方法的参数列表和返回值和函数式接口的抽象方法恰好一致,就可以直接传入方法引用。
1|0方法引用的语法要求及格式:
方法引用的格式:使用操作符::
将类(或对象)与方法名分隔开来。即:类(或对象)::方法名
.
方法引用主要有三种使用情况:
- 情况一:对象:: 实例方法名
- 情况二:类:: 静态方法名
- 情况三:类:: 实例方法名
方法引用的使用要求:实现接口的抽象方法的参数列表和返回值类型,必须与方法引用的方法的参数列表和返回值类型保持一致。
简单几个小例子:
1|0方法引用的简单使用
使用Lambda
表达式,我们可以不必编写FunctionalInterface
接口的实现类,从而简化代码,实际上除了Lambda
表达式,我们还可以直接传入方法引用,例如:
上述代码在Arrays.sort()
中直接传入了静态方法cmp
的引用,用Main::cmp
表示,其中Main
是类名,cmp
是静态方法名,属于类::静态方法名
类型。
因为Comparator<String>
接口定义的方法是int compare(String, String)
,和静态方法int cmp(String, String)
相比,除了方法名之外,方法参数列表和返回类型相同,因此,我们说两者的签名一致,可以把方法名作为Lambda
表达式传入:
Comparator<String> comparator = (o1, o2) -> o1.compareTo(o2);
Arrays.sort(array, comparator);
// 但实际上 `int cmp(String, String)` 与 `comparator` 的抽象方法的签名相同
Arrays.sort(array, Main::cmp);
在这里,方法签名只看参数类型和返回值类型,不看方法名称,也不看类的继承关系。
我们将代码改成如下格式:
public class Main{
public static void main(String[] args){String[] array = new String[]{"Apple","Orange","Banana","Lemon"};
Arrays.sort(array, String::compareTo);
System.out.println(String.join(",", array));}
}
String::compareTo
方法也符合Lambda
定义。观察String.compareTo()
的方法定义:
这个方法的签名只有一个参数,为什么和int Comparator<String>.compare(String, String)
能匹配呢?
因为实例方法有一个隐含的this
参数,String
类的compareTo()
方法在实际调用的时候,第一个隐含参数总是传入this
,相当于静态方法,所以情况三:类::实例方法
,就是说的此类情景:
当函数式接口方法的第一个参数是需要引用方法的调用者,并且第二个参数是需要引用方法的参数 (或无参数) 时:
ClassName::methodName
所以String.compareTo()
方法也可以作为方法引入传入。
3|2构造器引用
除了可以引用静态方法和实例方法之外,我们还可以引用构造方法。
简单看一个小例子:如果要把一个List<String>
转换成为List<Person>
,传统的做法代码如下:
class Person{
String name;
public Person(String name){
this.name = name;
}
public String toString(){
return "Person:" + this.name;
}
}
List<String> names = List.of("Bob", "Alice", "Tim");
List<Person> persons = new ArrayList<>();
for(String name : names){
persons.add(new Person(name));
}
要更简单地实现String
到Person
的转换,我们可以引用Person
的构造方法:
public class Main{
public static void main(String[] args){
List<String> names = List.of("Bob","Alice","Tim");
List<Person> persons = names.stream().map(Person::new).collect(Collectors.toList());
System.out.println(persons);
}
}
构造器引用:ClassName::new
与函数式接口相结合,自动与函数式接口中方法兼容。
可以把构造器引用赋值给定义的方法,要求构造器参数列表要与接口中抽象方法的参数列表一致,且方法的返回值即为构造器对应类的对象。
3|3数组引用
数组引用格式:type[]::new
4|0四、Stream API
4|1什么是 Stream
Java8 中有两大最为重要的改变。第一个是Lambda表达式
,另外一个则是Stream API
。
Stream
是 Java8 中处理集合的关键抽象概念,它可以指定你希望对集合进行的操作,可以执行非常复杂的查找、过滤、映射数据等操作。
使用Stream API
对集合数据进行操作,就类似于使用 SQL 执行的数据库查询。
也可以使用Stream API
来并行执行操作,简言之:Stream API
提供了一种高效且易于使用的处理数据的方式。
Stream
实际上是数据渠道,用于操作数据源(集合、数组等)所生成的元素序列。
4|2为什么要使用 Stream
实际开发中,项目中多数数据源都是来自于MySQL、Oracle
等,但现在数据源可以更多了,有MongDB、Redis
,而这些NoSQL
的数据就需要 Java 层面去处理。
Stream
和Collection
集合的区别:
Collection
是一种静态的内存数据结构,而Stream
是有关计算的。- 前者是主要面向内存,存储在内存中,后者主要是面向
CPU
,通过CPU
实现计算。
Stream
自己不会存储元素Stream
不会改变源对象。相反,他们会返回一个持有结果的新Stream
Stream
操作是延迟执行的,这意味着他们会等到需要结果的时候才执行
4|3Stream 的操作三个步骤:
1|01. 创建 Stream 流
我们通过四种方法可以获得Stream
流:
-
通过集合创建
Stream
流,Java8 中的Collection
接口被扩展,提供了两个获取流的方法:default Stream<E> stream(): 返回一个顺序流; default Stream<E> parallelStream(): 返回一个并行流; -
通过数据创建
Stream
流,Java8 中的Arrays
的静态方法stream()
可以获取数组流:重载形式,能够处理对应的基本类型的数组:
// 基本数据类型 int[] arr = new int[]{1,2,3,4,5,6}; IntStream intStream = Arrays.stream(arr); // 其余类型采用泛型 Employee e1 = new Employee(1001, "Tom"); Employee e2 = new Employee(1002, "Jerry"); Employee[] arr1 = new Employee[]{e1, e2}; Stream<Employee> stream = Arrays.stream(arr1); -
通过
Stream
的of()
方法,通过显示值创建一个流,它可以接收任意数量的参数:创建
Stream
最简单的方式,传入可变参数即可创建一个能输出确定元素的Stream
流。但实际上没有什么实际用途,但是在测试的时候很方便。 -
通过调用静态方法
Stream.iterate()
和Stream.generate()
创建无限流:// 迭代 public static<T> Stream<T> iterate(final T seed, final UnaryOperator<T> f); // 生成 public static<T> Stream<T> generate(Supplier<T> s);
1|02. 中间操作
多个中间操作可以连接起来形成一个流水线,除非流水线上触发终止操作,否则中间操作不会执行任何的处理。而在终止操作时一次性全部处理,称为惰性求值
。
1|01. 筛选与切片
filter(Predicate p):接收 `Lambda`,从流中排除某些元素;
distinct():筛选,通过流所生成元素的 hashCode() 和 equals() 去除重复元素;
limit(long maxSize):截断流,使其元素不超过给定数量;
skip(long n):跳过元素,返回一个扔掉了前 n 个元素的流。若流中元素不足 n 个,则返回一个空流。与 limit(n) 互补。
1|02. 映射
map(Function f):接收一个函数作为参数,该函数会被应用到每个元素上,并将其映射成一个新的元素。 flatMap(Function f):接收一个函数作为参数,将流中的每个值都换成另一个流,然后把所有流连接成一个流
1|03. 排序
sorted():产生一个新流,其中按自然顺序排序 sorted(Comparator com):产生一个新流,其中按比较器顺序排序
1|03. 终止操作(终端操作)
终端操作会从流的流水线生成结果。其结果可以是任何不是流的值,例如:List、Integer
,甚至是void
。
流进行了终止操作后,不能再次使用。
1|01. 匹配与查找
allMatch(Predicate p): 检查是否匹配所有元素 anyMatch(Predicate p): 检查是否至少匹配一个元素 noneMatch(Predicate p): 检查是否没有匹配所有元素 findFirst(): 返回第一个元素 findAny(): 返回当前流中的任意元素 count(): 返回流中元素总数 max(Comparator c): 返回流中最大值 min(Comparator c): 返回流中最小值 forEach(Consumer c): 内部迭代 (使用 Collection 接口需要用户去做迭代,称为外部迭代。相反,Stream API 使用内部迭代——它帮你把迭代做了)
1|02. 规约
reduce(T iden, BinaryOperator b): 可以将流中元素反复结合起来,得到一个值。返回 T
reduce(BinaryOperator b): 可以将流中元素反复结合起来,得到一个值。返回 Optional<T>
1|03. 收集
Collector
接口中方法的实现决定了如何对流执行收集的操作 ( 如收集到List、Set、Map
)。- 另外,
Collectors
实用类提供了很多静态方法,可以方便地创建常见收集器实例,具体方法与实例如下表:
// 查找工资大于 6000 的员工,返回一个 list
List<Employee> employees = EmployeeData.getEmployees();
List<Employee> employeeList = employees.stream().filter(e -> e.getSalary() > 6000).collect(Collectors.toList());
employeeList.forEach(System.out::println);
4|4并行流
将一个内容分为多个数据块,使用不同的线程分别处理每个数据块的流,并且含有工作窃取,即一个核对应的任务完成后,会从其他核偷取任务来执行,大大提高了执行效率。
-
使用
parallel
切换为并行流 -
使用
sequential
切换为串行流
在之前有一个ForkJoin
框架,这个而框架就实现了对一个任务的拆分和合并,大大提高了处理速度。
在 Java8 之后我们可以使用并行流来处理 ( 底层仍然是ForkJoin
框架 )
运算一千亿个数循环相加,用时 19s,CPU 利用率基本占满。
5|0Optional 类
5|1为什么要使用 Optional 类
到目前为止,臭名昭著的空指针异常是导致 Java 应用程序失败的最常见原因。
以前,为了解决空指针异常,Google
公司著名的Guava
项目引入了Optional
类,Guava 通过使用检查空值的方式来防止代码污染。
它鼓励程序员写更干净的代码。受到Google Guava
的启发,Optional
类已经成为 Java8 类库的一部分。
5|2什么是 Optional 类
Optional
类(java.util.Optional)
是一个容器类,它可以保存类型T
的值,代表这个值存在。或者仅仅保存null
,表示这个值不存在。
原来用null
表示一个值不存在,现在Optional
可以更好的表达这个概念。并且可以避免空指针异常。
Optional
类的Javadoc
描述如下:这是一个可以为null
的容器对象。如果值存在,则isPresent()
方法会返回true
,调用get()
方法会返回该对象。
5|3Optional 类提供的方法
Optional 提供很多有用的方法,这样我们就不用显式进行空值检测。
1|0创建 Optional 类对象的方法
Boy boy = new Boy();
//boy = null;
// 创建一个 Optional 实例, t 必须为非空,如果为空会报空指针错误
Optional<Boy> boy1 = Optional.of(boy);
System.out.println(boy1);
// 创建一个空的 Optional 实例
Optional<Object> empty = Optional.empty();
System.out.println(empty);
// 创建 Optional 类对象的方法,t 可以为空
boy = null;
Optional<Boy> boy2 = Optional.ofNullable(boy);
System.out.println(boy2);
1|0判断 Optional 容器是否包含对象
boolean isPresent(): 判断是否包含对象;
void ifPresent(Consumer<? super T> consumer): 如果有值,就执行 Consumer 接口的实现代码,并且该值会作为参数传给他;
Boy boy = new Boy();
boy = null;
Optional<Boy> boy2 = Optional.ofNullable(boy);
// 该 Optional 类是否包含对象
System.out.println(boy2.isPresent());
// 该 Optional 类是否为空
System.out.println(boy2.isEmpty());
// 当 Optional 不为空时,执行 consumer 的实现代码
boy2.ifPresent(System.out::println);
1|0获取 Optional 容器的对象
T get(): 如果调用对象包含值,返回该值,否则抛异常;
T orElse(T other): 如果有值将其返回,否则返回指定的 other 对象;
T orElseGet(Supplier<? extends T> other): 如果有值则将其返回,否则返回由 Supplier 接口实现提供的对象;
T orElseThrow(Supplier<? extends T> exceptionSupplier): 如果有值则将其返回,否则抛出由 Supplier 接口实现提供的异常;
Boy boy = new Boy("guosiliang",20);
Optional<Boy> boy1 = Optional.ofNullable(boy);
//T get(): 如果调用对象包含值,返回该值,如果为空,则抛出异常
System.out.println(boy1.get());
System.out.println("--------------------------");
//T orElse(T other): other 算是备胎,如果有值将其返回,如果没有值则返回 other
Boy boy2 = null;
Optional<Boy> boy21 = Optional.ofNullable(boy2);
Boy boy3 = boy21.orElse(boy);
System.out.println(boy3);
System.out.println("--------------------------");
//T orElseGet(Supplier<? extends T> other): 如果有值将其返回,否则返回由 Supplier 接口实现提供的对象
Optional<Boy> boy22 = Optional.ofNullable(boy2);
Boy boy4 = boy22.orElseGet(Boy::new);
System.out.println(boy4);
System.out.println("--------------------------");
//T orElseThrow(Supplier<? extends X> exceptionSupplier): 如果有值将其返回,否则抛出由 Supplier 接口实现提供的异常
boy2 = new Boy();
Optional<Boy> boy23 = Optional.ofNullable(boy2);
Boy boy5 = null;
try {
boy5 = boy23.orElseThrow(Exception::new);} catch (Exception e) {e.printStackTrace();
}
EOF
本文链接:https://www.cnblogs.com/guosiliang/p/13583711.html
关于博主:评论和私信会在第一时间回复。或者直接私信我。
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
声援博主:如果您觉得文章对您有帮助,可以点击文章右下角【推荐】一下。您的鼓励是博主的最大动力!