Java 14都来了,你还不了解Java 8吗?赶紧的,上车!
啥?文章看不懂?没关系,视频教程在这里 -> Java8新特性
文档中所有的基础数据。
对象数据结构:
public class Employee {
private Long id;
private String name;
private Integer age;
private Double salary;
// constructor/getter/setter/toString
}
集合数据:
public class DataUtils {
private static List<Employee> emps = new ArrayList<>();
static {
emps.add(new Employee(1L, "Morton", 22, 8000.0));
emps.add(new Employee(2L, "Dahlia", 20, 13000.0));
emps.add(new Employee(3L, "Babb", 23, 7000.0));
emps.add(new Employee(4L, "Rice", 21, 15000.0));
emps.add(new Employee(5L, "Handy", 26, 13000.0));
emps.add(new Employee(6L, "Rudy", 30, 14000.0));
emps.add(new Employee(7L, "Grady", 27, 12000.0));
emps.add(new Employee(8L, "Brenton", 32, 6000.0));
emps.add(new Employee(9L, "Vinson", 33, 7000.0));
emps.add(new Employee(10L, "Kemp", 23, 14000.0));
emps.add(new Employee(11L, "Sebastian", 22, 12000.0));
emps.add(new Employee(12L, "Evangeline", 24, 18000.0));
emps.add(new Employee(13L, "Lisette", 29, 8000.0));
emps.add(new Employee(14L, "Wilkes", 25, 7000.0));
emps.add(new Employee(15L, "Leach", 33, 6000.0));
emps.add(new Employee(16L, "Geiger", 32, 12000.0));
emps.add(new Employee(17L, "Holden", 34, 13000.0));
emps.add(new Employee(18L, "Thorpe", 26, 15000.0));
emps.add(new Employee(19L, "Adrienne", 23, 16000.0));
emps.add(new Employee(20L, "Calderon", 21, 14000.0));
}
public static List<Employee> getEmployees() {
return emps;
}
}
/**
* 找出年龄大于30的员工
*/
@Test
public void test01() {
List<Employee> emps = DataUtils.getEmployees();
List<Employee> result = new ArrayList<>();
for (Employee emp: emps) {
if (emp.getAge() > 30) {
result.add(emp);
}
}
System.out.println(result);
}
/**
* 找出员工式资大于10000的员工
*/
@Test
public void test02() {
List<Employee> emps = DataUtils.getEmployees();
List<Employee> result = new ArrayList<>();
for (Employee emp: emps) {
if (emp.getSalary() > 10000) {
result.add(emp);
}
}
System.out.println(result);
}
/**
* 找出年龄大于30工资大于10000的员工
*/
@Test
public void test03() {
List<Employee> emps = DataUtils.getEmployees();
List<Employee> result = new ArrayList<>();
for (Employee emp : emps) {
if (emp.getSalary() > 10000 && emp.getAge() > 30) {
result.add(emp);
}
}
System.out.println(result);
}
通过上面的代码可以看出,这三个代码片段大都是相同的,变化的只是if
的判断方式。那些相同的代码都是些重复性代码、冗余代码。那么有没有更好的方法来优化它们呢?
根据上面三个需求我们可以抽象出一个接口,接口中提供一个抽象方法,实现类实现其判断方式。
public interface EmployeePredicate {
boolean test(Employee employee);
}
根据上面三个需求,将重复性代码提取成一个共用方法:
public List<Employee> queryEmployees(List<Employee> emps, EmployeePredicate employeePredicate) {
List<Employee> result = new ArrayList<>();
for (Employee emp: emps) {
if (employeePredicate.test(emp)) {
result.add(emp);
}
}
return result;
}
那么,对于上面三个需求,我们只需要用这同一个方法即可。对于不同的判断,我们只需要根据需求实现EmployeePredicate
即可。
接口实现,实现判断方式:
public class EmployeeAgePredicate implements EmployeePredicate {
@Override
public boolean test(Employee employee) {
return employee.getAge() > 30;
}
}
使用接口:
@Test
public void test04() {
List<Employee> emps = DataUtils.getEmployees();
// 调用公共方法,伟入接口实现
List<Employee> result = queryEmployees(emps, new EmployeeAgePredicate());
System.out.println(result);
}
接口实现,实现判断方式:
public class EmployeeSalaryPredicate implements EmployeePredicate {
@Override
public boolean test(Employee employee) {
return employee.getSalary() > 10000;
}
}
使用:
@Test
public void test05() {
List<Employee> emps = DataUtils.getEmployees();
List<Employee> result = queryEmployees(emps, new EmployeeSalaryPredicate());
System.out.println(result);
}
接口实现,实现判断方式:
public class EmployeeAgeAndSalaryPredicate implements EmployeePredicate {
@Override
public boolean test(Employee employee) {
return employee.getAge() > 30 && employee.getSalary() > 10000;
}
}
使用:
@Test
public void test06() {
List<Employee> emps = DataUtils.getEmployees();
List<Employee> result = queryEmployees(emps, new EmployeeAgeAndSalaryPredicate());
System.out.println(result);
}
根据上面的修改,那些重复性的冗余的代码被消除了。但是又有新的问题出现了,就是每增加一个需求,那么对应的接口实现类就会增加一个。这样对于过多的需求就有很多的实现类,这也是不可取的。那么,要如何消除这种情况呢?
根据1.2中的分析,如果不需要实现类,Java中提供了匿名类来代替。使用匿名类,我们需要保留接口和公式方法。
接口:
public interface EmployeePredicate {
boolean test(Employee employee);
}
公共方法:
public List<Employee> queryEmployees(List<Employee> emps, EmployeePredicate employeePredicate) {
List<Employee> result = new ArrayList<>();
for (Employee emp: emps) {
if (employeePredicate.test(emp)) {
result.add(emp);
}
}
return result;
}
使用匿名类修改需求。
@Test
public void test07() {
List<Employee> emps = DataUtils.getEmployees();
List<Employee> result = queryEmployees(emps, new EmployeePredicate() {
@Override
public boolean test(Employee employee) {
return employee.getAge() > 30;
}
});
System.out.println(result);
}
@Test
public void test08() {
List<Employee> emps = DataUtils.getEmployees();
List<Employee> result = queryEmployees(emps, new EmployeePredicate() {
@Override
public boolean test(Employee employee) {
return employee.getSalary() > 10000;
}
});
System.out.println(result);
}
@Test
public void test09() {
List<Employee> emps = DataUtils.getEmployees();
List<Employee> result = queryEmployees(emps, new EmployeePredicate() {
@Override
public boolean test(Employee employee) {
return employee.getAge() > 30 && employee.getSalary() > 10000;
}
});
System.out.println(result);
}
通过使用匿名类完成上述所有需求,虽然解决了过多实现的问题,但是又回到了重复性问题上。Java 8中Lambda表达式的出现,正好解决了这些问题,让代码看上去非常简单且容易理解。
同样的,使用Lambda表达式,我们保留接口和公共方法。
接口:
public interface EmployeePredicate {
boolean test(Employee employee);
}
公共方法:
public List<Employee> queryEmployees(List<Employee> emps, EmployeePredicate employeePredicate) {
List<Employee> result = new ArrayList<>();
for (Employee emp: emps) {
if (employeePredicate.test(emp)) {
result.add(emp);
}
}
return result;
}
使用Lambda表达式:
@Test
public void test10() {
List<Employee> emps = DataUtils.getEmployees();
List<Employee> result = queryEmployees(emps, employee -> employee.getAge() > 30);
System.out.println(result);
}
@Test
public void test11() {
List<Employee> emps = DataUtils.getEmployees();
List<Employee> result = queryEmployees(emps, employee -> employee.getSalary() > 10000);
System.out.println(result);
}
@Test
public void test11() {
List<Employee> emps = DataUtils.getEmployees();
List<Employee> result = queryEmployees(emps, employee -> employee.getAge() > 30 && employee.getSalary() > 10000);
System.out.println(result);
}
通过上面的修改,可以发现使用Lambda表达式使代码看上去明显简洁了很多,而且要表达的意图也清晰了很多。
上一章我们感受了Lambda表达式的魅力,这一节我们将深入学习Lambda。
Lambda表达式,既然叫做表达式,那么它要表达式的是什么呢?实际上Lambda表达的是一个函数,这个函数只有参数列表和函数体,所以Lambda表达式也被叫做匿名函数。
Lambda由三部分组成,分别是参数列表、箭头、函数体。
参数列表
(ParamType1 param1, ParamType2 param2, ...)
。(param1, param2, ...)
。()
,如param1
。()
不能省略。箭头
Lambda表达式的参数列表后紧跟关的是箭头,如->
。
函数体
{ System.out.println("hello world!")}
。{}
,如System.out.println("hello world!")}
。return
关键字。举例:
Comparator<Integer> comparator = (Integer a, Integer b) -> {
return a - b;
};
Comparator<Integer> comparator = (a, b) -> {
return a - b;
};
()
Predicate<Integer> predicate = a -> {
return a > 3;
};
Runnable r = () -> {
System.out.println("Lambda表达式");
};
{}
Runnable r = () -> System.out.println("Lambda表达式");
return
关键字Predicate<Integer> predicate = a -> a > 3;
通过上面的例子,不难看出Lambda表达式可以被一个对象接收,所以Lambda表达式也能看做是一个对象,相当于匿名类,而接收Lambda表达式的对象类型也被统称为函数式接口。
通过对2.1中接收Lambda表达式的对象的类型的研究,我们可以发现,这些接口都有一个共同特征,那就是这些接口中只有一个抽象方法。所以对于函数式接口的定义也就是:只有一个抽象方法的接口就是函数式接口。Lambda表达式只能作用在函数式接口上。
@FunctionalInterface
注解,一般的函数式接口,都由@FunctionalInterface
注解注释,该注释用于表式该接口就是函数式接口,也能用于校验当前接口是否满足函数式接口的定义。要注意,并不是说只有使用了@FunctionalInterface
注解的接口才是函数式接口,只要满足函数式接口的定义的接口都是函数式接口。
正如我们在第1章中所定义的EmployeePredicate
接口,该接口只有一个抽象方法(并没有@FunctionalInterface
注解),它是一个函数式接口,所以最后才能使用Lambda表达式来改进代码。
2.1节中,我们用了几个Lambda表达式,并且用一个变量接收了它们,那么,它们该如何工作呢?
以下面这个为例:
Runnable r = () -> System.out.println("Lambda表达式");
我们用Runnable r
变量接收了一个Lambda表达式,那么r
是一个Runnable
接口类型的对象(多态),由于Runnable
接口中只有一个抽象方法run()
,所以该对象能调用的方法也就是这个run()
方法。前面我们也说过,Lambda表达式表达的是一个函数,在这个例子中,该Lambda表达式表达的就是run()
这个方法(函数)了。所以我们要使这个Lambda正常工作只需要调用这个run()
方法即可。
@Test
public void test01() {
Runnable r = () -> System.out.println("Lambda表达式");
r.run();
}
同理,对于
Comparator<Integer> comparator = (a, b) -> a - b;
我们同样只需要调用Comparator
接口中的抽象方法即可:
@Test
public void test() {
Comparator<Integer> comparator = (a, b) -> a - b;
int result = comparator.compare(2, 3);
System.out.println(result);
}
至于上面的Predicate
,要如何使用这里不再赘述。
正如我们上面所说的,Lambda表达式可以省略参数列表中的参数类型,那么如果省略了参数类型,Lambda是如何知道参数是什么类型的呢?Lambda表达式中用到的就是类型推断!
Comparator<Integer> comparator = (a, b) -> a - b;
上例中,使用了Comparator
接口,下面是Comparator
接口的定义,结合上例,Comparator
中泛型的类型是T
是Integer
,所以compare
的参数类型的T
也就Integer
。
@FunctionalInterface
public interface Comparator<T> {
int compare(T o1, T o2);
}
这就是在Lambda中使用到的类型推断。
这里我们要明确一个概念外部变量,外部变量指定的是不属性于当前函数或者类的变量,比如Lambda表达式引入一个没有在Lambda表达式中定义的变量,匿名类引用一个没在该类中定义的变量,这些变量都属性外部变量。
使用匿名类,在Java 1.8之前引用的外部变量必须是final
类型的,在Java 1.8之后,外部变量可以不用使用final
关键字,但是引用的外部变量不允许被改变。
Lambda相当于匿名类,所以对于Lambda表达式要使用外部变量也有同样的限制。
@Test
public void test03() {
String msg = "message";
// msg = "hello"; 不允许被修改
Runnable r = () -> {
// msg = "hello"; 不允许被修改
System.out.println(msg);
};
// msg = "hello"; 不允许被修改
r.run();
}
所以在Lambda中有引用到外部变量,该外部变量是不允许被改变的。
在JDK中也默认提供了一些常用的函数式接口,这些接口在java.util.function
包下,这里列举一些基本的函数式接口:
函数式接口 | 函数描述符 | 参数 | 返回类型 | 示例(在集合中的应用) |
---|---|---|---|---|
Predicate |
bolean test(T t); |
T |
boolean |
Stream |
Comsumer |
void accept(T t); |
T |
void |
void forEach(Consumer super T> action); |
Function |
R apply(T t); |
T |
R |
|
Supplier |
T get() |
无 | T |
上述4个函数式接口,是最常用的几个函数式接口。
关于装箱拆箱
如
Predicate
中的T
是泛型,泛型不能接收基本类型,但是使用包装类型会增加内存和计算开销,所以对于基本类型Java 8中也提供了对应的函数式接口,基本类型的函数式接口主要有:
函数式接口 函数描述符 IntPredicate
boolean test(int value);
IntConsumer
void accept(int value);
IntFunction
R apply(int value);
IntSupplier
int getAsInt();
以
int
类型为例的函数式接口如上,其他类型类同 。要注意,基本类型的函数式接口只针对常用的long
、int
及double
。其他基本类型没有对应的函数式接口。
更多函数式接口:
关于常用函数式接口总结:
函数式接口 | 函数描述符 | 扩展 |
---|---|---|
Predicate |
T -> boolean |
IntPredicate 、LongPredicate 、DoublePredicate |
Consumer |
T -> void |
IntConsumer 、LongConsumer 、DoubleConsumer |
Function |
T -> R |
IntFunction 、IntToDoubleFunction 、IntToLongFunction LongFunction 、LongToDoubleFunction 、LongToIntFunction DoubleFunction 、DoubleToIntFunction 、DoubleToLongFunction ToIntFunction 、ToDoubleFunction 、ToLongFunction |
Supplier |
() -> T |
BooleanSupplier 、IntSupplier 、LongSupplier |
BinaryOperator |
(T, T) -> T |
IntBinaryOperator 、LongBinaryOperator 、DoubleBinaryOperator |
BiPredicate |
(L, R) -> boolean |
|
BiConsumer |
(T, U) -> void |
ObjIntConsumer 、ObjLongConsumer 、ObjDoubleConsumer |
BiFunction |
(T, U) -> R |
ToIntBiFunction 、ToLongBiFunction 、ToDoubleBiFunction |
在Java 8之前,接口中的所有方法必须是抽象方法。实现该接口的类都必须实现接口中所有抽象方法,除非这个类是抽象类。
Java 8中使用了一个default
关键字用于在接口中定义默认方法,该方法有方法体,如果实现该接口的类没有“实现”(重写)该方法,那么实现类将使用默认方法,如果实现类中“实现”(重写)了该方法,实现类中的该方法将覆盖接口中的默认方法。
例如Java 8中的stream
方法。Java 8中为集合Collection
添加了stream()
方法用于获取流,该stream
方法就是一个抽象方法。如果不添加默认方法,而是添加stream()
抽象方法,自定义的实现了Collection
的类也要实现该抽象方法。如果自定义的类是在Java 8之前就已经有了,那么Java版本升级到Java 8之后,所有的自定义的Collection
实现类都得实现该抽象方法。如果项目中引用的第三方库中的集合类,就会出现版本不兼容情况。
在Collection
中使用default
关键字的例子:
default Stream<E> stream() {
return StreamSupport.stream(spliterator(), false);
}
接口中的默认方法,子类(接口继承、实现)如果重写了,子类的优先级更高。
实现多接口,接口中有相同的默认方法需要指明使用哪一个接口中定义的默认方法
public interface A {
default void show() {
System.out.println("A ..");
}
}
public interface B {
default void show() {
System.out.println("B ..");
}
}
public class C implements A, B{
@Override
public void show() {
// 通过Super来指定具体要使用的方法
A.super.show();
}
}
// 类中有抽象方法,类必须是抽象的
public abstract class C implements A, B{
@Override
public abstract void show();
}
Java 8中,接口也能添加静态方法。
public interface InterfaceStaticMethod {
static void staticMethod() {
System.out.println("这是接口中的静态方法");
}
}
Preidcate
Predicate
的接口定义如下:
@FunctionalInterface
public interface Predicate<T> {
boolean test(T t);
default Predicate<T> and(Predicate<? super T> other) {
Objects.requireNonNull(other);
return (t) -> test(t) && other.test(t);
}
default Predicate<T> negate() {
return (t) -> !test(t);
}
default Predicate<T> or(Predicate<? super T> other) {
Objects.requireNonNull(other);
return (t) -> test(t) || other.test(t);
}
static <T> Predicate<T> isEqual(Object targetRef) {
return (null == targetRef)
? Objects::isNull
: object -> targetRef.equals(object);
}
}
通过接口定义,可以看到该接口可以用于判断,其抽象方法test
返回值为boolean
,其中该接口还提供了3个默认方法,分别是and
、negate
、or
和一个静态方法isEqual
。下面我们分别来讨论这几个方法。
test
:用于判断
@Test
public void test01() {
// 接收Lambda表达式
Predicate<Employee> employeePredicate = employee -> employee.getAge() > 30;
// 使用Lambda
boolean test = employeePredicate.test(new Employee(1L, "张三", 30, 10000.0));
System.out.println(test);
}
and
:用于组合判断,相当于且
@Test
public void test02() {
// 接收Lambda表达式
Predicate<Employee> employeePredicate = employee -> employee.getAge() > 30;
// 使用and组合两个Predicate
Predicate<Employee> andPredicate = employeePredicate.and(employee -> employee.getSalary() > 10000);
// 使用
Assert.assertFalse(andPredicate.test(new Employee(1L, "张三", 30, 10000.0)));
}
negate
:判断取反,如果不使用negate
为true的话使用则为false
,反之亦然
@Test
public void test03() {
// 接收Lambda表达式
Predicate<Employee> employeePredicate = employee -> employee.getAge() > 30;
// negate取反
Assert.assertTrue(employeePredicate.negate().test(new Employee(1L, "张三", 30, 10000.0)));
}
or
:用于组合判断,相当于或
@Test
public void test04() {
// 接收Lambda表达式
Predicate<Employee> employeePredicate = employee -> employee.getAge() > 30;
Predicate<Employee> employeePredicate1 = employee -> employee.getSalary() == 10000;
Predicate<Employee> predicate = employeePredicate.or(employeePredicate1);
Assert.assertTrue(predicate.test(new Employee(1L, "张三", 30, 10000.0)));
}
isEqual
:用于对象的比较
@Test
public void test05() {
// 接收Lambda表达式
Employee employee = new Employee(1L, "张三", 30, 10000.0);
// isEqual接收一个对象,该对象是一个被比较的目标对象
Predicate<Employee> predicate = Predicate.isEqual(employee);
// test接收一个对象,该对象是要比较的对象,与目标对象比较
Assert.assertTrue(predicate.test(employee));
}
Consumer
Consumer
接口的定义如下,该接口的抽象方法accept
接收一个对象不返回值,其中该接口还提供了一个默认方法andThen
@FunctionalInterface
public interface Consumer<T> {
void accept(T t);
default Consumer<T> andThen(Consumer<? super T> after) {
Objects.requireNonNull(after);
return (T t) -> { accept(t); after.accept(t); };
}
}
accept
:接收一个对象,消费该对象,不返回值
@Test
public void test06() {
Consumer<String> consumer = x -> System.out.println(x);
consumer.accept("hello world !");
}
andThen
:接收一个Consumer
,用于再次“消费”
@Test
public void test07() {
Consumer<String> consumer = x -> System.out.println(x + "^_^");
Consumer<String> andThen = consumer.andThen(x -> System.out.println(x.toUpperCase()));
andThen.accept("hello world!");
}
// 所以打印结果会是:
/**
* hello world!^_^
* HELLO WORLD!
**/
Function
Function
接口定义如下:
@FunctionalInterface
public interface Function<T, R> {
R apply(T t);
default <V> Function<V, R> compose(Function<? super V, ? extends T> before) {
Objects.requireNonNull(before);
return (V v) -> apply(before.apply(v));
}
default <V> Function<T, V> andThen(Function<? super R, ? extends V> after) {
Objects.requireNonNull(after);
return (T t) -> after.apply(apply(t));
}
static <T> Function<T, T> identity() {
return t -> t;
}
}
该接口的抽象方法apply(T t)
接一个T类型的参数,返回一个R类型的值,其中该接口还提供了compose
、andThen
两个默认方法以及一个identity
静态方法。
apply
:接收一个对象,返回另一个对象
@Test
public void test08() {
Function<Integer, Long> integerToLongFunction = i -> i.longValue();
Long apply = integerToLongFunction.apply(10);
}
compose
:接收一个Function
,将接收的Function
返回的值作为参数传递给当前调用compose
的Function
@Test
public void test09() {
Function<Integer, Integer> function1 = i -> i * 2;
Function<Integer, Integer> function2 = function1.compose(x -> x - 1);
Integer apply = function2.apply(10); // 结果为18 = (10 - 1) * 2
}
andThen
:接收一个Function
,将调用andThen
方法的Function
返回的值作为参数传递给接收的Function
@Test
public void test09() {
Function<Integer, Integer> function1 = i -> i * 2;
Function<Integer, Integer> function2 = function1.andThen(x -> x - 1);
Integer result = function2.apply(10); // 结果为19 = 10 * 2 - 1
}
identity
:用于创建一个Function
类型的变量(接收参数返回值相同)
@Test
public void test11() {
// 创建一个Function,接收和返回值都是Integer
Function<Integer, Integer> identity = Function.identity();
Integer apply = identity.apply(10); // 接收10,将返回10
}
Comparator
Comparator
接口中方法有很多,我们来看重点的几个方法:
@FunctionalInterface
public interface Comparator<T> {
int compare(T o1, T o2);
default Comparator<T> reversed() {
return Collections.reverseOrder(this);
}
default Comparator<T> thenComparing(Comparator<? super T> other) {
Objects.requireNonNull(other);
return (Comparator<T> & Serializable) (
1, c2) -> {
int res = compare(c1, c2);
return (res != 0) ? res : other.compare(c1, c2);
};
}
public static <T extends Comparable<? super T>> Comparator<T> reverseOrder() {
return Collections.reverseOrder();
}
@SuppressWarnings("unchecked")
public static <T extends Comparable<? super T>> Comparator<T> naturalOrder() {
return (Comparator<T>) Comparators.NaturalOrderComparator.INSTANCE;
}
public static <T> Comparator<T> nullsFirst(Comparator<? super T> comparator) {
return new Comparators.NullComparator<>(true, comparator);
}
public static <T> Comparator<T> nullsLast(Comparator<? super T> comparator) {
return new Comparators.NullComparator<>(false, comparator);
}
public static <T, U extends Comparable<? super U>> Comparator<T> comparing(
Function<? super T, ? extends U> keyExtractor)
{
Objects.requireNonNull(keyExtractor);
return (Comparator<T> & Serializable)
(c1, c2) -> keyExtractor.apply(c1).compareTo(keyExtractor.apply(c2));
}
}
compare
:函数式接口中默认的抽象方法,返回值为int
,返回值可以为负数、0、正数且分别代表第一个参数小于、等于、大于第二个参数
@Test
public void test12() {
Comparator<Integer> integerComparator = (x, y) -> x - y;
int compare = integerComparator.compare(2, 3); // -1
}
reversed
:比较结果取反
@Test
public void test13() {
Comparator<Integer> integerComparator = (x, y) -> x - y;
Comparator<Integer> reversed = integerComparator.reversed();
int compare = reversed.compare(2, 3); // 1
}
thenCompare
:该方法接收一个Compartor
,用于当使用调用thenCompare
的Compartor
来比较两个值相等时,用接收的Compartor
再进行比较。
@Test
public void test14() {
Comparator<Integer> integerComparator = (x, y) -> x - y;
Comparator<Integer> comparator = integerComparator.thenComparing((x, y) -> x + 4 - y);
// 2和2相比 相等 ,所以再次比较 比较的规则是 x + 4 - y
int compare = comparator.compare(2, 2);
}
thenCompare
还有多个重载方法,可以查看源码怎么学习。
reverseOrder
:反转比较顺序
@Test
public void test15() {
Comparator<Integer> reverseOrder = Comparator.reverseOrder();
int compare = reverseOrder.compare(1, 2);
System.out.println(compare);
}
naturalOrder
:正常比较顺序,与reverseOrder
相反
@Test
public void test16() {
Comparator<Character> comparator = Comparator.naturalOrder();
int compare = comparator.compare('s', 'a');
System.out.println(compare);
}
nullsFirst
:null
值比任何值都小
@Test
public void test17() {
/**
* null值小于任何非null值,
* 如果nullsFirst的参数不为null时,如果比较的值都不为null,就使用传入的Comparator来比较
* 如果nullsFirst的参数为null时,如果比较的值都不为null,则认为两个值是相等的
*/
// 参数为null的情况
Comparator<Integer> nullsFirst = Comparator.nullsFirst(null);
int compare = nullsFirst.compare(1, 2); // 相等 为0
compare = nullsFirst.compare(null, 2); // null 小于 2, 为-1
compare = nullsFirst.compare(3, null); // 3 大于 null, 为1
// 参数不为null的情况
Comparator<Integer> nullsFirstNotNull = Comparator.nullsFirst((x, y) -> x * 2 - y);
compare = nullsFirstNotNull.compare(6, 10); // 6 * 2 大于 10, 为2
compare = nullsFirstNotNull.compare(null, 6); // null 小于 6, 为-1
compare = nullsFirstNotNull.compare(6, null); // 6 大于 null, 为1
}
nullsLast
:null
值比任何值都大,与nullsFirst
相反
@Test
public void test18() {
/**
* null值大于任何非null值,
* 如果nullsFirst的参数不为null时,如果比较的值都不为null,就使用传入的Comparator来比较
* 如果nullsFirst的参数为null时,如果比较的值都不为null,则认为两个值是相等的
*/
// 参数为null的情况
Comparator<Integer> nullsFirst = Comparator.nullsLast(null);
int compare = nullsFirst.compare(1, 2); // 相等 为0
compare = nullsFirst.compare(null, 2); // null 大于 2, 为1
compare = nullsFirst.compare(3, null); // 3 小于 null, 为-1
// 参数不为null的情况
Comparator<Integer> nullsFirstNotNull = Comparator.nullsLast((x, y) -> x * 2 - y);
compare = nullsFirstNotNull.compare(6, 10); // 6 * 2 大于 10, 为2
compare = nullsFirstNotNull.compare(null, 6); // null 大于 6, 为 1
compare = nullsFirstNotNull.compare(6, null); // 6 小于 null, 为 -1
}
comparing
:接收Function
参数,要比较的对象会经过Function
返回一个新的对象再比较
@Test
public void test19() {
Comparator<Integer> comparator = Comparator.comparing(x -> {
if (x > 0) {
x -= 2;
} else {
x /= 2;
}
return x;
});
int compare = comparator.compare(1, 0);
}
comparing
也有多个重载方法。
方法引用,可用于对Lambda表达式进一步简化,所以方法引用是用在Lambda表达式中的。方法引用中使用::
符号。
这一章用到的实体类:
public class MethodReferenceBean {
private String flag;
// 无参构造器
public MethodReferenceBean() {
}
// 有参构造器
public MethodReferenceBean(String flag) {
this.flag = flag;
}
// 实例方法
public void print(String word) {
System.out.println(word);
}
// 静态方法
public static void print(String s1, String s2) {
System.out.println("【" + s1 + "】" + s2);
}
}
通过我们要实例化一个对象A,通常是像如下操作new A()
。使用方法引用可以简化为A::new
。
@Test
public void test20() {
// 普通写法
Supplier<MethodReferenceBean> referenceBeanSupplier = () -> new MethodReferenceBean();
// 构造器引用,要注意没有()和->了
Supplier<MethodReferenceBean> methodReferenceBeanSupplier = MethodReferenceBean::new;
}
如果是有参构造器,Lambda中的参数会作为构造器参数传入:
@Test
public void test21() {
// 普通写法
Function<String, MethodReferenceBean> referenceBeanFunction = x -> new MethodReferenceBean(x);
// 构造器引用
Function<String, MethodReferenceBean> referenceBeanFunction1 = MethodReferenceBean::new;
MethodReferenceBean apply = referenceBeanFunction1.apply("constructor ...");
}
通常,我们在调用一个类的静态方法时,是这样的类名::静态方法名
。使用静态方法引用,我们可以这样类名::静态方法名
。同构造器引用一样,如果引用的静态方法是有参数的,Lambda表达式的参数将作为静态方法参数传入。
@Test
public void test22() {
// 普通写法
BiConsumer<String, String> consumer = (x, y) -> MethodReferenceBean.print(x, y);
// 静态方法引用写法
BiConsumer<String, String> consumer1 = MethodReferenceBean::print;
consumer1.accept("hello", "world");
}
通过,我们在调用一个实例方法时,是通过实例对象.实例方法
来调用的。方法引用中可以这样调用实例对象::实例方法
。同样的,如果实例方法是有参数的,Lambda表达式的参数将作为实例方法参数传入。
@Test
public void test23() {
// 实例化
MethodReferenceBean methodReferenceBean = new MethodReferenceBean();
// 普通写法
Consumer<String> consumer = x -> methodReferenceBean.print(x);
// 方法引用
Consumer<String> consumer1 = methodReferenceBean::print;
consumer.accept("hello world!");
}
Optional
Optional
是Java 8中提供的容器类,用于解决NPE
问题。Optional
的定义如下:
public final class Optional<T> {
private static final Optional<?> EMPTY = new Optional<>();
private final T value;
private Optional() {
this.value = null;
}
public static<T> Optional<T> empty() {
@SuppressWarnings("unchecked")
Optional<T> t = (Optional<T>) EMPTY;
return t;
}
private Optional(T value) {
this.value = Objects.requireNonNull(value);
}
public static <T> Optional<T> of(T value) {
return new Optional<>(value);
}
public static <T> Optional<T> ofNullable(T value) {
return value == null ? empty() : of(value);
}
public T get() {
if (value == null) {
throw new NoSuchElementException("No value present");
}
return value;
}
public boolean isPresent() {
return value != null;
}
public void ifPresent(Consumer<? super T> consumer) {
if (value != null)
consumer.accept(value);
}
public Optional<T> filter(Predicate<? super T> predicate) {
Objects.requireNonNull(predicate);
if (!isPresent())
return this;
else
return predicate.test(value) ? this : empty();
}
public<U> Optional<U> map(Function<? super T, ? extends U> mapper) {
Objects.requireNonNull(mapper);
if (!isPresent())
return empty();
else {
return Optional.ofNullable(mapper.apply(value));
}
}
public<U> Optional<U> flatMap(Function<? super T, Optional<U>> mapper) {
Objects.requireNonNull(mapper);
if (!isPresent())
return empty();
else {
return Objects.requireNonNull(mapper.apply(value));
}
}
public T orElse(T other) {
return value != null ? value : other;
}
public T orElseGet(Supplier<? extends T> other) {
return value != null ? value : other.get();
}
public <X extends Throwable> T orElseThrow(Supplier<? extends X> exceptionSupplier) throws X {
if (value != null) {
return value;
} else {
throw exceptionSupplier.get();
}
}
}
通过源码可以看到Optional
中有两个属性,分别是EMPTY
和value
,EMPTY
默认初始化一个value
值为null
的Optional
对象。value
就是Optional
容器存储数据的属性。注意Optional
的构造器是private
,所以不能通过new
创建对象的方式来创建Optional
对象。
对应还基本数据类型的Optional
,分别是OptionalInt
、OptionalDouble
和OptionalLong
。
empty
:获取一个value
为null
的Optional
对象@Test
public void test01() {
Optional<String> empty = Optional.empty();
}
of
:创建一个不能为null
的Optional
对象,该方法接收一个参数作为value
值,该参数不能为null
@Test
public void test02() {
Optional<String> string = Optional.of("Cant not be null");
}
ofNullable
:创建一个可以为null
的Optional
对象,该方法接收一个参数作为value
值,该参数可以为null
@Test
public void test03() {
Optional<String> nullable = Optional.ofNullable("Can be null");
}
如果该方法中传入的是一个null
值,那么得到的Optional
对象将和Optional.empty()
相等。
get
:用于获取Optional
中的value
的值@Test
public void test04() {
Optional<Integer> integer = Optional.of(100);
// 调用get方法获取 value 中保存的值
Integer integer1 = integer.get();
}
注:在获取值之前,一定要判断值是否存在。如果值不存在,调用get
获取值将抛出异常!
isPresent
:判断Optional
中value
属性是否为null
,如果不为null
,则返回true
,反之则返回false
@Test
public void test05() {
Optional<Integer> integer = Optional.of(100);
// 值存在返回true
boolean present = integer.isPresent();
}
ifPresent
:该方法接收一个Comsumer
类型参数,用于判断值是否存在,如果存在就执行Comsumer
操作@Test
public void test06() {
Optional<Integer> integer = Optional.of(100);
// 如果值存在,将值打印出来
integer.ifPresent(System.out::println);
}
filter
:过滤值。该方法接收一个Predicate
参数,如果Optional
中的值满足参数条件,就返回该Optional
,否则返回空的Optional
,如果原来Optional
中值为null
,新生成的Optional
中值也为null
@Test
public void test07() {
Optional<Integer> integer = Optional.of(100);
// 因为100 不大于 200 所以返回的是一个 empty Optional
Optional<Integer> filter = integer.filter(x -> x > 200);
}
map
:操作值返回新值。该方法接收一上Function
类型对象,接收Optional
中的值,生成一个新值包装成Optional
并返回,如果原来Optional
中值为null
,新生成的Optional
中值也为null
@Test
public void test08() {
Optional<Integer> integer = Optional.ofNullable(100);
// 将`Optional`中的值 100 + 20 返回 生成新的`Optional`返回
Optional<Integer> integer1 = integer.map(x -> x + 20);
}
flatMap
:接收一个Function
参数,该Function
参数返回一个Optional
对象@Test
public void test09() {
Optional<Integer> integer = Optional.of(100);
// Optional longOptional 是由flatMap的参数定义生成的
Optional<Long> longOptional = integer.flatMap(x -> Optional.ofNullable(x.longValue()));
}
orElse
:当Optional
中的值为null
时,返回该方法接收的参数@Test
public void test10() {
Optional<Integer> integer = Optional.ofNullable(null);
// 因为 Optional 中的值为 null ,所以会生成一个值为20的新的
Integer integer1 = integer.orElse(20);
}
orElseGet
:该方法接收一个Supplier
参数,用于在Optional
中的值为null
时返回Supplier
生成的值@Test
public void test11() {
Optional<Integer> integer = Optional.ofNullable(null);
Integer integer1 = integer.orElseGet(() -> 20);
}
orElseThrow
:该方法接收一个Supplier
参数,该Supplier
生成一个异常,用于在Optional
中的值为null
时抛出该异常@Test
public void test12() {
Optional<Integer> o = Optional.ofNullable(null);
o.orElseThrow(RuntimeException::new);
}
在Java中,集合是用得最多的数据结构,通常,我们在操作一个结合的时候,是通过迭代遍历进行的,迭代遍历代码要自己实现。Java 8中提供了Stream
为集合操作提供了更便利的操作。
简单来说,Stream
是Java 8中新增用于操作集合的API,使用Stream
API可以使我们对集合的操作只需要关注数据变化 ,而不是迭代过程。例如,我们从Employee
集合中挑选出年龄大于30的员工组成一个新的集合,我们需要这样做:
@Test
public void test01() {
List<Employee> employees = DataUtils.getEmployees();
List<Employee> result = employees.stream().filter(x -> x.getAge() > 30).collect(Collectors.toList());
}
没有使用for
循环,不再关注迭代过程,这个需求中我们只需要关注的是筛选条件年龄大于30,filter(x -> x.getAge() > 30)
和结果组成新的集合,collect(Collectors.toList())
。
流的迭代操作是在方法实现中进行的,不需要我们进行显示迭代操作。所以在使用流操作集合数据时,我们只需要关注数据本身,而不是迭代操作。
获取流后,对该流的操作是一次性的,也就是该流只能被操作一次,如果已经被操作的流再次操作,将抛出异常:
@Test
public void test02() {
List<Employee> employees = DataUtils.getEmployees();
// 获取流
Stream<Employee> stream = employees.stream();
// 流被操作了一次
Set<Employee> collect = stream.collect(Collectors.toSet());
// 再次操作流将抛出异常 java.lang.IllegalStateException: stream has already been operated upon or closed
TreeSet<Employee> collect1 = stream.collect(Collectors.toCollection(TreeSet::new));
}
该实现机制涉及两个概念,分别是惰性求值和及早求值。
惰性求值在执行代码时,实际上操作并没有执行,这名话不太容易理解,我们通过下面这个例子来说明:
@Test
public void test03() {
List<Employee> employees = DataUtils.getEmployees();
Stream<Employee> employeeStream = employees.stream().filter(x -> {
System.out.println(x.getAge());
return x.getAge() > 30;
});
}
执行上面的代码,我们会发现System.out.println(x.getAge());
并没有执行,所以我们称filter
是惰性求值操作,惰性求值期间不会执行相应代码操作。最终结果返回Stream
流。
及早求值在执行时最终会生成相应结果,执行惰性求值操作,而不是返回Stream
,看下面的例子:
@Test
public void test04() {
List<Employee> employees = DataUtils.getEmployees();
long employeeStream = employees.stream().filter(x -> {
System.out.println(x.getAge());
return x.getAge() > 30;
}).count();
}
执行上面的代码,我们会发现System.out.println(x.getAge());
已经执行,所以我们称count
是及早求值操作,及早求值期会执行所以惰性求值,并得到最终结果。
根据及早求值和惰性求值,流的操作也被分为中间操作和终端操作。
of
方法定义:
// 将传入的值生成对应类型的流
public static<T> Stream<T> of(T... values)
// 将单个对象生成流
public static<T> Stream<T> of(T t)
使用:
Stream<Integer> of1 = Stream.of(1, 2, 3, 4, 5);
List<Integer> integers = Arrays.asList(1, 2, 3, 4, 5);
Stream<List<Integer>> of2 = Stream.of(integers);
iterate
通过迭代生成对应的流,方法签名如下,其中seed
为初始值,f
为迭代过程:
public static<T> Stream<T> iterate(final T seed, final UnaryOperator<T> f)
使用:
// 则将生成从1开始,依次加2的值。
Stream<Integer> i = Stream.iterate(0, x -> x + 2);
注意,这将生成无限流,通过该方法配合limit
使用。
Arrays.stream()
由数组生成流:
int[] numbers = {1, 2, 3, 4, 5};
IntStream stream = Arrays.stream(numbers);
generate
定义:
public static<T> Stream<T> generate(Supplier<T> s) {
Objects.requireNonNull(s);
return StreamSupport.stream(
new StreamSpliterators.InfiniteSupplyingSpliterator.OfRef<>(Long.MAX_VALUE, s), false);
}
使用:
// 接收一个Supplier生成值
Stream<Double> generate = Stream.generate(Math::random);
注意,该方式生成的流也是无限流。
中间操作将定义处理流的过程,到终端操作时,中间操作将执行并得到最终结果。要注意,中间操作返回的是一个新的流。
filter
:接收一个Predicate
参数,根据Predicate
中定义的判断条件筛选流中元素定义:
Stream<T> filter(Predicate<? super T> predicate);
用法:
@Test
public void test05() {
// filter 接收一个 Predicate ,所以这里是根据 集合中员工的年龄是否大于30来筛选的
List<Employee> result = employees.stream().filter(x -> x.getAge() > 30).collect(Collectors.toList());
}
distinct
:筛选出流中重复的元素并剔除,使集合中的元素不重复(去重)定义:
Stream<T> distinct();
使用:
@Test
public void test06() {
// 去除集合中重复的元素
List<Employee> collect = employees.stream().distinct().collect(Collectors.toList());
}
limit
:接收一个long
类型数值,返回不超过该指定数值长度的流定义:
Stream<T> limit(long maxSize);
使用:
@Test
public void test07() {
// 返回流中前3个元素,如果流中元素不中3个返回所有元素
List<Employee> collect = employees.stream().limit(3).collect(Collectors.toList());
}
skip
:接收一个long
类型的数值,返回除指定数值长度元素以外的流,和limit
相反定义:
Stream<T> skip(long n);
使用:
@Test
public void test08() {
// 跳过前3个元素,返回除这三个元素以的所有元素组成的流
List<Employee> collect = employees.stream().skip(3).collect(Collectors.toList());
}
map
:将流中的元素映射成一个新的值定义:
<R> Stream<R> map(Function<? super T, ? extends R> mapper);
使用:
// 将集合中员工的名字组成一个新流
Stream<String> stringStream = employees.stream().map(Employee::getName);
flatMap
:接收一个Function
,该Function
将生成一个新的Stream
返回定义:
<R> Stream<R> flatMap(Function<? super T, ? extends Stream<? extends R>> mapper);
使用:
@Test
public void test10() {
// 准备数据
List<List<Employee>> emp = new ArrayList<>();
emp.add(employees.subList(1, 3));
emp.add(employees.subList(3, 6));
emp.add(employees.subList(6, 9));
emp.add(employees.subList(9, 12));
// 将流中每个元素都生成一个新流,再将新流中的元素截取1个后,将这些流和并成一个
List<Employee> employee = emp.stream().flatMap(x -> x.stream().limit(1)).collect(Collectors.toList());
}
这个方法可能对于刚接触的人来说有点难理解。
sorted
使用流提供的sorted
方法可以用于对集合中的元素进行排序操作,要注意的是,如果集合中的元素是自定义类,该类要实现Comparable
接口。
完整的Employee
类的定义:
// 实现 Comparable接口
public class Employee implements Comparable{
private Long id;
private String name;
private Integer age;
private Double salary;
public Employee() {
}
public Employee(Long id, String name, Integer age, Double salary) {
this.id = id;
this.name = name;
this.age = age;
this.salary = salary;
}
// getter/setter/toString
// 实现CompareTo方法
@Override
public int compareTo(Object o) {
if (o instanceof Employee) {
Employee e = (Employee) o;
return this.age - e.getAge();
} else {
throw new RuntimeException("类型不正确");
}
}
}
如果实现了Comparable
接口中的compareTo
方法,使用流中的sorted
方法会按照该规则来排序:
List<Employee> collect = emps.stream()
// 按照 Comparable 中的 compareTo 方法排序
.sorted()
.collect(Collectors.toList());
sorted(Comparator super T> comparator)
自定义的类也可以不实现Comparable
接口,可以使用流的sorted(Comparator super T> comparator)
方法,传入一个Comparator
的Lambda表达式来进行排序,例如按照Employee的Age属性排序:
// 写法一:Comparator接口的Lambda表达式
List<Employee> collect = emps.stream()
.sorted((x, y) -> x.getAge() - y.getAge())
.collect(Collectors.toList());
// 写法二:使用Comparator接口中的默认方法
List<Employee> collect = emps.stream()
.sorted(Comparator.comparing(employee -> employee.getAge()))
.collect(Collectors.toList());
// 写法三:使用方法引用
List<Employee> collect = emps.stream()
.sorted(Comparator.comparingInt(Employee::getAge))
.collect(Collectors.toList());
终端操作将生成最终结果。
anyMatch
:判断元素中是否有任意一个满足给定条件定义:
boolean anyMatch(Predicate<? super T> predicate);
使用:
@Test
public void test11() {
// 流中只要有一个元素 x.getAge() > 30 都返回true
boolean b = employees.stream().anyMatch(x -> x.getAge() > 30);
}
allMatch
:判断是否所有元素都满足给定条件定义:
boolean allMatch(Predicate<? super T> predicate);
使用:
@Test
public void test12() {
// 流中所有元素都满足x.getAge() > 30才返回true
boolean b = employees.stream().allMatch(x -> x.getAge() > 30);
}
noneMatch
:判断是否所有元素都不满足给定条件定义:
boolean noneMatch(Predicate<? super T> predicate);
使用:
@Test
public void test13() {
// 流中所有元素都不满足x.getAge() > 30才返回true
boolean b = employees.stream().noneMatch(x -> x.getAge() > 30);
}
findFirst
:查找第一个元素并返回定义:
Optional<T> findAny();
使用:
@Test
public void test14() {
// 返回 employees 中的第一个元素 如果employee为空,返回的 Optional 就是empty
Optional<Employee> first = employees.stream().findFirst();
}
findAny
:查找返回任意一个元素定义:
Optional<T> findAny();
使用:
@Test
public void test15() {
// 返回 empoyees 中的任一一元素 如果 employees 为空,返回的Optional就是empty
Optional<Employee> any = employees.stream().findAny();
}
reduce
:将流中的元素归约成一个值定义:
// 按照 accumulator 给定的方式进行计算
Optional<T> reduce(BinaryOperator<T> accumulator);
// identity 作为初始值,然后按照 accumulator 给你写的方式参与运算
T reduce(T identity, BinaryOperator<T> accumulator);
使用:
@Test
public void test16() {
// reduce(BinaryOperator accumulator); 映射出员工的age,在reduce中作 求和 操作
Optional<Integer> reduce = employees.stream().map(Employee::getAge).reduce(Integer::sum);
// 与上面一个例子相同,只是加了个基数10
Integer reduce1 = employees.stream().map(Employee::getAge).reduce(10, Integer::sum);
}
max
:最大值定义:
Optional<T> max(Comparator<? super T> comparator);
使用:
@Test
public void test17() {
Optional<Employee> max = employees.stream().max(Comparator.comparingDouble(Employee::getSalary));
}
min
:最小值定义:
Optional<T> min(Comparator<? super T> comparator);
使用:
@Test
public void test18() {
Optional<Employee> min = employees.stream().min((x, y) -> Double.compare(x.getSalary(), y.getSalary()));
}
count
:个数定义:
long count();
使用:
@Test
public void test19() {
// 计算集合中元素的个数
long count = employees.stream().count();
}
foreach
定义:
void forEach(Consumer<? super T> action);
使用:
@Test
public void test26() {
employees.stream().forEach(System.out::println);
}
collect
收集如前面所讲的.collect(Collectors.toList())
,我们在对流进行一系列操作过后将流收集生成一个结果。下面我们将进一步了解collect
的用法。
List
集合Collectors.toList
List<Employee> collect = emps.stream().filter(e -> e.getAge() > 30)
.collect(Collectors.toList());
Set
集合Collectors.toSet
Set<Employee> collect = emps.stream().filter(e -> e.getAge() > 30)
.collect(Collectors.toSet());
不管是生成List还是Set都是由编译器决定生成哪种类型的集合,我们也可以指定生成哪种类型的集合。
Collectors.toCollection(xxx)
TreeSet<Employee> collect = emps.stream().filter(e -> e.getAge() > 30)
.collect(Collectors.toCollection(TreeSet::new));
ArrayList<Employee> collect = emps.stream().filter(e -> e.getAge() > 30)
.collect(Collectors.toCollection(ArrayList::new));
Map
集合Collectors.toMap()
Map<String, Employee> collect = emps.stream().filter(e -> e.getAge() > 30).collect(Collectors.toMap(Employee::getName, e -> e));
toMap
方法可以生成Map
结果,其中第一个参数为Map
的Key,第二个参数是Map
的Value。
流最终不仅可以生成集合,也能生成对应的值。例如元素个数、最大值、最小值等等。
Collectors.counting()
Long count = emps.stream().filter(e -> e.getAge() > 30)
.collect(Collectors.counting());
该方法统计的是流中的元素的个数
Collectors.averagingDouble()
Double average = emps.stream().filter(e -> e.getAge() > 30)
.collect(Collectors.averagingDouble(Employee::getAge));
该方法是按给定的属性统计,如上使用的是averagingDouble
还有类似的averagingInt
和averagingLong
。
Collectors.summingDouble()
Double sum = emps.stream().filter(e -> e.getAge() > 30)
.collect(Collectors.summingDouble(Employee::getSalary));
该方法是按给定的属性统计,如上使用的是summingDouble
同样的也有summingInt
和summingLong
。
Collectors.maxBy()
Optional<Employee> max = emps.stream().filter(e -> e.getAge() > 30).collect(Collectors.maxBy(Comparator.comparingInt(Employee::getAge)));
该方法是找出给定属性的最大值,需要传入一个比较方式。要注意的是这里生成的是一个Optional
对象,Optional
所接收的是流中元素的类型。
Collectors.minBy()
Optional<Employee> max = emps.stream().filter(e -> e.getAge() > 30).collect(Collectors.minBy(Comparator.comparingInt(Employee::getAge)));
该方法是找出给定属性的最小值,接收一个比较方式。要注意的是这里生成的是一个Optional
对象,Optional
所接收的是流中元素的类型。
DoubleSummaryStatistics summaryStatistics = emps.stream().filter(e -> e.getAge() > 30)
.collect(Collectors.summarizingDouble(Employee::getSalary));
该方法是生成流中给定元素属性的统计信息,其中包含数量、总和、最小值、最大值和平均值,如:
{count=5, sum=44000.000000, min=6000.000000, average=8800.000000, max=13000.000000}
同样的这里使用的是summarizingDouble
,还有对应的summarizingInt
和summarizingLong
。
Collectors.partitioningBy()
collect
方法也能根据某个判断规则来为流中的数据进行分块处理。例如,以员工年龄是否大于30来将将员工分块:
Map<Boolean, List<Employee>> collect = emps.stream().collect(Collectors.partitioningBy(employee -> employee.getAge() > 30));
该结果返回一个Map
,Map
的Key就是判断条件true
或者false
,Value就是根据判断条件分出来的数据。
Collectors.groupingBy()
分组不同于分块中按true
和false
划分,分组可以按任意值来划分。例如,以员工年龄分组,将相同年龄的员工分为同一个组:
Map<Integer, List<Employee>> collect = emps.stream().collect(Collectors.groupingBy(Employee::getAge));
该结果返回一个Map
,Map
的Key保存的以划分组的值,Value保存的是结果。
上面的方法类似于:
Map<Integer, List<Employee>> collect = emps.stream().filter(e -> e.getAge() > 30).collect(Collectors.groupingBy(Employee::getAge, Collectors.toList()));
即groupingBy
方法有多个重载方法,其中一个参数的方法会以传入的参数作为Key,对应的集合作为Value保存在Map中。
给定收集器
有两个参数的方法,第一个参数是Map的Key,第二个参数接收一个收集器,如下:
Map<Integer, Long> collect = emps.stream().filter(e -> e.getAge() > 30).collect(Collectors.groupingBy(Employee::getAge, Collectors.counting()));
多级分组
@Test
public void test29() {
Map<String, Map<String, List<Employee>>> collect = employees.stream().collect(Collectors.groupingBy(e -> e.getAge() > 30 ? "大龄" : "正常",
Collectors.groupingBy(e -> e.getSalary() > 10000 ? "高薪" : "普薪")));
System.out.println(collect);
}
Collectors.joining()
将流中的元素按某种规则生成一个字符串。例如,将员工中年龄大于30的员工的名字生成一个字符串输出,每个名字用“、”隔开,首尾用“【”、“】”包裹。
String collect = emps.stream().filter(e -> e.getAge() > 30).map(Employee::getName).collect(Collectors.joining("、", "【", "】"));
要注意:joining
有三个重载方法,分别是没有参数、一个参数和三个参数的,其中没有参数的元素的生成字符串直接连接;一个参数的元素的生成字符串通过该参数接收的字符中连接;三个参数的,三个参数的作为分别是生成字符串的连接方式、前缀和后缀。
Collectors.mapping()
List<Integer> collect = emps.stream().filter(e -> e.getAge() > 30).collect(Collectors.mapping(Employee::getAge, Collectors.toList()));
该方法能将一个元素映射成另一个元素,如上,将Employee
的所有age属性映射成一个List集合。
Collectors.reducing()
定义:
// 无初始值
public static <T> Collector<T, ?, Optional<T>> reducing(BinaryOperator<T> op) {
class OptionalBox implements Consumer<T> {
T value = null;
boolean present = false;
@Override
public void accept(T t) {
if (present) {
value = op.apply(value, t);
}
else {
value = t;
present = true;
}
}
}
// 给定初始值
public static <T> Collector<T, ?, T> reducing(T identity, BinaryOperator<T> op) {
return new CollectorImpl<>(
boxSupplier(identity),
(a, t) -> { a[0] = op.apply(a[0], t); },
(a, b) -> { a[0] = op.apply(a[0], b[0]); return a; },
a -> a[0],
CH_NOID);
}
// 给定初始值和转换函数
public static <T, U> Collector<T, ?, U> reducing(U identity,
Function<? super T, ? extends U> mapper,
BinaryOperator<U> op) {
return new CollectorImpl<>(
boxSupplier(identity),
(a, t) -> { a[0] = op.apply(a[0], mapper.apply(t)); },
(a, b) -> { a[0] = op.apply(a[0], b[0]); return a; },
a -> a[0], CH_NOID);
}
使用:
@Test
public void test28() {
// 返回工资最多的员工
Optional<Employee> collect = employees.stream().collect(Collectors.reducing((e1, e2) -> e1.getSalary() >= e2.getSalary() ? e1 : e2));
// 初始值
Employee e = new Employee(33L, "Tom", 28, 20000.0);
// 给定初始值初始值要参与后面的运算,返回工资最多的员工
Employee collect1 = employees.stream().collect(Collectors.reducing(e, (x, y) -> x.getSalary() > y.getSalary() ? x : y));
// 给定初始值,转换成 工资 来计算, 将值相加
Double collect2 = employees.stream().collect(Collectors.reducing(0.0, Employee::getSalary, Double::sum));
}
基本类型在内存中有固定字节长度,包装类型是对象,其字节长度要大于基本类型,如果基本类型都采用包装类型
参与运算会造成更大的系统内存开销。
基本类型转换成包装类型(装箱),包装类型转换成基本类型(开箱),也会造成额外的计算开销。
对应的Java 8中对常用的double
、int
和long
有相应的Stream
提供,分别是DoubleStream
、IntStream
和LongStream
。
以IntStream
为例,学习如何使用基本类型对应的Stream
操作。
of(int value)
或of(int ...values)
:IntStream of1 = IntStream.of(3);
IntStream of2 = IntStream.of(3, 2, 4, 8);
该方法是将传入的参数构建成一个对应的流。
range(int startInclusive, int endExclusive)
:// 取值 1-10 不能取到10
IntStream range = IntStream.range(1, 10);
生成指定范围(startInclusive到endExclusive)的值,生成的数不包含第二个参数。
rangeClosed(int startInclusive, int endExclusive)
:// 取值 1-10 可以取到10
IntStream rangeClosed = IntStream.rangeClosed(1, 10);
生成指定范围(startInclusive到endExclusive)的值,生成的数包含第二个参数。
mapToInt
@Test
public void test() {
IntStream intStream = employees.stream().mapToInt(Employee::getAge);
}
将包装类型的流转换成基本类型的流。
summaryStatistics()
:// 获取统计数据
IntSummaryStatistics statistics = of.summaryStatistics();
// 从统计数据中获取流中元素平均值
statistics.getAverage();
// 从统计数据中获取流中元素的个数
statistics.getCount();
// 从统计数据中获取流中元素的最大值
statistics.getMax();
// 从统计数据中获取流中元素的最小值
statistics.getMin();
// 从统计数据中获取流中元素的和
statistics.getSum();
// 添加一个元素,统计数据将重新被计算
statistics.accept(20);
// 联合另一个统计数据,统计数据将被重新计算
statistics.combine(statistics);
Stream
操作List<Integer> integerStream = Arrays.asList(1, 2, 3, 4, 5);
integerStream.stream()
.mapToInt(x -> x)
.min();
mapToXxx
方法可以将原来的包装类的流转换成对应的基本类型的流。例如Stream
流中的元素为Integer
类型的,对应的方法就是IntStream mapToInt(ToIntFunction super T> mapper);
,生成的就是IntStream
。
同理Long
类型对应的就是LongStream mapToLong(ToLongFunction super T> mapper);
,生成的就是LongStream
。
Stream
操作要注意:流只能被终止操作一次。
count()
IntStream intStream = IntStream.range(1, 10);
intStream.count();
获取流中元素个数。
sum()
IntStream intStream = IntStream.range(1, 10);
intStream.sum();
计算流中元素的总和。
average()
IntStream intStream = IntStream.range(1, 10);
intStream.average();
计算流中元素的平均数。
min()
IntStream intStream = IntStream.range(1, 10);
intStream.min();
计算流中元素的最小值。
max
@Test
public void test22() {
IntStream intStream = IntStream.range(1, 10);
intStream.max();
}
计算流中的最大值。
boxed()
IntStream intStream = IntStream.range(1, 10);
Stream<Integer> boxed = intStream.boxed();
转换成对应的包装类型的流。
前面,我们所提到的流都是串行流,所有流操作都是依次执行。现在我们要说的并行流,就是将串行流分成几个部分,每个部分同时并行执行,执行后将结果汇总。
如果我们已经有一个串行流,我们要从这个串行流中获取并行流,我们只需要调用parallel()
方法:
List<Employee> employees = emps.stream().parallel().filter(x -> x.getAge() > 30).collect(Collectors.toList());
如果从一个集合中直接获取并行流,可以调用parallelStream()
方法:
List<Employee> employees = emps.parallelStream().filter(employee -> employee.getAge() > 30).collect(Collectors.toList());
何时使用并行流,何时使用串行流是根据代码执行速度来决定,要注意的是并行流并不一定比串行流快,这取决于代码执行环境和复杂度。
从上示例,我们也可以看出,并行流和串行流的操作无异,只是获取流的方式不同而已。
并行流操作共享变量,会影响计算结果。
@Test
public void test30() {
A a = new A();
LongStream.range(1, 1000000000).parallel().forEach(x -> a.add(x));
// 每次结果都不一样
System.out.println(a.i);
}
class A {
public long i = 0;
public void add(long j) {
i += j;
}
}
TemporalField
和TemporalUnit
Java 8中新的时间日期API中有点个重要的接口,分别是TemporalField
和TemporalUnit
,它们在很多地方都有使用到,这两个接口常用的实现类分别是ChronoField
和ChronoUnit
。
ChronoField
ChronoField
是一个枚举类,其中有许多枚举值,如ChronoField.DAY_OF_MONTH
表示日在月中的第几天。
@Test
public void test42() {
ChronoField dayOfMonth = ChronoField.DAY_OF_MONTH;
// 获取范围值
ValueRange range = dayOfMonth.range();
// 获取基本单位 DAY_OF_MONTH 所以获取的值是 Days
TemporalUnit baseUnit = dayOfMonth.getBaseUnit();
// 获取范围单位 DAY_OF_MONTH 所以获取的值是 Months
TemporalUnit rangeUnit = dayOfMonth.getRangeUnit();
// 是否用于时间
boolean timeBased = dayOfMonth.isTimeBased();
// 是否能用于日期
boolean dateBased = dayOfMonth.isDateBased();
}
ChronoUnit
ChronoUnit
是一个枚举类,其中有许多枚举值,如ChronoUnit.DAYS
表示天。
@Test
public void test43() {
ChronoUnit days = ChronoUnit.DAYS;
// 是否基于日期
boolean dateBased = days.isDateBased();
// 是否基于时间
boolean timeBased = days.isTimeBased();
// 获取持续时间
Duration duration = days.getDuration();
}
Java 8提供了新是时间日期API,其中java.time.LocalDate
用于操作日期,java.time.LocalTime
用于操作时间,java.time.LocalDateTime
用于操作日期时间。
虽然日期、时间或日期时间是不同的类,但是他们的创建及操作方法是类似的。这三个类中,多个方法使用到了TemporalField
和TemporalUnit
,并且也提供了isSupported
方法来判断是否对TemporalField
和TemporalUnit
支持。
boolean supported = now.isSupported(ChronoField.NANO_OF_DAY);
boolean supported1 = now.isSupported(ChronoUnit.DAYS);
三者都提供了相同的创建方式,其中of
方法接收指定的值用于创建,parse
用于指定格式类型的值创建,now
用于创建当前的结果。
@Test
public void test01() {
// 创建日期,指定年、月、日
LocalDate date1 = LocalDate.of(2020, 1, 10);
LocalDate date2 = LocalDate.of(2020, Month.JANUARY, 30);
// 创建日期,指定格式日期
LocalDate parse = LocalDate.parse("2020-10-20"); // 默认格式
LocalDate parse1 = LocalDate.parse("2020/10/20", DateTimeFormatter.ofPattern("yyyy/MM/dd")); // 指定格式
// 获取当前日期
LocalDate now = LocalDate.now(); // 系统默认时区
LocalDate.now(ZoneId.of("Asia/Shanghai")); // 指定时区
LocalDate.now(Clock.systemDefaultZone()); // 系统默认时区
// 指定年 和 年中的第几天
LocalDate date = LocalDate.ofYearDay(2020, 1);
}
@Test
public void test02() {
// 创建时间,指定时、分、秒、纳秒 用于创建,of方法有多个重载
LocalTime of = LocalTime.of(0, 1, 2, 3);
// 创建时间,指定格式时间
LocalTime p = LocalTime.parse("20:20:20"); // 默认格式
LocalTime p1 = LocalTime.parse("10:20", DateTimeFormatter.ofPattern("HH:mm"));// 指定格式
// 获取当前时间
LocalTime now = LocalTime.now(); // 系统默认时区
LocalTime.now(ZoneId.of("Asia/Shanghai")); // 指定时区
LocalTime.now(Clock.systemDefaultZone()); // 系统默认时区
// 一天中的时间戳获取
LocalTime localTime = LocalTime.ofSecondOfDay(1 * 24 * 60 * 60 - 1); // 当天第多少秒
LocalTime localTime1 = LocalTime.ofNanoOfDay(1L * 60 * 60 * 1000_000_000); // 当天第多少毫秒
}
@Test
public void test03() {
LocalDate nowDate = LocalDate.now();
LocalTime nowTime = LocalTime.now();
// of方法有多个构造器
LocalDateTime of = LocalDateTime.of(nowDate, nowTime); // LocalDate LocalTime
LocalDateTime.of(2010, 10, 20, 17, 50); // 年 月 日 时 分,
// 按格式创建
LocalDateTime parse = LocalDateTime.parse("2020-01-10T18:01:50.722"); // 默认格式
LocalDateTime parse1 = LocalDateTime.parse("2020-01-20 20:20:12", DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")); // 自定义格式
// 获取当前
LocalDateTime now = LocalDateTime.now(); // 系统默认时区
LocalDateTime.now(ZoneId.of("Asia/Shanghai")); // 指定时区
LocalDateTime.now(Clock.systemDefaultZone()); // 系统默认时区
// 时间戳秒 、毫秒(不是时间戳) 、 时区
LocalDateTime localDateTime = LocalDateTime.ofEpochSecond(1578972303, 621000000, ZoneOffset.ofHours(8));
}
@Test
public void test01() {
LocalDate localDate = LocalDate.now();
// 获取日历系统 默认ISO
IsoChronology chronology = localDate.getChronology();
// 纪年系统 默认 CE(公元纪年)
Era era = localDate.getEra();
}
@Test
public void test02() {
LocalDate localDate = LocalDate.now();
// 获取年份
int year = localDate.getYear();
// 获取月份 Month对象
Month month = localDate.getMonth();
// 获取月分 int值
int monthValue = localDate.getMonthValue();
// 获取日期中的天在一年中的第几天
int dayOfYear = localDate.getDayOfYear();
// 获取日期中的天在当月的第几天
int dayOfMonth = localDate.getDayOfMonth();
// 获取日期中的天在所在星期的第几天
DayOfWeek dayOfWeek = localDate.getDayOfWeek();
// 指定单位获取
int i = localDate.get(ChronoField.DAY_OF_MONTH);
// 指定单位获取,得到long值
long aLong = localDate.getLong(ChronoField.DAY_OF_WEEK);
}
Test
public void test03 () {
LocalDate now = LocalDate.now();
// 时间戳 单位 天
long l = now.toEpochDay();
)
@Test
public void test44() {
LocalDate now = LocalDate.now();
// 日期所在 月 中 天的范围 如 1-31
ValueRange range = now.range(ChronoField.DAY_OF_MONTH);
// 日期所在年 天数 366
int i = now.lengthOfYear();
// 日期所在月 天数 31
int i1 = now.lengthOfMonth();
}
@Test
public void test45() {
LocalTime now = LocalTime.now();
// 获取小时
int hour = now.getHour();
// 获取分钟
int minute = now.getMinute();
// 获取秒钟
int second = now.getSecond();
// 获取纳秒
int nano = now.getNano();
// 指定单位获取
int i = now.get(ChronoField.MINUTE_OF_HOUR);
// 指定单位获取,得到long值
long aLong = now.getLong(ChronoField.SECOND_OF_MINUTE);
}
@Test
public void test46() {
LocalTime now = LocalTime.now();
// 当天时间戳 纳秒
long l = now.toNanoOfDay();
// 当天时间戳 秒
int i = now.toSecondOfDay();
}
@Test
public void test47() {
LocalTime now = LocalTime.now();
// 获取一天中 小时的范围 0 - 23
ValueRange range = now.range(ChronoField.HOUR_OF_DAY);
}
日期时间综合和了日期和时间的所有方法,即可以用于获取时间相关数据,也可以获取日期相关数据,例如:
@Test
public void test48() {
LocalDateTime now = LocalDateTime.now();
// 日期相关 日在年中的第几天
int dayOfYear = now.getDayOfYear();
// 获取 小时值
int hour = now.getHour();
}
DateTimeFormatter
构建DateTimeFormatter
对象:
@Test
public void test49() {
// 指定格式
DateTimeFormatter formatter1 = DateTimeFormatter.ofPattern("yyyy-MM-dd");
// 指定格式,指定地区
DateTimeFormatter formatter2 = DateTimeFormatter.ofPattern("yyyy-MM-dd", Locale.CHINA);
// 用于LocalDate 格式化 FormatStyle枚举类,定义多种格式 SHORT 表现为20-1-14 即 2020-01-14
DateTimeFormatter formatter3 = DateTimeFormatter.ofLocalizedDate(FormatStyle.SHORT);
// 用于LocalTime 格式化 FormatStyle枚举类,定义多种格式 SHORT 表现为 下午3:06 即 15:06
DateTimeFormatter formatter4 = DateTimeFormatter.ofLocalizedTime(FormatStyle.SHORT);
// 用于LocalDateTime 格式化 FormatStyle枚举类,定义多种格式 SHORT 表现为 20-1-14 下午3:08 即 2020-01-14 15:06
DateTimeFormatter formatter5 = DateTimeFormatter.ofLocalizedDateTime(FormatStyle.SHORT);
}
@Test
public void test() {
DateTimeFormatter dateFormatter = DateTimeFormatter.ofPattern("yyyy/MM/dd");
DateTimeFormatter timeFormatter = DateTimeFormatter.ofPattern("HH:mm:ss");
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy/MM/dd HH:mm:ss");
// 日期格式化
String format = LocalDate.now().format(dateFormatter);
// 时间格式化
String format1 = LocalTime.now().format(timeFormatter);
// 日期时间格式化
String format2 = LocalDateTime.now().format(formatter);
// 格式化生成日期或时间
LocalDate parse = LocalDate.parse("2020/01/20", dateFormatter);
LocalTime parse1 = LocalTime.parse("20:20:10", timeFormatter);
LocalDateTime parse2 = LocalDateTime.parse("2020/01/20 20:10:02", formatter);
}
日期或时间API中提供了withXxx
方法用于修改对应的类型的值。
@Test
public void test51() {
LocalDate now = LocalDate.now();
// 修改Month为2月
LocalDate date = now.withMonth(2);
// 修改Year
LocalDate date1 = now.withYear(2018);
// 修改 天在月中为20号
LocalDate date2 = now.withDayOfMonth(20);
// 修改天在年中为200天
LocalDate date3 = now.withDayOfYear(200);
// 修改为3月,指定单位修改
LocalDate with = now.with(ChronoField.MONTH_OF_YEAR, 3);
}
@Test
public void test52() {
LocalTime now = LocalTime.now();
// 纳秒改为1
LocalTime localTime = now.withNano(1);
// 秒改为30
LocalTime localTime1 = now.withSecond(30);
// 分钟改为20
LocalTime localTime2 = now.withMinute(20);
// 小时改为12
LocalTime localTime3 = now.withHour(12);
// 小时改为20, 指定单位修改
LocalTime with = now.with(ChronoField.HOUR_OF_DAY, 20);
}
LocalDateTim
中的withXxx
方法是LocaDate
和LocalTime
的综合。
@Test
public void test53() {
LocalDate now = LocalDate.now();
// 加20天
LocalDate date = now.plusDays(20);
// 加2周
LocalDate date1 = now.plusWeeks(2);
// 加2个月
LocalDate date2 = now.plusMonths(2);
// 加1年
LocalDate date3 = now.plusYears(1);
// 加10天,自定义单位
LocalDate plus = now.plus(10, ChronoUnit.DAYS);
}
@Test
public void test54() {
LocalDate now = LocalDate.now();
// 减1天
LocalDate date = now.minusDays(1);
// 减2个星期
LocalDate date1 = now.minusWeeks(2);
// 减3个月
LocalDate date2 = now.minusMonths(3);
// 减1年
LocalDate date3 = now.minusYears(1);
// 减2天,自定义单位
LocalDate minus = now.minus(2, ChronoUnit.DAYS);
}
@Test
public void test55() {
LocalTime now = LocalTime.now();
// 加2小时
LocalTime localTime = now.plusHours(2);
// 加20秒
LocalTime localTime1 = now.plusSeconds(20);
// 加10分
LocalTime localTime2 = now.plusMinutes(10);
// 加2000纳秒
LocalTime localTime3 = now.plusNanos(2000);
// 加20秒,自定义单位
LocalTime plus = now.plus(20, ChronoUnit.SECONDS);
}
@Test
public void test56() {
LocalTime now = LocalTime.now();
// 减一小时
LocalTime localTime = now.minusHours(1);
// 减30分钟
LocalTime localTime1 = now.minusMinutes(30);
// 减50秒
LocalTime localTime2 = now.minusSeconds(50);
// 减10000纳秒
LocalTime localTime3 = now.minusNanos(10000);
// 减20秒
LocalTime minus = now.minus(20, ChronoUnit.SECONDS);
}
LocalDateTime
中包含了所有LocalDate
和LocalTime
中的plusXxx
和miusXxx
方法,如:
@Test
public void test57() {
LocalDateTime now = LocalDateTime.now();
// 加20天
LocalDateTime localDateTime = now.plusDays(20);
// 减20小时
LocalDateTime localDateTime1 = now.minusHours(20);
}
@Test
public void test() {
LocalDate date1 = LocalDate.now();
LocalDate date2 = LocalDate.parse("2020-01-20");
// date1是否在date2之前
boolean before = date1.isBefore(date2);
// date1是否在date2之后
boolean after = date1.isAfter(date2);
// date1是否和date2相等
boolean equal = date1.isEqual(date2);
}
@Test
public void test59() {
LocalTime time1 = LocalTime.now();
LocalTime time2 = LocalTime.parse("20:20:20");
// time1是否在time2之前
boolean before = time1.isBefore(time2);
// time1是否在time2之前
boolean after = time1.isAfter(time2);
}
@Test
public void test60() {
LocalDateTime dateTime1 = LocalDateTime.now();
LocalDateTime dateTime2 = LocalDateTime.parse("2020-01-20 20:30:40");
// dateTime1 是否和 dateTime2 相等
boolean equal = dateTime1.isEqual(dateTime2);
// dateTime1 是否在 dateTime2 之前
boolean before = dateTime1.isBefore(dateTime2);
// dateTime1 是否在 dateTime2 之后
boolean after = dateTime1.isAfter(dateTime2);
}
@Test
public void test61() {
LocalDate localDate = LocalDate.now();
LocalTime localTime = LocalTime.now();
// 用 LocalDate 和 LocalTime 生成 LocalDateTime
LocalDateTime localDateTime = LocalDateTime.of(localDate, localTime);
// LocalDateTime 转换成 LocalDate
LocalDate date = localDateTime.toLocalDate();
// LocalDateTime 转换成 LocalTime
LocalTime localTime1 = localDateTime.toLocalTime();
// LocalDate 加上 LocalTime 得到 LocalDateTime 时、分、秒、纳秒
LocalDateTime dateTime = localDate.atTime(2, 30, 20, 1000);
LocalDateTime dateTime1 = localDate.atTime(localTime);
// 获取一天开始的时间 ,LocalDate 转成 LocalDateTime
LocalDateTime dateTime2 = localDate.atStartOfDay();
// LocalTime 加上 LocalDate 得到 LocalDateTime
LocalDateTime dateTime3 = localTime.atDate(localDate);
}
Instant
@Test
public void test24() {
// 当前时间
Instant now = Instant.now();
// 默认格式解析生成
Instant parse = Instant.parse("2020-01-11T02:02:53.241Z");
// 时间戳 毫秒生成
Instant instant = Instant.ofEpochMilli(90 * 60 * 1000);
// 时间戳 毫秒 + 纳秒
Instant instant1 = Instant.ofEpochSecond(60 * 60, 30L * 60 * 1000_000_000);
// Unix 元年时间 1970-01-01 00:00:00
Instant instant2 = Instant.EPOCH;
}
@Test
public void test25() {
Instant now = Instant.now();
// 获取时间戳 秒 long
long aLong = now.getLong(ChronoField.INSTANT_SECONDS);
System.out.println(aLong);
// 获取 当前时间中的纳秒
int nano = now.getNano();
// 获取时间戳 秒
long epochSecond = now.getEpochSecond();
// 获取毫秒时间戳
long l = now.toEpochMilli();
}
@Test
public void test26() {
Instant instant1 = Instant.now();
Instant instant2 = Instant.parse("2020-01-11T02:02:53.241Z");
// instant1 是否在 instant2 之后
boolean after = instant1.isAfter(instant2);
// instant1 是否在 instant2 之前
boolean before = instant1.isBefore(instant2);
}
@Test
public void test27() {
Instant instant1 = Instant.now();
// 减去 20分钟
Instant minus = instant1.minus(Duration.ofMinutes(20));
// 减去2000毫秒 第一个参数 数值 第二个参数 单位
Instant minus1 = instant1.minus(2000, ChronoUnit.MILLIS);
// 减去纳秒数
Instant instant = instant1.minusNanos(60 * 60 * 1000_000_000);
// 减去秒数
Instant instant2 = instant1.minusSeconds(60 * 60);
// 减去毫秒数
Instant instant3 = instant1.minusMillis(60 * 60 * 1000);
}
@Test
public void test28() {
Instant now = Instant.now();
// 加 1 小时
Instant plus = now.plus(Duration.ofHours(1));
// 加 1 分钟 数值 单位
Instant plus1 = now.plus(1, ChronoUnit.MINUTES);
// 加纳秒
Instant instant = now.plusNanos(60 * 60 * 1000_000_000);
// 加 秒
Instant instant1 = now.plusSeconds(60 * 60);
// 加毫秒
Instant instant2 = now.plusMillis(60 * 60 * 1000);
}
@Test
public void test29() {
Instant now = Instant.now();
// 机器时间 就是时间戳 秒 所以修改秒的话 就相当于改成时间戳为1000 结果为1970-01-01 00:16:40.464Z、
Instant with = now.with(ChronoField.INSTANT_SECONDS, 1000);
}
Duration
Duration
可以用于获取两个相同类型时间或日期的时间段,或指定特定类型的时间段。如果第一个时间比第二个时间大,获取的值为负数。
@Test
public void test30() {
Instant now = Instant.now();
Instant parse = Instant.parse("2020-01-11T02:02:53.241Z");
Duration between = Duration.between(now, parse);
Duration duration1 = Duration.ofDays(1);
Duration duration = Duration.ofHours(1);
Duration duration2 = Duration.ofMinutes(1);
Duration duration3 = Duration.ofSeconds(1);
Duration duration4 = Duration.ofSeconds(1, 1000_000_000);
Duration duration5 = Duration.ofMillis(10000);
Duration duration6 = Duration.ofNanos(10000000);
// 最大指定为 ChronoUnit.DAYS
Duration of = Duration.of(100, ChronoUnit.DAYS);
}
@Test
public void test31() {
Duration duration = Duration.ofDays(1);
// 获取绝对值,
Duration abs = duration.abs();
// 获取 秒 数
long l = duration.get(ChronoUnit.SECONDS);
// 获取毫秒数
int nano = duration.getNano();
// 获取 秒 数
long seconds = duration.getSeconds();
// 获取支持的 TemporalUnit值
List<TemporalUnit> units = duration.getUnits();
}
@Test
public void test32() {
Duration duration = Duration.ofHours(25);
// 转换成天数,不满一天舍去
long l = duration.toDays();
// 转换成小时数
long l1 = duration.toHours();7.
long l4 = duration.toMinutes();
long l2 = duration.toMillis();
long l3 = duration.toNanos();
}
Duration
同样支持计算和修改值,同时还增加了乘除操作:
@Test
public void test() {
Duration duration = Duration.ofHours(10);
Duration duration1 = duration.dividedBy(10);
Duration duration2 = duration.multipliedBy(10);
}
// 减去 20分钟
Instant minus = instant1.minus(Duration.ofMinutes(20));
Period
Period
同Duration
一样,但Period
主要是对LocalDate
的操作。
TemporalAdjuster
TemporalAdjuster
在时间日期中也有涉及,TemporalAdjusters
中封装了许多静态方法快速生成TemporalAdjuster
对象。
如:
@Test
public void test34() {
LocalDate now = LocalDate.now();
// 时间跳转到所在月第一天
LocalDate with = now.with(TemporalAdjusters.firstDayOfMonth());
}
关于TemporalAdjuster
更多用法,请自行参考TemporalAdjusters
中的方法。