Java 8 新特性——检视阅读
参考
Deprecated,请参考Java 8 实践篇
Oracle 公司于 2014 年 3 月 18 日发布 Java 8 ,它支持函数式编程,新的 JavaScript 引擎,新的日期 API,新的Stream API 等。
新特性
Java8 新增了非常多的特性,我们主要讨论以下几个:
- Lambda 表达式 − Lambda 允许把函数作为一个方法的参数(函数作为参数传递到方法中)。
- 方法引用 − 方法引用提供了非常有用的语法,可以直接引用已有Java类或对象(实例)的方法或构造器。与lambda联合使用,方法引用可以使语言的构造更紧凑简洁,减少冗余代码。
- 默认方法 − 默认方法就是一个在接口里面有了一个实现的方法。
- 新工具 − 新的编译工具,如:Nashorn引擎 jjs、 类依赖分析器jdeps。
- Stream API −新添加的Stream API(java.util.stream) 把真正的函数式编程风格引入到Java中。
- Date Time API − 加强对日期与时间的处理。
- Optional 类 − Optional 类已经成为 Java 8 类库的一部分,用来解决空指针异常。
- Nashorn, JavaScript 引擎 − Java 8提供了一个新的Nashorn javascript引擎,它允许我们在JVM上运行特定的javascript应用。
Lambda 表达式
Lambda 表达式,也可称为闭包,它是推动 Java 8 发布的最重要新特性。
Lambda 允许把函数作为一个方法的参数(函数作为参数传递进方法中)。
使用 Lambda 表达式可以使代码变的更加简洁紧凑。
语法
//lambda 表达式的语法格式如下:
(parameters) -> expression
//或
(parameters) ->{ statements; }
lambda表达式的重要特征:只要是可选的满足条件,都是可以省略的。
- 可选类型声明:不需要声明参数类型,编译器可以统一识别参数值。
- 可选的参数圆括号:一个参数无需定义圆括号,但多个参数需要定义圆括号。
- 可选的大括号:如果主体包含了一个语句,就不需要使用大括号。
- 可选的返回关键字:如果主体只有一个表达式返回值则编译器会自动返回值,大括号需要指定明表达式返回了一个数值。
使用 Lambda 表达式需要注意以下两点:
- Lambda 表达式主要用来定义行内执行的方法类型接口,例如,一个简单方法接口。在上面例子中,我们使用各种类型的Lambda表达式来定义MathOperation接口的方法。然后我们定义了sayHi的执行。
- Lambda 表达式免去了使用匿名方法的麻烦,并且给予Java简单但是强大的函数化的编程能力。
变量作用域
lambda 表达式只能引用标记了 final 的外层局部变量,这就是说不能在 lambda 内部修改定义在域外的局部变量,否则会编译错误。
在 Lambda 表达式当中不允许声明一个与局部变量同名的参数或者局部变量。
示例:
public class JavaTest {
public static void main(String[] args) {
//注意泛型要标注在前面List<Integer>
List<Integer> numList1 = new ArrayList<>();
numList1.add(80);
numList1.add(5);
numList1.add(3);
numList1.add(100);
List numList2 = new ArrayList<Integer>();
numList2.add(80);
numList2.add(5);
numList2.add(3);
numList2.add(100);
System.out.println("java 8");
Collections.sort(numList1,(a,b) -> a.compareTo(b));
System.out.println(numList1);
System.out.println("java 7");
Collections.sort(numList2, new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
return o1.compareTo(o2);
}
});
System.out.println(numList2);
}
}
public class LambdaTest {
public static void main(String[] args) {
// 类型声明 add
MathOperation add = (Integer a, Integer b) -> a + b;
// 不用类型声明 sub
MathOperation sub = (a, b) -> a - b;
// 大括号中的返回语句 muti
MathOperation muti = (a, b) -> {
return a * b;
};
MathOperation div = (a, b) -> a / b;
LambdaTest tester = new LambdaTest();
System.out.println("10 + 5 = " + tester.operate(10, 5, add));
System.out.println("10 - 5 = " + tester.operate(10, 5, sub));
System.out.println("10 x 5 = " + tester.operate(10, 5, muti));
System.out.println("10 / 5 = " + tester.operate(10, 5, div));
// 不用括号
HiService hi = str -> System.out.println("Hi " + str);
hi.sayHi("lambda");
// 用括号
HiService hi1 = (str) -> System.out.println("Hi " + str);
hi1.sayHi("world");
//报错信息:Local variable num defined in an enclosing scope must be final or effectively
/* String hello = "hello ";
HiService hi2 = str -> {
hello = hello + str;
System.out.println(hello);
};
hi2.sayHi("final");*/
}
private Integer operate(int a, int b, MathOperation add) {
return add.operate(a, b);
}
}
public interface MathOperation {
Integer operate(Integer a, Integer b);
}
public interface HiService {
void sayHi(String str);
}
方法引用
方法引用通过方法的名字来指向一个方法。
方法引用可以使语言的构造更紧凑简洁,减少冗余代码。
方法引用使用一对冒号 :: 。
方法引用在书中被称为 Lambda 的语法糖,语法糖大致意思就是一种为了简化代码书写的语法:
语法糖(Syntactic sugar),也译为糖衣语法,是由英国计算机科学家彼得·蘭丁发明的一个术语,指计算机语言中添加的某种语法,这种语法对语言的功能并没有影响,但是更方便程序员使用。
Lambda 的语法糖就是为了在特定情况下简化 Lambda 书写的语法 。
方法引用共分为四类:
1.类名::静态方法名
2.对象::实例方法名
3.类名::实例方法名
4.类名::new (即构造方法引用 )
示例:
public class Java8Tester {
public static void main(String[] args) {
Student student1 = new Student("zhangsan", 90);
Student student2 = new Student("lisi", 50);
Student student3 = new Student("wangwu", 100);
Student student4 = new Student("zhaoliu", 87);
List<Student> students = Arrays.asList(student1, student2, student3, student4);
//lambda表达式
/* students.sort((s1,s2)->s1.getScore()-s2.getScore());
students.forEach(student -> {
System.out.println(student);
});*/
//第一种 类名::静态方法名
/* students.sort(Student::compareStudentByScore);
students.forEach(System.out::println);*/
//第二种 对象::实例方法名
/* StudentComparator studentComparator = new StudentComparator();
students.sort(studentComparator::compareStudentByScore);
students.forEach(System.out::println);*/
//第三种 类名::实例方法名
/* students.sort(Student::compareByScore);
students.forEach(System.out::println);*/
//第四种 类名::new 也称构造方法引用
Supplier<Student> supplier = Student::new;
Student student = supplier.get();
student.setName("方法引用");
student.setScore(100);
System.out.println(student);
}
}
public class StudentComparator {
public int compareStudentByScore(Student student1, Student student2) {
return student1.getScore() - student2.getScore();
}
}
输出:
Student{name='lisi', score=50}
Student{name='zhaoliu', score=87}
Student{name='zhangsan', score=90}
Student{name='wangwu', score=100}
扩展:
第三种 类名::实例方法名
这种方法引用的方式较之前两种稍微有一些不好理解,因为无论是通过类名调用静态方法还是通过对象调用实例方法这都是符合Java的语法,使用起来也比较清晰明了。那我们带着这个疑问来了解一下这个比较特殊的方法引用。
现在再看一下Student类中静态方法的定义
public static int compareStudentByScore(Student student1,Student student2){
return student1.getScore() - student2.getScore();
}
虽然这个方法在语法上没有任何问题,可以作为一个工具正常使用,但是有没有觉得其在设计上是不合适的或者是错误的。这样的方法定义放在任何一个类中都可以正常使用,而不只是从属于Student这个类,那如果要定义一个只能从属于Student类的比较方法下面这个实例方法更合适一些
public int compareByScore(Student student){
return this.getScore() - student.getScore();
}
接收一个Student对象和当前调用该方法的Student对象的分数进行比较即可。现在我们就可以使用 类名::实例方法名 这种方式的方法引用替换lambda表达式了
students.sort(Student::compareByScore);
students.forEach(student -> System.out.println(student.getScore()));
这里非常奇怪,sort方法接收的lambda表达式不应该是两个参数么,为什么这个实例方法只有一个参数也满足了lambda表达式的定义(想想这个方法是谁来调用的)。这就是 类名::实例方法名 这种方法引用的特殊之处:当使用 类名::实例方法名 方法引用时,一定是lambda表达式所接收的第一个参数来调用实例方法,如果lambda表达式接收多个参数,其余的参数作为方法的参数传递进去。
结合本例来看,最初的lambda表达式是这样的
students.sort((o1, o2) -> o1.getScore() - o2.getScore());
那使用 类名::实例方法名 方法引用时,一定是o1来调用了compareByScore实例方法,并将o2作为参数传递进来进行比较。是不是就符合了compareByScore的方法定义。
Java 8 函数式接口
所谓的函数式接口,当然首先是一个接口,然后就是在这个接口里面只能有一个抽象方法。
这种类型的接口也称为SAM接口,即Single Abstract Method interfaces。
函数式接口(Functional Interface)就是一个有且仅有一个抽象方法,但是可以有多个非抽象方法的接口。
函数式接口可以被隐式转换为 lambda 表达式。
Lambda 表达式和方法引用(实际上也可认为是Lambda表达式)上。
函数式接口可以对现有的函数友好地支持 lambda。
JDK 1.8 之前已有的函数式接口:
- java.lang.Runnable
- java.util.concurrent.Callable
- java.security.PrivilegedAction
- java.util.Comparator
- java.io.FileFilter
- java.nio.file.PathMatcher
- java.lang.reflect.InvocationHandler
- java.beans.PropertyChangeListener
- java.awt.event.ActionListener
- javax.swing.event.ChangeListener
JDK 1.8 新增加的函数接口:
- java.util.function
java.util.function 它包含了很多类,用来支持 Java的 函数式编程,该包中的函数式接口有:
序号 | 接口 & 描述 |
---|---|
1 | BiConsumer<T,U>代表了一个接受两个输入参数的操作,并且不返回任何结果 |
2 | BiFunction<T,U,R>代表了一个接受两个输入参数的方法,并且返回一个结果 |
3 | BinaryOperator<T>代表了一个作用于于两个同类型操作符的操作,并且返回了操作符同类型的结果 |
4 | BiPredicate<T,U>代表了一个两个参数的boolean值方法 |
5 | BooleanSupplier代表了boolean值结果的提供方 |
6 | Consumer<T>代表了接受一个输入参数并且无返回的操作 |
7 | DoubleBinaryOperator代表了作用于两个double值操作符的操作,并且返回了一个double值的结果。 |
8 | DoubleConsumer代表一个接受double值参数的操作,并且不返回结果。 |
9 | DoubleFunction<R>代表接受一个double值参数的方法,并且返回结果 |
10 | DoublePredicate代表一个拥有double值参数的boolean值方法 |
11 | DoubleSupplier代表一个double值结构的提供方 |
12 | DoubleToIntFunction接受一个double类型输入,返回一个int类型结果。 |
13 | DoubleToLongFunction接受一个double类型输入,返回一个long类型结果 |
14 | DoubleUnaryOperator接受一个参数同为类型double,返回值类型也为double 。 |
15 | Function<T,R>接受一个输入参数,返回一个结果。 |
16 | IntBinaryOperator接受两个参数同为类型int,返回值类型也为int 。 |
17 | IntConsumer接受一个int类型的输入参数,无返回值 。 |
18 | IntFunction<R>接受一个int类型输入参数,返回一个结果 。 |
19 | IntPredicate:接受一个int输入参数,返回一个布尔值的结果。 |
20 | IntSupplier无参数,返回一个int类型结果。 |
21 | IntToDoubleFunction接受一个int类型输入,返回一个double类型结果 。 |
22 | IntToLongFunction接受一个int类型输入,返回一个long类型结果。 |
23 | IntUnaryOperator接受一个参数同为类型int,返回值类型也为int 。 |
24 | LongBinaryOperator接受两个参数同为类型long,返回值类型也为long。 |
25 | LongConsumer接受一个long类型的输入参数,无返回值。 |
26 | LongFunction<R>接受一个long类型输入参数,返回一个结果。 |
27 | LongPredicateR接受一个long输入参数,返回一个布尔值类型结果。 |
28 | LongSupplier无参数,返回一个结果long类型的值。 |
29 | LongToDoubleFunction接受一个long类型输入,返回一个double类型结果。 |
30 | LongToIntFunction接受一个long类型输入,返回一个int类型结果。 |
31 | LongUnaryOperator接受一个参数同为类型long,返回值类型也为long。 |
32 | ObjDoubleConsumer<T>接受一个object类型和一个double类型的输入参数,无返回值。 |
33 | ObjIntConsumer<T>接受一个object类型和一个int类型的输入参数,无返回值。 |
34 | ObjLongConsumer<T>接受一个object类型和一个long类型的输入参数,无返回值。 |
35 | Predicate<T>接受一个输入参数,返回一个布尔值结果。 |
36 | Supplier<T>无参数,返回一个结果。 |
37 | ToDoubleBiFunction<T,U>接受两个输入参数,返回一个double类型结果 |
38 | ToDoubleFunction<T>接受一个输入参数,返回一个double类型结果 |
39 | ToIntBiFunction<T,U>接受两个输入参数,返回一个int类型结果。 |
40 | ToIntFunction<T>接受一个输入参数,返回一个int类型结果。 |
41 | ToLongBiFunction<T,U>接受两个输入参数,返回一个long类型结果。 |
42 | ToLongFunction<T>接受一个输入参数,返回一个long类型结果。 |
43 | UnaryOperator<T>接受一个参数为类型T,返回值类型也为T。 |
示例:
@FunctionalInterface
public interface HelloExample {
void hello(String name);
default void greeting(){
System.out.println("lannister send the regard!");
}
}
HelloExample helloExample = name -> System.out.println("hello "+ name);
helloExample.hello("batman");
helloExample.greeting();
输出:
hello batman
lannister send the regard!
示例2:
public static void main(String args[]){
List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9);
// Predicate<Integer> predicate = n -> true
// n 是一个参数传递到 Predicate 接口的 test 方法
// n 如果存在则 test 方法返回 true
System.out.println("输出所有数据:");
// 传递参数 n
eval(list, n->true);
// Predicate<Integer> predicate1 = n -> n%2 == 0
// n 是一个参数传递到 Predicate 接口的 test 方法
// 如果 n%2 为 0 test 方法返回 true
System.out.println("输出所有偶数:");
eval(list, n-> n%2 == 0 );
// Predicate<Integer> predicate2 = n -> n > 3
// n 是一个参数传递到 Predicate 接口的 test 方法
// 如果 n 大于 3 test 方法返回 true
System.out.println("输出大于 3 的所有数字:");
eval(list, n-> n > 3 );
}
public static void eval(List<Integer> list, Predicate<Integer> predicate) {
for(Integer n: list) {
if(predicate.test(n)) {
System.out.println(n + " ");
}
}
}
Java 8 默认方法
Java 8 新增了接口的默认方法。
简单说,默认方法就是接口可以有实现方法,而且不需要实现类去实现其方法。
我们只需在方法名前面加个 default 关键字即可实现默认方法。
为什么要有这个特性?
首先,之前的接口是个双刃剑,好处是面向抽象而不是面向具体编程,缺陷是,当需要修改接口时候,需要修改全部实现该接口的类,目前的 java 8 之前的集合框架没有 foreach 方法,通常能想到的解决办法是在JDK里给相关的接口添加新的方法及实现。然而,对于已经发布的版本,是没法在给接口添加新方法的同时不影响已有的实现。所以引进的默认方法。他们的目的是为了解决接口的修改与现有的实现不兼容的问题。
语法
默认方法语法格式如下:
public interface Vehicle {
default void print(){
System.out.println("我是一辆车!");
}
}
静态默认方法
Java 8 的另一个特性是接口可以声明(并且可以提供实现)静态方法。例如:
public interface Vehicle {
default void print(){
System.out.println("我是一辆车!");
}
// 静态方法
static void blowHorn(){
System.out.println("按喇叭!!!");
}
}
Java 8 Stream
Java 8 API添加了一个新的抽象称为流Stream,可以让你以一种声明的方式处理数据。
Stream 使用一种类似用 SQL 语句从数据库查询数据的直观方式来提供一种对 Java 集合运算和表达的高阶抽象。
Stream API可以极大提高Java程序员的生产力,让程序员写出高效率、干净、简洁的代码。
这种风格将要处理的元素集合看作一种流, 流在管道中传输, 并且可以在管道的节点上进行处理, 比如筛选, 排序,聚合等。
元素流在管道中经过中间操作(intermediate operation)的处理,最后由最终操作(terminal operation)得到前面处理的结果。
什么是 Stream?
Stream(流)是一个来自数据源的元素队列并支持聚合操作
- 元素是特定类型的对象,形成一个队列。 Java中的Stream并不会存储元素,而是按需计算。
- 数据源 流的来源。 可以是集合,数组,I/O channel, 产生器generator 等。
- 聚合操作 类似SQL语句一样的操作, 比如filter, map, reduce, find, match, sorted等。
和以前的Collection操作不同, Stream操作还有两个基础的特征:
- Pipelining: 中间操作都会返回流对象本身。 这样多个操作可以串联成一个管道, 如同流式风格(fluent style)。 这样做可以对操作进行优化, 比如延迟执行(laziness)和短路( short-circuiting)。
- 内部迭代: 以前对集合遍历都是通过Iterator或者For-Each的方式, 显式的在集合外部进行迭代, 这叫做外部迭代。 Stream提供了内部迭代的方式, 通过访问者模式(Visitor)实现。
生成流
在 Java 8 中, 集合接口有两个方法来生成流:
- stream() − 为集合创建串行流。
- parallelStream() − 为集合创建并行流。
Stream接口还包含几个基本类型的子接口如IntStream, LongStream 和 DoubleStream。
关于流和其它集合具体的区别:
- 不存储数据。流是基于数据源的对象,它本身不存储数据元素,而是通过管道将数据源的元素传递给操作。
-
函数式编程。流的操作不会修改数据源,例如
filter
不会将数据源中的数据删除。 - 延迟操作。流的很多操作如filter,map等中间操作是延迟执行的,只有到终点操作才会将操作顺序执行。
-
可以解绑。对于无限数量的流,有些操作是可以在有限的时间完成的,比如
limit(n)
或findFirst()
,这些操作可是实现"短路"(Short-circuiting),访问到有限的元素后就可以返回。 - 纯消费。流的元素只能访问一次,类似Iterator,操作没有回头路,如果你想从头重新访问流的元素,对不起,你得重新生成一个新的流。
流的操作是以管道的方式串起来的。流管道包含一个数据源,接着包含零到N个中间操作,最后以一个终点操作结束。
Stream 的特性可以归纳为:
- 不是数据结构
- 它没有内部存储,它只是用操作管道从 source(数据结构、数组、generator function、IO channel)抓取数据。
- 它也绝不修改自己所封装的底层数据结构的数据。例如 Stream 的 filter 操作会产生一个不包含被过滤元素的新 Stream,而不是从 source 删除那些元素。
- 所有 Stream 的操作必须以 lambda 表达式为参数
- 不支持索引访问
- 你可以请求第一个元素,但无法请求第二个,第三个,或最后一个。不过请参阅下一项。
- 很容易生成数组或者 List
- 惰性化
- 很多 Stream 操作是向后延迟的,一直到它弄清楚了最后需要多少数据才会开始。
- Intermediate 操作永远是惰性化的。
- 并行能力
- 当一个 Stream 是并行化的,就不需要再写多线程代码,所有对它的操作会自动并行进行的。
- 可以是无限的
- 集合有固定大小,Stream 则不必。limit(n) 和 findFirst() 这类的 short-circuiting 操作可以对无限的 Stream 进行运算并很快完成。
示例:
public class StreamTest {
public static void main(String[] args) {
Long count = null;
List<String> strings = Arrays.asList("abc", "", "bc", "efg", "abcd", "", "jkl");
System.out.println("列表: " + strings);
//filter返回的流中只包含满足断言(predicate)的数据。
//count方法返回流中的元素的数量
//Long count = strings.stream().filter(String::isEmpty).count();
count = strings.stream().filter(str -> str.isEmpty()).count();
System.out.println("空字符串数量为: " + count);
count = strings.stream().filter(s -> s.length() == 3).count();
System.out.println("字符串长度为 3 的数量为: " + count);
// collect:流转换为其它数据结构
List filtered = strings.stream().filter(s -> !s.isEmpty()).collect(Collectors.toList());
System.out.println("不为空的筛选后的列表: " + filtered);
String mergedString = strings.stream().filter(s -> !s.isEmpty()).collect(Collectors.joining(" "));
System.out.println("合并字符串: " + mergedString);
List<Integer> numbers = Arrays.asList(3, 2, 2, 3, 7, 3, 5);
//map方法将流中的元素映射成另外的值,新的值类型可以和原来的元素的类型不同
//distinct保证输出的流中包含唯一的元素,它是通过Object.equals(Object)来检查是否包含相同的元素。
List<Integer> squaresList = numbers.stream().map(i -> i * i).distinct().collect(Collectors.toList());
System.out.println("Squares List: " + squaresList);
List<Integer> integers = Arrays.asList(1, 2, 13, 4, 15, 6, 17, 8, 19);
System.out.println("列表: " + integers);
//主要作用是把 Stream 元素组合起来。它提供一个起始值(种子),然后依照运算规则(BinaryOperator),和前面 Stream 的第一个、第二个、第 n 个元素组合。从这个意义上说,字符串拼接、数值的 sum、min、max、average 都是特殊的 reduce。
Integer sum = integers.stream().reduce((i,j) -> i +j).get();
System.out.println("总数: " + sum);
//mapToInt返回mapToInt流,有统计功能
IntSummaryStatistics stats = integers.stream().mapToInt(i -> i).summaryStatistics();
//max返回流中的最大值,
//min返回流中的最小值。
System.out.println("列表中最大的数 : " + stats.getMax());
System.out.println("列表中最小的数 : " + stats.getMin());
System.out.println("所有数之和 : " + stats.getSum());
System.out.println("平均数 : " + stats.getAverage());
// 输出10个随机数
Random random = new Random();
//limit方法指定数量的元素的流。
//sorted()将流中的元素按照自然排序方式进行排序,如果元素没有实现Comparable,则终点操作执行时会抛出java.lang.ClassCastException异常。
//public final class Integer extends Number implements Comparable<Integer> 。Integer有实现Comparable
//forEach遍历流的每一个元素,执行指定的action。
random.ints().limit(3).sorted().forEach(System.out::println);
// 并行处理
count = strings.parallelStream().filter(String::isEmpty).count();
System.out.println("空字符串的数量为: " + count);
}
}
输出:
列表: [abc, , bc, efg, abcd, , jkl]
空字符串数量为: 2
字符串长度为 3 的数量为: 3
不为空的筛选后的列表: [abc, bc, efg, abcd, jkl]
合并字符串: abc bc efg abcd jkl
Squares List: [9, 4, 49, 25]
列表: [1, 2, 19]
总数: 22
列表中最大的数 : 19
列表中最小的数 : 1
所有数之和 : 22
平均数 : 7.333333333333333
-676015796
-493263854
70721576
空字符串的数量为: 2
Java 8 Optional 类
Optional 类是一个可以为null的容器对象。如果值存在则isPresent()方法会返回true,调用get()方法会返回该对象。
Optional 是个容器:它可以保存类型T的值,或者仅仅保存null。Optional提供很多有用的方法,这样我们就不用显式进行空值检测。
Optional 类的引入很好的解决空指针异常。
Optional 的完整路径是 java.util.Optional
,使用它是为了避免代码中的 if (obj != null) { }
这样范式的代码,可以采用链式编程的风格。
而且通过 Optional 中提供的 filter 方法可以判断对象是否符合条件,在符合条件的情况下才会返回;map 方法可以在返回对象前修改对象中的属性。
Optional 类方法
序号 | 方法 & 描述 |
---|---|
1 | static <T> Optional<T> empty()返回空的 Optional 实例。 |
2 | boolean equals(Object obj)判断其他对象是否等于 Optional。 |
3 | Optional<T> filter(Predicate<? super <T> predicate)如果值存在,并且这个值匹配给定的 predicate,返回一个Optional用以描述这个值,否则返回一个空的Optional。 |
4 | <U> Optional<U> flatMap(Function<? super T,Optional<U>> mapper)如果值存在,返回基于Optional包含的映射方法的值,否则返回一个空的Optional |
5 | T get()如果在这个Optional中包含这个值,返回值,否则抛出异常:NoSuchElementException |
6 | int hashCode()返回存在值的哈希码,如果值不存在 返回 0。 |
7 | void ifPresent(Consumer<? super T> consumer)如果值存在则使用该值调用 consumer , 否则不做任何事情。 |
8 | boolean isPresent()如果值存在则方法会返回true,否则返回 false。 |
9 | <U>Optional<U> map(Function<? super T,? extends U> mapper)如果有值,则对其执行调用映射函数得到返回值。如果返回值不为 null,则创建包含映射返回值的Optional作为map方法返回值,否则返回空Optional。 |
10 | static <T> Optional<T> of(T value)返回一个指定非null值的Optional。 |
11 | static <T> Optional<T> ofNullable(T value)如果为非空,返回 Optional 描述的指定值,否则返回空的 Optional。 |
12 | T orElse(T other)如果存在该值,返回值, 否则返回 other。 |
13 | T orElseGet(Supplier<? extends T> other)如果存在该值,返回值, 否则触发 other,并返回 other 调用的结果。 |
14 | <X extends Throwable> T orElseThrow(Supplier<? extends X> exceptionSupplier)如果存在该值,返回包含的值,否则抛出由 Supplier 继承的异常 |
15 | String toString()返回一个Optional的非空字符串,用来调试 |
尽量避免使用的地方:
1、避免使用Optional.isPresent()来检查实例是否存在,因为这种方式和null != obj没有区别,这样用就没什么意义了。
2、避免使用Optional.get()方式来获取实例对象,因为使用前需要使用Optional.isPresent()来检查实例是否存在,否则会出现NPE问题。
3、避免使用Optional作为类或者实例的属性,而应该在返回值中用来包装返回实例对象。
4、避免使用Optional作为方法的参数,原因同3。
示例:
1、ifPresent:调用其他方法返回一个集合,在不通过 if 判断是否为 null 就可以放心的遍历
- 通过 Optional 的 ofNullable 构造函数封装
- 然后通过其中的 ifPresent 方法:如果对象不为 null, 才会执行 Consumer 接口对象中的方法
- 使用范式如下
Optional.ofNullable(userList)
.ifPresent()
public static void ifPresent(String[] args) {
// ifPresent 表示 Optional 中的对象存在才会执行 Consumer 接口对象中的方法
List<String> dataList = new ArrayList<>();
// 1. 不为空没有值的集合
Optional.ofNullable(dataList)
.ifPresent(t -> {
System.out.println("1"); // 输出 1
t.forEach(a -> System.out.println(a));
});
// 2. 为 null 的集合, 自动判断为 null, 没有执行 Consumer 接口对象中的方法
dataList = null;
Optional.ofNullable(dataList)
.ifPresent(t -> {
System.out.println("2"); // 没有执行
t.forEach(a -> System.out.println(a));
});
// 3. 有值的集合
dataList = new ArrayList<>();
dataList.add("b");
Optional.ofNullable(dataList).ifPresent(strings -> {
System.out.println("3");
strings.forEach(System.out::println);
});
/* Optional.ofNullable(dataList)
.ifPresent(t -> {
System.out.println("3"); // 输出 3
t.forEach(a -> System.out.println(a));
});*/
// 4. 过去的方式, 多了 if 判断
if (dataList != null && dataList.size() > 0) {
dataList.forEach(a -> System.out.println(a));
}
}
输出:
1
3
b
b
2、filter:在判断是否为 null 后,仅遍历集合中符合条件的对象
- 在通过 ifPresent 方法返回对象前通过 filter 方法设置过滤条件
- filter 不会减少集合中对象的数量,只要集合中的任意一个对象满足条件就会返回整个集合,否则返回空集合
- 使用范式如下
Optional.ofNullable(userList)
.filter()
.ifPresent()
public static void main(String[] args) {
List<User> userList = new ArrayList<>();
userList.add(new User("艾米", "艾米帝国", "123456456"));
userList.add(new User("大青山", "艾米帝国", "123456456"));
userList.add(new User("霍二叔", "艾米帝国", "123456456"));
userList.add(new User("阿风", "艾米帝国", "123456456"));
userList.add(new User("易海兰", "恶魔岛", "123456456"));
Optional.ofNullable(userList).filter(t -> t.stream().anyMatch(s -> s.getDept().equals("艾米帝国"))).ifPresent(t -> {
t.forEach(r ->{
System.out.println("1:" + JSON.toJSONString(r));
});
});
}
1:{"dept":"艾米帝国","name":"艾米","phone":"123456456"}
1:{"dept":"艾米帝国","name":"大青山","phone":"123456456"}
1:{"dept":"艾米帝国","name":"霍二叔","phone":"123456456"}
1:{"dept":"艾米帝国","name":"阿风","phone":"123456456"}
1:{"dept":"恶魔岛","name":"易海兰","phone":"123456456"}
3、orElse:在目标集合对象为 null 的时候可以设定默认值
- 通过 orElse 函数设定默认值
- ofNullable 函数为目标集合对象,如果为 null, 才会使用 orElse 函数设定的默认值
- 使用范式如下
Optional.ofNullable()
.orElse()
public static void main(String[] args) {
List<String> elseList = new ArrayList<>();
elseList.add("I");
elseList.add("am");
elseList.add("nobody");
List<String> rtList = null;
Optional.ofNullable(rtList).orElse(elseList).forEach(t -> {
System.out.println("orElse:" + JSON.toJSONString(t));
});
}
orElse:"I"
orElse:"am"
orElse:"nobody"
4、orElseGet:在目标集合对象为 null 的时候,可以动态从其他地方加载集合
- 在构造 Optional 的时候,如果其中的对象为 null, 通过 orElseGet 方法可以动态构造一个对象,比如可以再从数据库中加载
- orElseGet 直接返回的是 Optional 中的对象
- 与 orElse 相比,orElseGet 的参数是 Supplier 接口对象
- 使用范式如下
Optional.ofNullable()
.orElseGet()
public static void main(String[] args) {
List<String> tempList = new ArrayList<>();
tempList.add("a");
tempList.add("b");
tempList.add("c");
tempList.add("d");
// 1. 给定的对象为 null, 将会执行 orElseGet 方法提供的 Supplier 接口对象中的方法
List<String> dataList = null;
Optional.ofNullable(dataList)
.orElseGet(() -> tempList)
.forEach(t -> System.out.println("orElseGet:" + t));
}
orElseGet:a
orElseGet:b
orElseGet:c
orElseGet:d
5、 orElseThrow:如果目标对象为 null, 抛出自定义的异常
- 在使用 optional 包装的对象前,如果对象为 null 抛出自定义的异常
- 使用范式如下
Optional.ofNullable()
.orElseThrow(
public static void main(String[] args) {
// 1. 给定的对象为 null, 将会抛出异常
List<String> dataList = null;
try {
Optional.ofNullable(dataList)
.orElseThrow(() -> new RuntimeException("dataList 对象为空"))
.forEach(t -> System.out.println("1:" + t));
} catch (Exception e) {
System.out.println(e.getMessage());
Assert.assertTrue(e instanceof RuntimeException);
}
}
dataList 对象为空
Java 8 Nashorn JavaScript
Nashorn 一个 javascript 引擎。
从JDK 1.8开始,Nashorn取代Rhino(JDK 1.6, JDK1.7)成为Java的嵌入式JavaScript引擎。Nashorn完全支持ECMAScript 5.1规范以及一些扩展。它使用基于JSR 292的新语言特性,其中包含在JDK 7中引入的 invokedynamic,将JavaScript编译成Java字节码。
与先前的Rhino实现相比,这带来了2到10倍的性能提升。
Java 中调用 JavaScript
todo
JavaScript 中调用 Java
todo
Java 8 日期时间 API
Java 8通过发布新的Date-Time API (JSR 310)来进一步加强对日期与时间的处理。
在旧版的 Java 中,日期时间 API 存在诸多问题,其中有:
- 非线程安全 − java.util.Date 是非线程安全的,所有的日期类都是可变的,这是Java日期类最大的问题之一。
- 设计很差 − Java的日期/时间类的定义并不一致,在java.util和java.sql的包中都有日期类,此外用于格式化和解析的类在java.text包中定义。java.util.Date同时包含日期和时间,而java.sql.Date仅包含日期,将其纳入java.sql包并不合理。另外这两个类都有相同的名字,这本身就是一个非常糟糕的设计。
- 时区处理麻烦 − 日期类并不提供国际化,没有时区支持,因此Java引入了java.util.Calendar和java.util.TimeZone类,但他们同样存在上述所有的问题。
Java 8 在 java.time 包下提供了很多新的 API。以下为两个比较重要的 API:
- Local(本地) − 简化了日期时间的处理,没有时区的问题。
- Zoned(时区) − 通过制定的时区处理日期时间。
新的java.time包涵盖了所有处理日期,时间,日期/时间,时区,时刻(instants),过程(during)与时钟(clock)的操作。
Java8 Base64
在Java 8中,Base64编码已经成为Java类库的标准。
Java 8 内置了 Base64 编码的编码器和解码器。
Base64工具类提供了一套静态方法获取下面三种BASE64编解码器:
- 基本:输出被映射到一组字符A-Za-z0-9+/,编码不添加任何行标,输出的解码仅支持A-Za-z0-9+/。
- URL:输出映射到一组字符A-Za-z0-9+_,输出是URL和文件。
- MIME:输出隐射到MIME友好格式。输出每行不超过76字符,并且使用'\r'并跟随'\n'作为分割。编码输出最后没有行分割。
内嵌类
序号 | 内嵌类 & 描述 |
---|---|
1 | static class Base64.Decoder该类实现一个解码器用于,使用 Base64 编码来解码字节数据。 |
2 | static class Base64.Encoder该类实现一个编码器,使用 Base64 编码来编码字节数据。 |
方法
序号 | 方法名 & 描述 |
---|---|
1 | static Base64.Decoder getDecoder()返回一个 Base64.Decoder ,解码使用基本型 base64 编码方案。 |
2 | static Base64.Encoder getEncoder()返回一个 Base64.Encoder ,编码使用基本型 base64 编码方案。 |
3 | static Base64.Decoder getMimeDecoder()返回一个 Base64.Decoder ,解码使用 MIME 型 base64 编码方案。 |
4 | static Base64.Encoder getMimeEncoder()返回一个 Base64.Encoder ,编码使用 MIME 型 base64 编码方案。 |
5 | static Base64.Encoder getMimeEncoder(int lineLength, byte[] lineSeparator)返回一个 Base64.Encoder ,编码使用 MIME 型 base64 编码方案,可以通过参数指定每行的长度及行的分隔符。 |
6 | static Base64.Decoder getUrlDecoder()返回一个 Base64.Decoder ,解码使用 URL 和文件名安全型 base64 编码方案。 |
7 | static Base64.Encoder getUrlEncoder()返回一个 Base64.Encoder ,编码使用 URL 和文件名安全型 base64 编码方案。 |
示例:
public static void main(String[] args) throws UnsupportedEncodingException {
// 使用基本编码
String base64encodedString = Base64.getEncoder().encodeToString("whosyourdaddy".getBytes("utf-8"));
System.out.println("Base64 编码字符串 (基本) :" + base64encodedString);
// 解码
byte[] base64decodedBytes = Base64.getDecoder().decode(base64encodedString);
System.out.println("原始字符串: " + new String(base64decodedBytes, "utf-8"));
base64encodedString = Base64.getUrlEncoder().encodeToString("whosyourdaddy".getBytes("utf-8"));
System.out.println("Base64 编码字符串 (URL) :" + base64encodedString);
StringBuilder stringBuilder = new StringBuilder();
for (int i = 0; i < 10; ++i) {
stringBuilder.append(UUID.randomUUID().toString());
}
byte[] mimeBytes = stringBuilder.toString().getBytes("utf-8");
String mimeEncodedString = Base64.getMimeEncoder().encodeToString(mimeBytes);
System.out.println("Base64 编码字符串 (MIME) :" + mimeEncodedString);
}
Base64 编码字符串 (基本) :d2hvc3lvdXJkYWRkeQ==
原始字符串: whosyourdaddy
Base64 编码字符串 (URL) :d2hvc3lvdXJkYWRkeQ==
Base64 编码字符串 (MIME) :ZjJiYTkxOWYtNTgzNC00NDVkLWJmYTUtZWU3NzU3Y2IxMWI5MmU3ODRkMDgtZGZjZC00Y2YyLWE0
NTktZThlNmU4NjdiYjNjYWE2YmQ1M2ItNjFkZS00M2Q1LTg5NzUtNzEzMjBkNjI3ODcwZGNlNmRl
NTMtNGZhZS00ZThhLTkxYWYtZjlhZWUxYzUzODVhNjRhZmM4MDQtYWQ3Mi00OWQxLTliM2YtZTAz
YWRiNjUyYzMyZTJhYzE0MmQtZjMyNS00MTE0LThjYzItYzZhYTM2MmJjODQ4MWUwNzNiYTUtMjEy
Ny00MTkyLTk5Y2YtNTcyNGUwZGYyNzAwZmUxOGMwMzMtNzY3YS00NzMyLWJmOGItMDYyMmE3ZjBh
ZTZiZjk3ZTdiNmUtMzlmOS00NzkzLWIzNTUtZWU1NWVhNGRmNWVmZmMzZTcwYjUtYjAxMy00NGY2
LWJiNTQtNjhmMWM1ZDk0MmUy
网友评论