Java8
是 Java
发布以来改动最大的一个版本
添加了函数式编程、Stream
、全新的日期处理类 函数式编程新加了一些概念:Lambda
表达式、函数式接口、函数引用、默认方法、Optional
类等 Stream
中提供了一些流式处理集合的方法,并提供了一些归约、划分等类的方法 日期中添加了ZoneDateTime
、DataFormat
等线程安全的方法类
Lambda
可定义为一种简洁、可传递的匿名函数,它是推动Java 8发布的最重要新特性Lambda
本质上是一个函数,虽然它不属于某个特定的类,但具备参数列表、函数主体、返回类型,甚至 能够抛出异常Lambda
是匿名的,它没有具体的函数名称Lambda
允许把函数作为一个方法的参数(函数作为参数传递进方法中)Lambda
可以使代码变的更加简洁lambda
表达式默认隐含了 return
关键字,在单个表达式中,我们无需在写 return
关键字,也无需写花括号。return
关键字// 返回给定字符串的长度(隐含return语句)
(String str) -> str.length()
// 始终返回233的无参方法(隐含return语句)
() -> 233
// 返回当前用户是否年龄大于20岁,返回一个boolean值(隐含return语句)
(User user) -> user.getAge() > 20
// 包含多行表达式,需用花括号括起来,并使用return关键字返回
(int x, int y) -> {
int z = x * y;
return x + z;
}
//使用Lambda
Runnable r1 = () -> System.out.println("Hello World 1");
//传统匿名类
Runnable r2 = new Runnable(){
public void run(){
System.out.println("Hello World 2");
}
};
//执行Runnable方法
//使用Lambda
Runnable r1 = () -> System.out.println("Hello World 1");
//传统匿名类
Runnable r2 = new Runnable(){
public void run(){
System.out.println("Hello World 2");
}
};
//执行Runnable方法
public static void process(Runnable r){
r.run();
}
//打印 "Hello World 1"
process(r1);
//打印 "Hello World 2"
process(r2);
//利用直接传递的 Lambda 打印 "Hello World 3"
process(() -> System.out.println("Hello World 3")); r.run();
}
//打印 "Hello World 1"
process(r1);
//打印 "Hello World 2"
process(r2);
//利用直接传递的 Lambda 打印 "Hello World 3"
process(() -> System.out.println("Hello World 3"));
了解了 lambda
的基本用法,下面我们就可以开始着手Stream API的学习了!
Lambda
表达式利用函数式编程提供精简的方式表达行为。Lambda
表达式中处理异常的解决方案首先我们看一段简单的代码,将50与List中每个元素相除并打印出结果
List integers = Arrays.asList(1, 2, 3, 4, 5, 6);
integers.forEach(i -> System.out.println(50 / i));
这样看是不会有问题的,代码简洁。
但是如果List中包含元素0
,那么就会抛出异常:ArithmeticException: / by zero
有经验的小伙伴可能会立马给出解决方案,使用传统的try-catch来处理异常,代码如下:
List<Integer> integers = Arrays.asList(1, 2, 3, 4, 5, 6, 0);
integers.forEach(i -> {
try {
System.out.println(50 / i);
} catch (ArithmeticException e) {
System.err.println( "Arithmetic Exception occured : " + e.getMessage());
}
});
使用try-catch解决了问题,但是失去了lambda表达式的精简,代码变得臃肿,想必并不是完美的解决方案。
对于一些强迫症老哥来说,这种代码是绝对不能存活的,所以我们需要如下的解决方案。
我们将会对抛出异常的函数进行包装,使其不抛出受检异常
如果一个 FunctionInterface
的方法会抛出受检异常(比如 Exception
),那么该
FunctionInterface
便可以作为会抛出受检异常的 Lambda
的目标类型。 我们定义如下一个 FunctionInterface
:
@FunctionalInterface
interface UncheckedFunction<T, R> {
R apply(T t) throws Exception;
}
那么该 FunctionInterface
便可以作为抛出受检异常的 Lambda
的目标类型,此时 Lambda
中并不需要 捕获异常,因为目标类型的 apply
方法已经将异常抛出了。
我们如何使用 UncheckedFunction
到流式操作的 Lambda
中呢?
首先我们定义一个 Try 类,它的 of 方法提供将 UncheckedFunction
包装为 Function
的功能:
public class Try {
public static <T, R> Function<T, R> of(UncheckedFunction<T, R> mapper) {
Objects.requireNonNull(mapper);
return t -> {
try {
return mapper.apply(t);
} catch (Exception e) {
throw Exceptions.unchecked(e);
}
};
}
@FunctionalInterface
public interface UncheckedFunction<T, R> {
R apply(T t) throws Exception;
}
}
然后在原先的代码中,我们使用 Try.of
方法来对会抛出受检异常的 Lambda
进行包装:
List<Integer> integers = Arrays.asList(1, 2, 3, 4, 5, 6, 0);
integers.forEach(Try.of(i -> System.out.println(50 / i)));
此时,我们便可以选择是否去捕获异常( RuntimeException
)。这种解决方法下,我们一般不关心抛出异 常的情况 。 比如自己写的小例子,抛出了异常程序就该终止;或者你知道这个 Lambda
确实 100% 不会抛出异 常。
什么是流?
流是Java8引入的全新概念,它用来处理集合中的数据,暂且可以把它理解为一种高级集合。 众所周知,集合操作非常麻烦,若要对集合进行筛选、投影,需要写大量的代码,而流是以声明的形式操作集 合,它就像SQL语句,我们只需告诉流需要对集合进行什么操作,它就会自动进行操作,并将执行结果交给你, 无需我们自己手写代码。
因此,流的集合操作对我们来说是透明的,我们只需向流下达命令,它就会自动把我们想要的结果给我们。由于 操作过程完全由Java处理,因此它可以根据当前硬件环境选择最优的方法处理,我们也无需编写复杂又容易出错 的多线程代码了。
**流的特点 **
**流的操作种类 **
流的操作分为两种,分别为中间操作和终端操作。
**流的操作过程 **
使用流一共需要三步:
准备一个数据源
执行中间操作
中间操作可以有多个,它们可以串连起来形成流水线。
执行终端操作
执行终端操作后本次流结束,你将获得一个执行结果。
// 转stream
list.stream()
// 并发处理
list.parallelStream()
Stream<T> filter(Predicate<? super T> predicate);
<R> Stream<R> map(Function<? super T, ? extends R> mapper);
IntStream mapToInt(ToIntFunction<? super T> mapper);
LongStream mapToLong(ToLongFunction<? super T> mapper);
DoubleStream mapToDouble(ToDoubleFunction<? super T> mapper);
<R> Stream<R> flatMap(Function<? super T, ? extends Stream<? extends R>> mapper);
IntStream flatMapToInt(Function<? super T, ? extends IntStream> mapper);
LongStream flatMapToLong(Function<? super T, ? extends LongStream> mapper);
DoubleStream flatMapToDouble(Function<? super T, ? extends DoubleStream> mapper);
Stream<T> distinct();
Stream<T> sorted();
Stream<T> sorted(Comparator<? super T> comparator);
Stream<T> peek(Consumer<? super T> action);
Stream<T> limit(long maxSize);
Stream<T> skip(long n);
void forEach(Consumer<? super T> action);
void forEachOrdered(Consumer<? super T> action);
Object[] toArray();
<A> A[] toArray(IntFunction<A[]> generator);
T reduce(T identity, BinaryOperator<T> accumulator);
Optional<T> reduce(BinaryOperator<T> accumulator);
<U> U reduce(U identity,
BiFunction<U, ? super T, U> accumulator,
BinaryOperator<U> combiner);
<R> R collect(Supplier<R> supplier,
BiConsumer<R, ? super T> accumulator,
BiConsumer<R, R> combiner);
<R, A> R collect(Collector<? super T, A, R> collector);
// 转list
Collectors.toList();
// 转set
Collectors.toSet();
// 转map
List<TestVo> testList = new ArrayList<>(10);
Map<Long, TestVo> data = releaseList.stream() .collect(Collectors.toMap(TestVo::getId, x -> x));
long count();
boolean anyMatch(Predicate<? super T> predicate);
boolean allMatch(Predicate<? super T> predicate);
boolean noneMatch(Predicate<? super T> predicate);
Optional<T> findFirst();
Optional<T> findAny();
在使用流之前,首先需要拥有一个数据源,并通过StreamAPI提供的一些方法获取该数据源的流对象。数据源可 以有多种形式:
List<Person> list = new ArrayList<Person>();
Stream<Person> stream = list.stream();
数组
通过Arrays类提供的静态函数stream()获取数组的流对象:
String[] names = {
"chaimm","peter","john"};
Stream<String> stream = Arrays.stream(names);
值
直接将几个值变成流对象:
Stream<String> stream = Stream.of("chaimm","peter","john");
try(Stream lines = Files.lines(Paths.get(“文件路径名”),Charset.defaultCharset())) {
//可对lines做一些操作
}catch(IOException e){
}
iterator
创建无限流
Stream.iterate(0, n -> n + 2)
.limit(10)
.forEach(System.out::println);
PS:Java7简化了IO操作,把打开IO操作放在try后的括号中即可省略关闭IO的代码。
filter 函数接收一个Lambda表达式作为参数,该表达式返回boolean,在执行过程中,流将元素逐一输送给 filter,并筛选出执行结果为true的元素。
如,筛选出所有学生:
List<Person> result = list.stream()
.filter(Person::isStudent)
.collect(toList());
去掉重复的结果:
List<Person> result = list.stream()
.limit(3)
.collect(toList());
跳过流的前n个元素:
List<Person> result = list.stream()
.skip(3)
.collect(toList());
对流中的每个元素执行一个函数,使得元素转换成另一种类型输出。流会将每一个元素输送给map函数,并执行map中的Lambda表达式,最后将执行结果存入一个新的流中。
如,获取每个人的姓名(实则是将Perosn类型转换成String类型):
List<Person> result = list.stream()
.map(Person::getName)
.collect(toList());
例:列出List中各不相同的单词,List集合如下:
List<String> list = new ArrayList<String>();
list.add("I am a boy");
list.add("I love the girl");
list.add("But the girl loves another girl");
思路如下:
首先将list变成流:
list.stream();
按空格分词:
list.stream()
.map(line->line.split(" "));
分完词之后,每个元素变成了一个String[]数组。
将每个 String[]
变成流:
list.stream()
.map(line->line.split(" "))
.map(Arrays::stream)
此时一个大流里面包含了一个个小流,我们需要将这些小流合并成一个流。
将小流合并成一个大流:用 flatMap
替换刚才的 map
list.stream()
.map(line->line.split(" "))
.flatMap(Arrays::stream)
去重
list.stream()
.map(line->line.split(" "))
.flatMap(Arrays::stream)
.distinct()
.collect(toList());
anyMatch用于判断流中是否存在至少一个元素满足指定的条件,这个判断条件通过Lambda表达式传递给anyMatch,执行结果为boolean类型。
如,判断list中是否有学生:
boolean result = list.stream()
.anyMatch(Person::isStudent);
allMatch用于判断流中的所有元素是否都满足指定条件,这个判断条件通过Lambda表达式传递给anyMatch,执行结果为boolean类型。
如,判断是否所有人都是学生:
boolean result = list.stream()
.allMatch(Person::isStudent);
noneMatch与allMatch恰恰相反,它用于判断流中的所有元素是否都不满足指定条件:
boolean result = list.stream()
.noneMatch(Person::isStudent);
findAny能够从流中随便选一个元素出来,它返回一个Optional类型的元素。
Optional person = list.stream().findAny();
Optional person = list.stream().findFirst();
归约是将集合中的所有元素经过指定运算,折叠成一个元素输出,如:求最值、平均数等,这些操作都是将一个集合的元素折叠成一个元素输出。
在流中,reduce函数能实现归约。
reduce函数接收两个参数:
元素求和:自定义Lambda表达式实现求和
例:计算所有人的年龄总和
@Test
public void contextLoads() {
List<Person> list = new ArrayList<>();
list.add(new Person().setAge(20));
list.add(new Person().setAge(25));
int age = list.stream().map(Person::getAge).reduce(0, Integer::sum);
System.out.println(age);
}
@Data
@Accessors(chain = true)
class Person {
private int age;
}
元素求和:使用Integer.sum函数求和
上面的方法中我们自己定义了Lambda表达式实现求和运算,如果当前流的元素为数值类型,那么可以使用Integer提供了sum函数代替自定义的Lambda表达式,如:
int age = list.stream().reduce(0, Integer::sum);
Integer类还提供了 min
、max
等一系列数值操作,当流中元素为数值类型时可以直接使用。
采用reduce进行数值操作会涉及到基本数值类型和引用数值类型之间的装箱、拆箱操作,因此效率较低。
当流操作为纯数值操作时,使用数值流能获得较高的效率。
将普通流转换成数值流
StreamAPI提供了三种数值流:IntStream、DoubleStream、LongStream,也提供了将普通流转换成数值流的三种方法:mapToInt、mapToDouble、mapToLong。
如,将Person中的age转换成数值流:
IntStream stream = list.stream().mapToInt(Person::getAge);
数值计算
每种数值流都提供了数值计算函数,如max、min、sum等。如,找出最大的年龄:
OptionalInt maxAge = list.stream()
.mapToInt(Person::getAge)
.max();
由于数值流可能为空,并且给空的数值流计算最大值是没有意义的,因此max函数返回OptionalInt,它是Optional的一个子类,能够判断流是否为空,并对流为空的情况作相应的处理。
此外,mapToInt、mapToDouble、mapToLong进行数值操作后的返回结果分别为:OptionalInt、OptionalDouble、OptionalLong
操作 | 类型 | 返回类型 | 使用的类型/函数式接口 | 函数描述符 |
---|---|---|---|---|
filter |
中间 | Stream |
Predicate |
T -> boolean |
distinct |
中间 | Stream |
||
skip |
中间 | Stream |
long | |
map |
中间 | Stream |
Function |
T -> R |
flatMap |
中间 | Stream |
Function> |
T -> Stream |
limit |
中间 | Stream |
long | |
sorted |
中间 | Stream |
Comparator |
(T, T) -> int |
anyMatch |
终端 | boolean |
Predicate |
T -> boolean |
noneMatch |
终端 | boolean |
Predicate |
T -> boolean |
allMatch |
终端 | boolean |
Predicate |
T -> boolean |
findAny |
终端 | Optional |
||
findFirst |
终端 | Optional |
||
forEach |
终端 | void |
Consumer |
T -> void |
collect |
终端 | R |
Collector |
|
reduce |
终端 | Optional |
BinaryOperator |
(T, T) -> T |
count |
终端 | long |
收集器用来将经过筛选、映射的流进行最后的整理,可以使得最后的结果以不同的形式展现。
collect
方法即为收集器,它接收 Collector
接口的实现作为具体收集器的收集方法。
Collector
接口提供了很多默认实现的方法,我们可以直接使用它们格式化流的结果;也可以自定义 Collector
接口的实现,从而定制自己的收集器。
流由一个个元素组成,归约就是将一个个元素“折叠”成一个值,如求和、求最值、求平均值都是归约操作。
若你需要自定义一个归约操作,那么需要使用 Collectors.reducing
函数,该函数接收三个参数:
Collectors类专门为汇总提供了一个工厂方法:Collectors.summingInt
。
它可接受一 个把对象映射为求和所需int的函数,并返回一个收集器;该收集器在传递给普通的 collect
方法后即执行我们需要的汇总操作。
数据分组是一种更自然的分割数据操作,分组就是将流中的元素按照指定类别进行划分,类似于SQL语句中的 GROUPBY
。
多级分组可以支持在完成一次分组后,分别对每个小组再进行分组。
使用具有两个参数的 groupingBy
重载方法即可实现多级分组。
groupingBy
函数,该函数包含二级分组的条件Collectors 类的静态工厂方法
工厂方法 | 返回类型 | 用途 | 示例 |
---|---|---|---|
toList |
List |
把流中所有项目收集到一个 List | List projects = projectStream.collect(toList()); |
toSet |
Set |
把流中所有项目收集到一个 Set,删除重复项 | Set projects = projectStream.collect(toSet()); |
toCollection |
Collection |
把流中所有项目收集到给定的供应源创建的集合 | Collection projects = projectStream.collect(toCollection(), ArrayList::new); |
counting |
Long |
计算流中元素的个数 | long howManyProjects = projectStream.collect(counting()); |
summingInt |
Integer |
对流中项目的一个整数属性求和 | int totalStars = projectStream.collect(summingInt(Project::getStars)); |
averagingInt |
Double |
计算流中项目 Integer 属性的平均值 | double avgStars = projectStream.collect(averagingInt(Project::getStars)); |
summarizingInt |
IntSummaryStatistics |
收集关于流中项目 Integer 属性的统计值,例如最大、最小、 总和与平均值 | IntSummaryStatistics projectStatistics = projectStream.collect(summarizingInt(Project::getStars)); |
joining |
String |
连接对流中每个项目调用 toString 方法所生成的字符串 | String shortProject = projectStream.map(Project::getName).collect(joining(", ")); |
maxBy |
Optional |
按照给定比较器选出的最大元素的 Optional, 或如果流为空则为 Optional.empty() | Optional fattest = projectStream.collect(maxBy(comparingInt(Project::getStars))); |
minBy |
Optional |
按照给定比较器选出的最小元素的 Optional, 或如果流为空则为 Optional.empty() | Optional fattest = projectStream.collect(minBy(comparingInt(Project::getStars))); |
reducing |
归约操作产生的类型 | 从一个作为累加器的初始值开始,利用 BinaryOperator 与流中的元素逐个结合,从而将流归约为单个值 | int totalStars = projectStream.collect(reducing(0, Project::getStars, Integer::sum)); |
collectingAndThen |
转换函数返回的类型 | 包含另一个收集器,对其结果应用转换函数 | int howManyProjects = projectStream.collect(collectingAndThen(toList(), List::size)); |
groupingBy |
Map> |
根据项目的一个属性的值对流中的项目作问组,并将属性值作 为结果 Map 的键 | Map> projectByLanguage = projectStream.collect(groupingBy(Project::getLanguage)); |
partitioningBy |
Map> |
根据对流中每个项目应用断言的结果来对项目进行分区 | Map> vegetarianDishes = projectStream.collect(partitioningBy(Project::isVegetarian)); |
有一些收集器可以生成其他集合。比如前面已经见过的 toList
,生成了 java.util.List
类的实例。
还有 toSet
和 toCollection
,分别生成 Set
和 Collection
类的实例。
到目前为止, 我已经讲了很多流上的链式操作,但总有一些时候,需要最终生成一个集合——比如:
使用 toCollection
,用定制的集合收集元素
stream.collect(toCollection(TreeSet::new));
还可以利用收集器让流生成一个值。 maxBy
和 minBy
允许用户按某种特定的顺序生成一个值。
分区是分组的特殊情况:由一个断言(返回一个布尔值的函数)作为分类函数,它称分区函数。
分区函数返回一个布尔值,这意味着得到的分组 Map
的键类型是 Boolean
,于是它最多可以分为两组: true是一组,false是一组。
分区的好处在于保留了分区函数返回true或false的两套流元素列表。
并行流就是一个把内容分成多个数据块,并用不不同的线程分别处理每个数据块的流。最后合并每个数据块的计算结果。
将一个顺序执行的流转变成一个并发的流只要调用 parallel()
方法
public static long parallelSum(long n){
return Stream.iterate(1L, i -> i +1).limit(n).parallel().reduce(0L,Long::sum);
}
将一个并发流转成顺序的流只要调用 sequential()
方法
stream.parallel().filter(...).sequential().map(...).parallel().reduce();
这两个方法可以多次调用,只有最后一个调用决定这个流是顺序的还是并发的。
并发流使用的默认线程数等于你机器的处理器核心数。
通过这个方法可以修改这个值,这是全局属性。
System.setProperty("java.util.concurrent.ForkJoinPool.common.parallelism", "12");
并非使用多线程并行流处理数据的性能一定高于单线程顺序流的性能,因为性能受到多种因素的影响。
如何高效使用并发流的一些建议:
流的数据源和可分解性
源 | 可分解性 |
---|---|
ArrayList |
非常好 |
LinkedList |
差 |
IntStream.range |
非常好 |
Stream.iterate |
差 |
HashSet |
好 |
TreeSet |
好 |
流的特性以及中间操作对流的修改都会对数据对分解性能造成影响。 比如固定大小的流在任务分解的时候就可以平均分配,但是如果有filter操作,那么流就不能预先知道在这个操作后还会剩余多少元素。
考虑终端操作的性能:如果终端操作在合并并发流的计算结果时的性能消耗太大,那么使用并发流提升的性能就会得不偿失。
Optional
类库。Optional
实际上是个容器:它可以保存类型T的值,或者仅仅保存null。Optional
提供很多有用的方法,这样我们就不用显式进行空值检测。Optional.of()
或者Optional.ofNullable()
:创建Optional
对象,差别在于of
不允许参数是null
,而ofNullable
则无限制。// 参数不能是null
Optional optional1 = Optional.of(1);
// 参数可以是null
Optional optional2 = Optional.ofNullable(null);
// 参数可以是非null
Optional optional3 = Optional.ofNullable(2);
Optional.empty()
:所有null包装成的Optional
对象Optional optional1 = Optional.ofNullable(null);
Optional optional2 = Optional.ofNullable(null);
System.out.println(optional1 == optional2);// true
System.out.println(optional1 == Optional.empty());// true
Object o1 = Optional.empty();
Object o2 = Optional.empty();
System.out.println(o1 == o2);// true
isPresent()
:判断值是否存在Optional<Integer> optional1 = Optional.ofNullable(1);
Optional<Integer> optional2 = Optional.ofNullable(null);
// isPresent判断值是否存在
System.out.println(optional1.isPresent() == true);
System.out.println(optional2.isPresent() == false);
ifPresent(Consumer consumer)
:如果option对象保存的值不是null,则调用consumer对象,否则不调用Optional<Integer> optional1 = Optional.ofNullable(1);
Optional<Integer> optional2 = Optional.ofNullable(null);
// 如果不是null,调用Consumer
optional1.ifPresent(new Consumer<Integer>() {
@Override
public void accept(Integer t) {
System.out.println("value is " + t);
}
});
// null,不调用Consumer
optional2.ifPresent(new Consumer<Integer>() {
@Override
public void accept(Integer t) {
System.out.println("value is " + t);
}
});
orElse(value)
:如果optional对象保存的值不是null
,则返回原来的值,否则返回value
Optional<Integer> optional1 = Optional.ofNullable(1);
Optional<Integer> optional2 = Optional.ofNullable(null);
// orElse
System.out.println(optional1.orElse(1000) == 1);// true
System.out.println(optional2.orElse(1000) == 1000);// true
orElseGet(Supplier supplier)
:功能与orElse
一样,只不过orElseGet
参数是一个对象Optional<Integer> optional1 = Optional.ofNullable(1);
Optional<Integer> optional2 = Optional.ofNullable(null);
System.out.println(optional1.orElseGet(() -> 1000) == 1);//true
System.out.println(optional2.orElseGet(() -> 1000) == 1000);//true
orElseThrow()
:值不存在则抛出异常,存在则什么不做,有点类似Guava
的Precoditions
Optional<Integer> optional1 = Optional.ofNullable(1);
Optional<Integer> optional2 = Optional.ofNullable(null);
optional1.orElseThrow(() -> {
throw new IllegalStateException();
});
try {
optional2.orElseThrow(() -> {
throw new IllegalStateException();
});
} catch (IllegalStateException e) {
e.printStackTrace();
}
filter(Predicate)
:判断Optional
对象中保存的值是否满足Predicate
,并返回新的Optional
。Optional<Integer> optional1 = Optional.ofNullable(1);
Optional<Integer> optional2 = Optional.ofNullable(null);
Optional<Integer> filter1 = optional1.filter((a) -> a == null);
Optional<Integer> filter2 = optional1.filter((a) -> a == 1);
Optional<Integer> filter3 = optional2.filter((a) -> a == null);
System.out.println(filter1.isPresent());// false
System.out.println(filter2.isPresent());// true
System.out.println(filter2.get().intValue() == 1);// true
System.out.println(filter3.isPresent());// false
map(Function)
:对Optional
中保存的值进行函数运算,并返回新的Optional
(可以是任何类型)Optional<Integer> optional1 = Optional.ofNullable(1);
Optional<Integer> optional2 = Optional.ofNullable(null);
Optional<String> str1Optional = optional1.map((a) -> "key" + a);
Optional<String> str2Optional = optional2.map((a) -> "key" + a);
System.out.println(str1Optional.get());// key1
System.out.println(str2Optional.isPresent());// false
10.flatMap()
:功能与map()
相似,差别请看如下代码。flatMap
方法与map
方法类似,区别在于mapping
函数的返回值不同。map
方法的mapping
函数返回值可以是任何类型T
,而flatMap
方法的mapping
函数必须是Optional
。
Optional<Integer> optional1 = Optional.ofNullable(1);
Optional<Optional<String>> str1Optional = optional1.map((a) -> Optional.of("key" + a));
Optional<String> str2Optional = optional1.flatMap((a) -> Optional.of("key" + a));
System.out.println(str1Optional.get().get());// key1
System.out.println(str2Optional.get());// key1
方法 | 描述 |
---|---|
empty |
返回一个空的 Optional 实例 |
filter |
如果值存在并且满足提供的断言, 就返回包含该值的 Optional 对象;否则返回一个空的 Optional 对象 |
map |
如果值存在,就对该值执行提供的 mapping 函数调用 |
flatMap |
如果值存在,就对该值执行提供的 mapping 函数调用,返回一个 Optional 类型的值,否则就返 回一个空的 Optional 对象 |
get |
如果该值存在,将该值用 Optional 封装返回,否则抛出一个 NoSuchElementException 异常 |
ifPresent |
如果值存在,就执行使用该值的方法调用,否则什么也不做 |
isPresent |
如果值存在就返回 true,否则返回 false |
of |
将指定值用 Optional 封装之后返回,如果该值为 null,则抛出一个 NullPointerException 异常 |
ofNullable |
将指定值用 Optional 封装之后返回,如果该值为 null,则返回一个空的 Optional 对象 |
orElse |
如果有值则将其返回,否则返回一个默认值 |
orElseGet |
如果有值则将其返回,否则返回一个由指定的 Supplier 接口生成的值 |
orElseThrow |
如果有值则将其返回,否则抛出一个由指定的 Supplier 接口生成的异常 |
// Function -T作为输入,返回的R作为输出
Function<String,String> fun = (x) -> {
System.out.print(x+": ");return "Function";};
System.out.println(function.apply("hello world"));
//Predicate -T作为输入,返回的boolean值作为输出
Predicate<String> pre = (x) ->{
System.out.print(x);return false;};
System.out.println(": "+pre.test("hello World"));
//Consumer - T作为输入,执行某种动作但没有返回值
Consumer<String> con = (x) -> {
System.out.println(x);};
con.accept("hello world");
//Supplier - 没有任何输入,返回T
Supplier<String> supp = () -> {
return "Supplier";};
System.out.println(supp.get());
//BinaryOperator -两个T作为输入,返回一个T作为输出,对于“reduce”操作很有用
BinaryOperator<String> bina = (x,y) ->{
System.out.print(x+" "+y);return "BinaryOperator";};
System.out.println(" "+bina.apply("hello ","world"));
@FunctionalInterface
Java 8中的时区操作被很大程度上简化了,新的时区类 java.time.ZoneId
是原有的 java.util.TimeZone
类的替代品。
ZoneId对象可以通过 ZoneId.of()
方法创建,也可以通过 ZoneId.systemDefault()
获取系统默认时区:
ZoneId shanghaiZoneId = ZoneId.of("Asia/Shanghai");
ZoneId systemZoneId = ZoneId.systemDefault();
of()
方法接收一个“区域/城市”的字符串作为参数,你可以通过 getAvailableZoneIds()
方法获取所有合法的“区域/城市”字符串:
Set<String> zoneIds = ZoneId.getAvailableZoneIds();
对于老的时区类 TimeZone
,Java 8也提供了转化方法:
ZoneId oldToNewZoneId = TimeZone.getDefault().toZoneId();
有了 ZoneId
,我们就可以将一个 LocalDate
、LocalTime
或 LocalDateTime
对象转化为 ZonedDateTime
对象:
LocalDateTime localDateTime = LocalDateTime.now();
ZonedDateTime zonedDateTime = ZonedDateTime.of(localDateTime, shanghaiZoneId);
ZonedDateTime
对象由两部分构成,LocalDateTime
和 ZoneId
,其中 2018-03-03T15:26:56.147
部分为 LocalDateTime
,+08:00[Asia/Shanghai]
部分为ZoneId。
另一种表示时区的方式是使用 ZoneOffset
,它是以当前时间和 世界标准时间(UTC)/格林威治时间(GMT) 的偏差来计算,例如:
ZoneOffset zoneOffset = ZoneOffset.of("+09:00");
LocalDateTime localDateTime = LocalDateTime.now();
OffsetDateTime offsetDateTime = OffsetDateTime.of(localDateTime, zoneOffset);
Instant类在Java日期与时间功能中,表示了时间线上一个确切的点,定义为距离初始时间的时间差(初始时间为GMT 1970年1月1日00:00)经测量一天有86400秒,从初始时间开始不断向前移动。
创建一个Instant实例
你可以通过Instant类的工厂方法创建一个Instant实例,例如你可以调用instant.now()来创建一个确切的表达当前时间的Instant对象:
Instant now = Instant.now();
另外也有一些其它方法能创建Instant,具体请查阅Java官方文档。
访问Instant的时间
一个Instant对象里有两个域:距离初始时间的秒钟数、在当前一秒内的第几纳秒,他们的组合表达了当前时间点。你可以通过以下两个方法得到它们的值:
long seconds = getEpochSecond()
int nanos = getNano()
Instant的计算
Instant类有一些方法,可以用于获得另一Instant的值,例如:
plusSeconds()
plusMillis()
plusNanos()
minusSeconds()
minusMillis()
minusNanos()
我下面将向你展示两个例子,来说明这些方法如何使用:
Instant now = Instant.now();
Instant later = now.plusSeconds(3);
Instant earlier = now.minusSeconds(3);
第一行获得了一个Instant对象,表示当前时间。第二行创建了一个Instant表示三秒后,第三行创建了一个Instant表示三秒前。
seconds 表示从
1970-01-01 00:00:00
开始到现在的秒数,nanos 表示纳秒部分(nanos的值不会超过999,999,999)
Clock类提供了访问当前日期和时间的方法,Clock是时区敏感的,可以用来取代 System.currentTimeMillis()
来获取当前的微秒数。
某一个特定的时间点也可以使用Instant类来表示,Instant 类也可以用来创建老的 java.util.Date
对象。
Clock clock = Clock.systemDefaultZone();
long millis = clock.millis();
Instant instant = clock.instant();
Date legacyDate = Date.from(instant); // legacy java.util.Date
LocalDate类是Java 8中日期时间功能里表示一个本地日期的类,它的日期是无时区属性的。
可以用来表示生日、节假日期等等。这个类用于表示一个确切的日期,而不是这个日期所在的时间(如java.util.Date中的2000.01.01表示的实际是这一天的00:00这个瞬间)。
LocalDate类位于java.time包下,人名叫java.time.LocalDate,创建出来的实例也是不可变对象,所以涉及它的计算方法将返回一个新的LocalDate。
创建一个LocalDate实例
我们有多种方式可以创建出 LocalDate
实例。第一种方法是使用 now()
方法获得值为今天当日的 LocalDate
对象:
LocalDate localDate = LocalDate.now();
另一种方法是使用年月日信息构造出LocalDate对象:
LocalDate localDate2 = LocalDate.of(2018, 3, 3);
LocalDate 的 of()
方法创建出一个指定年月日的日期,并且没有时区信息。
访问日期信息
可以用如下方法访问LocalDate中的日期信息:
int year = localDate.getYear();
Month month = localDate.getMonth();
int dayOfMonth = localDate.getDayOfMonth();
int dayOfYear = localDate.getDayOfYear();
DayOfWeek dayOfWeek = localDate.getDayOfWeek();
可以注意到getMonth()与getDayOfWeek()方法返回了一个枚举类型代替一个int。你可以通过枚举类型中的getValue()来获得信息。
LocalDate计算
你可以进行一堆简单的日期计算,只要使用如下的方法:
plusDays()
plusWeeks()
plusMonths()
plusYears()
minusDays()
minusWeeks()
minusMonths()
minusYears()
以下举几个使用的例子来帮助理解使用:
LocalDate d = LocalDate.of(2018, 3, 5);
LocalDate d1 = localDate.plusYears(3);
LocalDate d2 = localDate.minusYears(3);
LocalTime类是Java 8中日期时间功能里表示一整天中某个时间点的类,它的时间是无时区属性的(早上10点等等)。比如你需要描述学校几点开学,这个时间不涉及在什么城市,这个描述是对任何国家城市都适用的,此时使用无时区的LocalTime就足够了。
LocalTime类的对象也是不可变的,所以计算方法会返回一个新的LocalTime实例。
创建一个LocatTime实例
有多种方式可以新建LocalTime实例。比如使用当前时间作为值新建对象:
LocalTime localTime = LocalTime.now();
另一种方式是使用指定的时分秒和纳秒来新建对象:
LocalTime localTime2 = LocalTime.of(21, 30, 59, 11001);
也有另一种版本的 of()
方法只需要小时分钟两项,或时分秒三项值作为参数。
访问LocalTime对象的时间
你可以通过这些方法访问其时、分、秒、纳秒:
getHour()
getMinute()
getSecond()
getNano()
LocalTime的计算
LocalTime类包含一系列方法,能帮你完成时间计算:
plusHours()
plusMinutes()
plusSeconds()
plusNanos()
minusHours()
minusMinutes()
minusSeconds()
minusNanos()
以下举一个例子:
LocalTime localTime2 = LocalTime.of(21, 30, 59, 11001);
LocalTime localTimeLater = localTime.plusHours(3);
LocalTime localTimeEarlier = localTime.minusHours(3);
LocalTime类的对象也是不可变的,所以计算方法会返回一个新的LocalTime实例。
LocalDateTime类是Java 8中日期时间功能里,用于表示当地的日期与时间的类,它的值是无时区属性的。你可以将其视为Java 8中LocalDate与LocalTime两个类的结合。
LocalDateTime类的值是不可变的,所以其计算方法会返回一个新的LocalDateTime实例。
创建一个LocatDateTime实例
可以通过LocalDateTime的静态工厂方法来创建LocalDateTime实例。以下举例使用 now()
方法创建:
LocalDateTime localDateTime = LocalDateTime.now();
另一种方式是使用指定的年月日、时分秒、纳秒来新建对象:
LocalDateTime localDateTime2 = LocalDateTime.of(2018, 11, 26, 13, 55, 36, 123);
访问LocalDateTime对象的时间
你可以通过这些方法访问其日期时间:
getYear()
getMonth()
getDayOfMonth()
getDayOfWeek()
getDayOfYear()
getHour()
getMinute()
getSecond()
getNano()
这些方法中有一些返回int有一些返回枚举类型,你可以通过枚举类型中的 getValue()
方法来获得int值。
LocalDateTime的计算
LocalDateTime 类包含一系列方法,能帮你完成时间计算:
plusYears()
plusMonths()
plusDays()
plusHours()
plusMinutes()
plusSeconds()
plusNanos()
minusYears()
minusMonths()
minusDays()
minusHours()
minusMinutes()
minusSeconds()
minusNanos()
以下举一个例子:
LocalDateTime localDateTime = LocalDateTime.now();
LocalDateTime localDateTime1 = localDateTime.plusYears(3);
LocalDateTime localDateTime2 = localDateTime.minusYears(3);
ZonedDateTime类是Java 8中日期时间功能里,用于表示带时区的日期与时间信息的类。可以用于表示一个真实事件的开始时间,如某火箭升空时间等等。
ZonedDateTime 类的值是不可变的,所以其计算方法会返回一个新的ZonedDateTime 实例。
创建一个ZonedDateTime实例
有多种方式可以新建ZonedDateTime实例。比如使用当前时间作为值新建对象:
ZonedDateTime dateTime = ZonedDateTime.now();
另一种方式是使用指定的年月日、时分秒、纳秒以及时区ID来新建对象:
ZoneId zoneId = ZoneId.of("UTC+1");
ZonedDateTime dateTime2 = ZonedDateTime.of(2015, 11, 30, 23, 45, 59, 1234, zoneId);
访问ZonedDateTime对象的时间
你可以通过这些方法访问其日期时间:
getYear()
getMonth()
getDayOfMonth()
getDayOfWeek()
getDayOfYear()
getHour()
getMinute()
getSecond()
getNano()
这些方法中有一些返回int有一些返回枚举类型,但可以通过枚举类型中的getValue()方法来获得int值。
ZonedDateTime的计算
ZonedDateTime类包含一系列方法,能帮你完成时间计算:
plusYears()
plusMonths()
plusDays()
plusHours()
plusMinutes()
plusSeconds()
plusNanos()
minusYears()
minusMonths()
minusDays()
minusHours()
minusMinutes()
minusSeconds()
minusNanos()
但注意计算时,若不巧跨越了夏令时(会补一小时或减一小时),可能得不到希望的结果。一个替代的正确做法是使用Period:
ZonedDateTime zoneDateTime = previousDateTime.plus(Period.ofDays(3));
时区
时区是用ZoneId类表示的,你可以使用ZoneId.now()或ZoneId.of(“xxx”)来实例化:
ZoneId zoneId = ZoneId.of("UTC+1");
传给 of()
方法的参数是时区的ID,如“UTC+1”指距离UTC(格林威治时间)有一小时的时差,你可以使用你想要的时差来表示ZoneId(如+1与-5等等)
你也可以使用另一种方式表示zone id,即使用地区名字,也是可以的:
ZoneId zoneId2 = ZoneId.of("Europe/Copenhagen");
ZoneId zoneId3 = ZoneId.of("Europe/Paris");
DateTimeFormatter类是Java 8中日期时间功能里,用于解析和格式化日期时间的类,位于 java.time.format
包下。
预定义的DateTimeFormatter实例
DateTimeFormatter类包含一系列预定义(常量)的实例,可以解析和格式化一些标准时间格式。这将让你免除麻烦的时间格式定义,类中包含如下预定义的实例:
BASIC_ISO_DATE
ISO_LOCAL_DATE
ISO_LOCAL_TIME
ISO_LOCAL_DATE_TIME
ISO_OFFSET_DATE
ISO_OFFSET_TIME
ISO_OFFSET_DATE_TIME
ISO_ZONED_DATE_TIME
ISO_INSTANT
ISO_DATE
ISO_TIME
ISO_DATE_TIME
ISO_ORDINAL_TIME
ISO_WEEK_DATE
RFC_1123_DATE_TIME
每个预定义的DateTimeFormatter实例都有不同的日期格式,我就不解释全部的了。具体的可以查阅Java官方文档,但我在这篇的后续中会解释其中几个,以方便理解。
格式化日期
当你获取一个DateTimeFormatter实例后,就可以用format()方便来将一个日期格式化为某种字符串,例如:
DateTimeFormatter formatter = DateTimeFormatter.BASIC_ISO_DATE;
String formattedDate = formatter.format(LocalDate.now());
System.out.println(formattedDate);
这个样例把LocalDate对象格式化了,并输出20150703,这个输出表示现在2018年,3月5日。
再举一个关于ZonedDateTime的例子:
DateTimeFormatter formatter = DateTimeFormatter.BASIC_ISO_DATE;
String formattedZonedDate = formatter.format(ZonedDateTime.now());
System.out.println("formattedZonedDate = " + formattedZonedDate);
这个例子会输出:20180305+0800
表示今年2018年,3月5日,位于UTC+8时区。
一个Duration对象表示两个Instant间的一段时间,是在Java 8中加入的新功能。
一个Duration实例是不可变的,当创建出对象后就不能改变它的值了。你只能通过Duration的计算方法,来创建出一个新的Durtaion对象。你会在之后的教程中见到的。
创建Duration实例
使用 Duration
类的工厂方法来创建一个 Duration
对象,以下是一个使用 between()
的例子:
Instant first = Instant.now();
// wait some time while something happens
Instant second = Instant.now();
Duration duration = Duration.between(first, second);
访问Duration的时间
一个Duration对象里有两个域:纳秒值(小于一秒的部分),秒钟值(一共有几秒),他们的组合表达了时间长度。注意屯使用System.getCurrentTimeMillis()时不同,Duration不包含毫秒这个属性。
你可以通过以下两个方法得到它们的值:
long seconds = getSeconds()
int nanos = getNano()
你也可以转换整个时间到其它单位如纳秒、分钟、小时、天:
toNanos()
toMillis()
toMinutes()
toHours()
toDays()
举例而言:toNanos()
与 getNano()
不同,toNanos()
获得的是 Duration
整个时间共有多少纳秒,
而 getNano()
只是获得这段时间中小于一秒的部分。
你也许会问,为什么没有 toSeconds()
方法,因为已经有 getSeconds()
这个方法能达到同样的功能了。
Duration计算
Duration类包含一系列的计算方法:
plusNanos()
plusMillis()
plusSeconds()
plusMinutes()
plusHours()
plusDays()
minusNanos()
minusMillis()
minusSeconds()
minusMinutes()
minusHours()
minusDays()
这些方法所做的事都是相似的,我在这儿也不展示内部实现细节了,就展示一个加减的例子吧:
Duration start = ... //obtain a start duration
Duration added = start.plusDays(3);
Duration subtracted = start.minusDays(3);
所有的计算方法都会返回一个新的Duration,以保证Duration的不可变属性。
long days = duration.toDays(); // 这段时间的总天数
long hours = duration.toHours(); // 这段时间的小时数
long minutes = duration.toMinutes(); // 这段时间的分钟数
long seconds = duration.getSeconds(); // 这段时间的秒数
long milliSeconds = duration.toMillis(); // 这段时间的毫秒数
long nanoSeconds = duration.toNanos(); // 这段时间的纳秒数
Java 8中的日期/时间类都是不可变的,这是为了保证线程安全。当然,新的日期/时间类也提供了方法用于创建对象的可变版本,比如增加一天或者减少一天:
LocalDate date = LocalDate.of(2017, 1, 5); // 2017-01-05
LocalDate date1 = date.withYear(2016); // 修改为 2016-01-05
LocalDate date2 = date.withMonth(2); // 修改为 2017-02-05
LocalDate date3 = date.withDayOfMonth(1); // 修改为 2017-01-01
LocalDate date4 = date.plusYears(1); // 增加一年 2018-01-05
LocalDate date5 = date.minusMonths(2); // 减少两个月 2016-11-05
LocalDate date6 = date.plus(5, ChronoUnit.DAYS); // 增加5天 2017-01-10
上面例子中对于日期的操作比较简单,但是有些时候我们要面临更复杂的时间操作,比如将时间调到下一个工作日,
或者是下个月的最后一天,这时候我们可以使用 with()
方法的另一个重载方法,它接收一个TemporalAdjuster参数,
可以使我们更加灵活的调整日期:
LocalDate date7 = date.with(nextOrSame(DayOfWeek.SUNDAY)); // 返回下一个距离当前时间最近的星期日
LocalDate date9 = date.with(lastInMonth(DayOfWeek.SATURDAY)); // 返回本月最后一个星期六
要使上面的代码正确编译,你需要使用静态导入 TemporalAdjusters
对象:
import static java.time.temporal.TemporalAdjusters.*;
TemporalAdjusters
类中包含了很多静态方法可以直接使用,下面的表格列出了一些方法:
方法名 | 描述 |
---|---|
dayOfWeekInMonth |
返回同一个月中每周的第几天 |
firstDayOfMonth |
返回当月的第一天 |
firstDayOfNextMonth |
返回下月的第一天 |
firstDayOfNextYear |
返回下一年的第一天 |
firstDayOfYear |
返回本年的第一天 |
firstInMonth |
返回同一个月中第一个星期几 |
lastDayOfMonth |
返回当月的最后一天 |
lastDayOfNextMonth |
返回下月的最后一天 |
lastDayOfNextYear |
返回下一年的最后一天 |
lastDayOfYear |
返回本年的最后一天 |
lastInMonth |
返回同一个月中最后一个星期几 |
next / previous |
返回后一个/前一个给定的星期几 |
nextOrSame / previousOrSame |
返回后一个/前一个给定的星期几,如果这个值满足条件,直接返回 |
如果上面表格中列出的方法不能满足你的需求,你还可以创建自定义的 TemporalAdjuster
接口的实现,
TemporalAdjuster
也是一个函数式接口,所以我们可以使用Lambda表达式:
@FunctionalInterface
public interface TemporalAdjuster {
Temporal adjustInto(Temporal temporal);
}
比如给定一个日期,计算该日期的下一个工作日(不包括星期六和星期天):
LocalDate date = LocalDate.of(2017, 1, 5);
date.with(temporal -> {
// 当前日期
DayOfWeek dayOfWeek = DayOfWeek.of(temporal.get(ChronoField.DAY_OF_WEEK));
// 正常情况下,每次增加一天
int dayToAdd = 1;
// 如果是星期五,增加三天
if (dayOfWeek == DayOfWeek.FRIDAY) {
dayToAdd = 3;
}
// 如果是星期六,增加两天
if (dayOfWeek == DayOfWeek.SATURDAY) {
dayToAdd = 2;
}
return temporal.plus(dayToAdd, ChronoUnit.DAYS);
});
Java中使用的历法是ISO 8601日历系统,它是世界民用历法,也就是我们所说的公历。平年有365天,闰年是366天。闰年的定义是:非世纪年,能被4整除;世纪年能被400整除。为了计算的一致性,公元1年的前一年被当做公元0年,以此类推。
此外Java 8还提供了4套其他历法(很奇怪为什么没有汉族人使用的农历),每套历法都包含一个日期类,分别是:
ThaiBuddhistDate
:泰国佛教历MinguoDate
:中华民国历JapaneseDate
:日本历HijrahDate
:伊斯兰历每个日期类都继承 ChronoLocalDate
类,所以可以在不知道具体历法的情况下也可以操作。不过这些历法一般不常用,除非是有某些特殊需求情况下才会使用。
这些不同的历法也可以用于向公历转换:
LocalDate date = LocalDate.now();
JapaneseDate jpDate = JapaneseDate.from(date);
由于它们都继承ChronoLocalDate类,所以在不知道具体历法情况下,可以通过ChronoLocalDate类操作日期:
Chronology jpChronology = Chronology.ofLocale(Locale.JAPANESE);
ChronoLocalDate jpChronoLocalDate = jpChronology.dateNow();
我们在开发过程中应该尽量避免使用 ChronoLocalDate
,尽量用与历法无关的方式操作时间,因为不同的历法计算日期的方式不一样,
比如开发者会在程序中做一些假设,假设一年中有12个月,如果是中国农历中包含了闰月,一年有可能是13个月,
但开发者认为是12个月,多出来的一个月属于明年的。
再比如假设年份是累加的,过了一年就在原来的年份上加一,但日本天皇在换代之后需要重新纪年,所以过了一年年份可能会从1开始计算。
在实际开发过程中建议使用 LocalDate
,包括存储、操作、业务规则的解读;除非需要将程序的输入或者输出本地化,
这时可以使用 ChronoLocalDate
类。
Lombok
是一种Java™
实用工具,可用来帮助开发人员消除 Java 的冗长,尤其是对于简单的 Java 对象(POJO)。它通过注解实现这一目的。
Lombok
采取注解形式,在编译后,自动生成相应的方法,所以需要下载插件来支持它。lombok plugin
安装即可。<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${
lombok.version}</version>
<scope>provided</scope>
</dependency>
getter
、setter
方法@Getter
@Setter
public class Notice {
private Integer id;
private String title;
private Boolean isDeleted;
}
编译后将会转化为
public class Notice {
private Integer id;
private String title;
private Boolean isDeleted;
public Notice() {
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public Boolean getDeleted() {
return isDeleted;
}
public void setDeleted(Boolean deleted) {
isDeleted = deleted;
}
}
equals
和hashCode
。@Data
注解就可以有下面几个注解的功能:@ToString
、@Getter
、@Setter
、@EqualsAndHashCode
、@NoArgsConstructor
。@Data
和@AllArgsConstructor
后 ,默认的无参构造函数失效,如果需要它,要重新设置@NoArgsConstructor
log.info(xxxx);
try with resource
@Cleanup
InputStream in = new FileInputStream(args[0]);
@Cleanup
OutputStream out = new FileOutputStream(args[1]);
public NonNullExample(@NonNull Person person) {
this.name = person.getName();
}
转换为:
public NonNullExample(@NonNull Person person) {
if (person == null) {
throw new NullPointerException("person");
}
this.name = person.getName();
}
@SneakyThrows(Exception.class)
private final Object lock = new Object();
@Synchronized("lock")
public void foo() {
// Do something
}