Java 8新特性-教程

Java 8新特性-教程

Java8新特性

Java 14都来了,你还不了解Java 8吗?赶紧的,上车!
视频教程正在录制中…

文档中所有的基础数据。

对象数据结构:

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;
	}
}

1. 引入

1.1 常规求值

  • 从上面的数据中找出年龄大于30的员工
	/**
	 * 找出年龄大于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的员工
	/**
	 * 找出员工式资大于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的员工
	/**
	 * 找出年龄大于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的判断方式。那些相同的代码都是些重复性代码、冗余代码。那么有没有更好的方法来优化它们呢?

1.2 使用接口

根据上面三个需求我们可以抽象出一个接口,接口中提供一个抽象方法,实现类实现其判断方式。

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即可。

  • 从上面的数据中找出年龄大于30的员工

接口实现,实现判断方式:

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);
}
  • 从上面的数据中找出工资大于10000的员工

接口实现,实现判断方式:

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);
}
  • 找出年龄大于30且工资大于10000的员工

接口实现,实现判断方式:

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.3 使用匿名类

根据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;
}

使用匿名类修改需求。

  • 从上面的数据中找出年龄大于30的员工
@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);
}
  • 从上面的数据中找出工资大于10000的员工
@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);
}
  • 找出年龄大于30且工资大于10000的员工
@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表达式的出现,正好解决了这些问题,让代码看上去非常简单且容易理解。

1.4 使用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表达式:

  • 从上面的数据中找出年龄大于30的员工
@Test
public void test10() {
  List<Employee> emps = DataUtils.getEmployees();
  List<Employee> result = queryEmployees(emps, employee -> employee.getAge() > 30);
  System.out.println(result);
}
  • 从上面的数据中找出工资大于10000的员工
@Test
public void test11() {
  List<Employee> emps = DataUtils.getEmployees();
  List<Employee> result = queryEmployees(emps, employee -> employee.getSalary() > 10000);
  System.out.println(result);
}
  • 找出年龄大于30且工资大于10000的员工
@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表达式使代码看上去明显简洁了很多,而且要表达的意图也清晰了很多。

2. Lambda表达式

上一章我们感受了Lambda表达式的魅力,这一节我们将深入学习Lambda。

Lambda表达式,既然叫做表达式,那么它要表达式的是什么呢?实际上Lambda表达的是一个函数,这个函数只有参数列表和函数体,所以Lambda表达式也被叫做匿名函数

2.1 Lambda表达式组成

Lambda由三部分组成,分别是参数列表箭头函数体

  • 参数列表

    1. 和普通函数一样,Lambda也有参数列表,且表达方式相同,如:(ParamType1 param1, ParamType2 param2, ...)
    2. 可以省略参数列表参数类型,如(param1, param2, ...)
    3. 如果参数列表只有一个,且省略了参数类型,可以省略(),如param1
    4. 参数列表没有参数,()不能省略。
  • 箭头

    Lambda表达式的参数列表后紧跟关的是箭头,如->

  • 函数体

    1. 和普通函数一样,Lambda也有函数体,如{ System.out.println("hello world!")}
    2. 如果函数体只有一条语句,可以省略{},如System.out.println("hello world!")}
    3. 如果函数体只有一条语句,且函数有返回值,要省略return关键字。

举例:

  1. 完整参数列表
Comparator<Integer> comparator = (Integer a, Integer b) -> {
  return a - b;
};
  1. 参数列表省略参数类型
Comparator<Integer> comparator = (a, b) ->  {
  return a - b;
};
  1. 参数列表省略()
Predicate<Integer> predicate = a -> {
  return a > 3;
};
  1. 参数列表没有参数
Runnable r = () -> {
			System.out.println("Lambda表达式");
		};
  1. 函数体只有一条语句,可以省略{}
Runnable r = () -> System.out.println("Lambda表达式");
  1. 函数体只有一条语句,且函数有返回值,省略return关键字
Predicate<Integer> predicate = a -> a > 3;

通过上面的例子,不难看出Lambda表达式可以被一个对象接收,所以Lambda表达式也能看做是一个对象,相当于匿名类,而接收Lambda表达式的对象类型也被统称为函数式接口

2.2 函数式接口

通过对2.1中接收Lambda表达式的对象的类型的研究,我们可以发现,这些接口都有一个共同特征,那就是这些接口中只有一个抽象方法。所以对于函数式接口的定义也就是:只有一个抽象方法的接口就是函数式接口。Lambda表达式只能作用在函数式接口上。

@FunctionalInterface注解,一般的函数式接口,都由@FunctionalInterface注解注释,该注释用于表式该接口就是函数式接口,也能用于校验当前接口是否满足函数式接口的定义。要注意,并不是说只有使用了@FunctionalInterface注解的接口才是函数式接口,只要满足函数式接口的定义的接口都是函数式接口。

正如我们在第1章中所定义的EmployeePredicate接口,该接口只有一个抽象方法(并没有@FunctionalInterface注解),它是一个函数式接口,所以最后才能使用Lambda表达式来改进代码。

2.3 如何使用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<Integer> predicate = a -> a > 3;,要如何使用这里不再赘述。

2.4 类型推断

正如我们上面所说的,Lambda表达式可以省略参数列表中的参数类型,那么如果省略了参数类型,Lambda是如何知道参数是什么类型的呢?Lambda表达式中用到的就是类型推断!

Comparator<Integer> comparator = (a, b) -> a - b;

上例中,使用了Comparator接口,下面是Comparator接口的定义,结合上例,Comparator中泛型的类型是TInteger,所以compare的参数类型的T也就Integer

@FunctionalInterface
public interface Comparator<T> {
    int compare(T o1, T o2); 
}

这就是在Lambda中使用到的类型推断。

2.5 引用外部变量

这里我们要明确一个概念外部变量,外部变量指定的是不属性于当前函数或者类的变量,比如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中有引用到外部变量,该外部变量是不允许被改变的。

2.6 默认函数式接口

在JDK中也默认提供了一些常用的函数式接口,这些接口在java.util.function包下,这里列举一些基本的函数式接口:

函数式接口 函数描述符 参数 返回类型 示例(在集合中的应用)
Predicate<T> bolean test(T t); T boolean Stream<T> filter(Predicate<? super T> predicate);
Comsumer<T> void accept(T t); T void void forEach(Consumer<? super T> action);
Function<T, R> R apply(T t); T R <R> Stream<R> map(Function<? super T, ? extends R> mapper);
Supplier<T> T get() T

上述4个函数式接口,是最常用的几个函数式接口。

关于装箱拆箱

Predicate<T>中的T是泛型,泛型不能接收基本类型,但是使用包装类型会增加内存和计算开销,所以对于基本类型Java 8中也提供了对应的函数式接口,基本类型的函数式接口主要有:

函数式接口 函数描述符
IntPredicate boolean test(int value);
IntConsumer void accept(int value);
IntFunction R apply(int value);
IntSupplier int getAsInt();

int类型为例的函数式接口如上,其他类型类同 。要注意,基本类型的函数式接口只针对常用的longintdouble。其他基本类型没有对应的函数式接口。

更多函数式接口:

关于常用函数式接口总结:

函数式接口 函数描述符 扩展
Predicate<T> T -> boolean IntPredicateLongPredicateDoublePredicate
Consumer<T> T -> void IntConsumerLongConsumerDoubleConsumer
Function<T, R> T -> R IntFunction<R>IntToDoubleFunctionIntToLongFunction
LongFunction<R>LongToDoubleFunctionLongToIntFunction
DoubleFunction<R>DoubleToIntFunctionDoubleToLongFunction
ToIntFunction<T>ToDoubleFunction<T>ToLongFunction<T>
Supplier<T> () -> T BooleanSupplierIntSupplierLongSupplier
BinaryOperator<T> (T, T) -> T IntBinaryOperatorLongBinaryOperatorDoubleBinaryOperator
BiPredicate<L, R> (L, R) -> boolean
BiConsumer<T, U> (T, U) -> void ObjIntConsumer<T>ObjLongConsumer<T>ObjDoubleConsumer<T>
BiFunction<T, U, R> (T, U) -> R ToIntBiFunction<T, U>ToLongBiFunction<T, U>ToDoubleBiFunction<T, U>

3. 默认方法和静态方法

3.1 什么是默认方法

在Java 8之前,接口中的所有方法必须是抽象方法。实现该接口的类都必须实现接口中所有抽象方法,除非这个类是抽象类。

Java 8中使用了一个default关键字用于在接口中定义默认方法,该方法有方法体,如果实现该接口的类没有“实现”(重写)该方法,那么实现类将使用默认方法,如果实现类中“实现”(重写)了该方法,实现类中的该方法将覆盖接口中的默认方法。

3.2 为什么要使用默认方法

例如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);
}

3.3 默认方法的优先级

  1. 接口中的默认方法,子类(接口继承、实现)如果重写了,子类的优先级更高。

  2. 实现多接口,接口中有相同的默认方法需要指明使用哪一个接口中定义的默认方法

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();
	}
}

  1. 子类不使用接口中的默认方法需求将该方法覆盖且为抽象方法
// 类中有抽象方法,类必须是抽象的
public abstract class C implements A, B{
	@Override
	public abstract void show();
}

3.4 静态方法

Java 8中,接口也能添加静态方法。

public interface InterfaceStaticMethod {
	static void staticMethod() {
		System.out.println("这是接口中的静态方法");
	}
}

3.5 函数式接口中默认方法和静态方法的应用

  • Preidcate<T>

Predicate<T>的接口定义如下:

@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个默认方法,分别是andnegateor和一个静态方法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<T>

Consumer<T>接口的定义如下,该接口的抽象方法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<T, R>

Function<T, R>接口定义如下:

@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类型的值,其中该接口还提供了composeandThen两个默认方法以及一个identity静态方法。

apply:接收一个对象,返回另一个对象

@Test
public void test08() {
  Function<Integer, Long> integerToLongFunction = i -> i.longValue();
  Long apply = integerToLongFunction.apply(10);
}

compose:接收一个Function,将接收的Function返回的值作为参数传递给当前调用composeFunction

@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<T, T>类型的变量(接收参数返回值相同)

@Test
public void test11() {
  // 创建一个Function,接收和返回值都是Integer
  Function<Integer, Integer> identity = Function.identity();
  Integer apply = identity.apply(10); // 接收10,将返回10
}
  • Comparator<T>

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,用于当使用调用thenCompareCompartor来比较两个值相等时,用接收的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);
}

nullsFirstnull值比任何值都小

@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
}

nullsLastnull值比任何值都大,与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也有多个重载方法。

4. 方法引用

方法引用,可用于对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);
	}

}

4.1 构造器引用

通过我们要实例化一个对象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 ...");
}

4.2 静态方法引用

通常,我们在调用一个类的静态方法时,是这样的类名::静态方法名。使用静态方法引用,我们可以这样类名::静态方法名。同构造器引用一样,如果引用的静态方法是有参数的,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");
}

4.3 实例方法引用

通过,我们在调用一个实例方法时,是通过实例对象.实例方法来调用的。方法引用中可以这样调用实例对象::实例方法。同样的,如果实例方法是有参数的,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!");
}

5. Optional

5.1 什么是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中有两个属性,分别是EMPTYvalueEMPTY默认初始化一个value值为nullOptional对象。value就是Optional容器存储数据的属性。注意Optional的构造器是private,所以不能通过new创建对象的方式来创建Optional对象。

对应还基本数据类型的Optional,分别是OptionalIntOptionalDoubleOptionalLong

5.2 创建

  • empty:获取一个valuenullOptional对象
@Test
public void test01() {
  Optional<String> empty = Optional.empty();
}
  • of:创建一个不能为nullOptional对象,该方法接收一个参数作为value值,该参数不能为null
@Test
public void test02() {
  Optional<String> string = Optional.of("Cant not be null");
}
  • ofNullable:创建一个可以为nullOptional对象,该方法接收一个参数作为value值,该参数可以为null
@Test
public void test03() {
  Optional<String> nullable = Optional.ofNullable("Can be null");
}

如果该方法中传入的是一个null值,那么得到的Optional对象将和Optional.empty()相等。

5.2 获取值

  • get:用于获取Optional中的value的值
@Test
public void test04() {
  Optional<Integer> integer = Optional.of(100);
  // 调用get方法获取 value 中保存的值
  Integer integer1 = integer.get();
}

注:在获取值之前,一定要判断值是否存在。如果值不存在,调用get获取值将抛出异常!

5.3 判断空值

  • isPresent:判断Optionalvalue属性是否为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);
}

5.4 操作值

  • 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<Long> longOptional 是由flatMap的参数定义生成的
  Optional<Long> longOptional = integer.flatMap(x -> Optional.ofNullable(x.longValue()));
}

5.4 操作空值

  • 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);
}

6. Stream

在Java中,集合是用得最多的数据结构,通常,我们在操作一个结合的时候,是通过迭代遍历进行的,迭代遍历代码要自己实现。Java 8中提供了Stream为集合操作提供了更便利的操作。

6.1 什么是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循环,不再关注迭代过程,这个需求中我们只需要关注的是筛选条件年龄大于30filter(x -> x.getAge() > 30)和结果组成新的集合collect(Collectors.toList())

6.2 内部迭代

流的迭代操作是在方法实现中进行的,不需要我们进行显示迭代操作。所以在使用流操作集合数据时,我们只需要关注数据本身,而不是迭代操作。

6.3 一次消费

获取流后,对该流的操作是一次性的,也就是该流只能被操作一次,如果已经被操作的流再次操作,将抛出异常:

@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));
}

6.4 实现机制

该实现机制涉及两个概念,分别是惰性求值及早求值

惰性求值在执行代码时,实际上操作并没有执行,这名话不太容易理解,我们通过下面这个例子来说明:

@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是及早求值操作,及早求值期会执行所以惰性求值,并得到最终结果。

根据及早求值惰性求值,流的操作也被分为中间操作终端操作

6.5 获取流

  • 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);

注意,该方式生成的流也是无限流。

6.6 中间操作

中间操作将定义处理流的过程,到终端操作时,中间操作将执行并得到最终结果。要注意,中间操作返回的是一个新的流

6.6.1 筛选

  • 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());
}

6.6.2 切片

  • 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());
}

6.6.3 映射

  • 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());
}

这个方法可能对于刚接触的人来说有点难理解。

6.6.4 排序

  • 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());
    

6.7 终端操作

终端操作将生成最终结果。

6.7.1 匹配

  • 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);
}

6.7.2 查找

  • 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();
}

6.7.3 归约

  • reduce:将流中的元素归约成一个值

定义:

// 按照 accumulator 给定的方式进行计算
Optional<T> reduce(BinaryOperator<T> accumulator);
// identity 作为初始值,然后按照 accumulator 给你写的方式参与运算
T reduce(T identity, BinaryOperator<T> accumulator);

使用:

@Test
public void test16() {
  // reduce(BinaryOperator<T> 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();
}

6.7.4 遍历

  • foreach

定义:

void forEach(Consumer<? super T> action);

使用:

@Test
public void test26() {
  employees.stream().forEach(System.out::println);
}

6.8 collect收集

6.8.1 生成集合

如前面所讲的.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。

6.8.2 生成值

流最终不仅可以生成集合,也能生成对应的值。例如元素个数、最大值、最小值等等。

  • 计算元素个数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还有类似的averagingIntaveragingLong

  • 计算总合Collectors.summingDouble()
Double sum = emps.stream().filter(e -> e.getAge() > 30)
				.collect(Collectors.summingDouble(Employee::getSalary));

该方法是按给定的属性统计,如上使用的是summingDouble同样的也有summingIntsummingLong

  • 计算最大值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,还有对应的summarizingIntsummarizingLong

6.8.3 分块

  • Collectors.partitioningBy()

collect方法也能根据某个判断规则来为流中的数据进行分块处理。例如,以员工年龄是否大于30来将将员工分块:

Map<Boolean, List<Employee>> collect = emps.stream().collect(Collectors.partitioningBy(employee -> employee.getAge() > 30));

该结果返回一个MapMap的Key就是判断条件true或者false,Value就是根据判断条件分出来的数据。

6.8.4 分组

  • Collectors.groupingBy()

分组不同于分块中按truefalse划分,分组可以按任意值来划分。例如,以员工年龄分组,将相同年龄的员工分为同一个组:

Map<Integer, List<Employee>> collect = emps.stream().collect(Collectors.groupingBy(Employee::getAge));

该结果返回一个MapMap的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);
}

6.8.5 生成字符串

  • Collectors.joining()

将流中的元素按某种规则生成一个字符串。例如,将员工中年龄大于30的员工的名字生成一个字符串输出,每个名字用“、”隔开,首尾用“【”、“】”包裹。

String collect = emps.stream().filter(e -> e.getAge() > 30).map(Employee::getName).collect(Collectors.joining("、", "【", "】"));

要注意:joining有三个重载方法,分别是没有参数、一个参数和三个参数的,其中没有参数的元素的生成字符串直接连接;一个参数的元素的生成字符串通过该参数接收的字符中连接;三个参数的,三个参数的作为分别是生成字符串的连接方式、前缀和后缀。

6.8.6 映射

  • Collectors.mapping()
List<Integer> collect = emps.stream().filter(e -> e.getAge() > 30).collect(Collectors.mapping(Employee::getAge, Collectors.toList()));

该方法能将一个元素映射成另一个元素,如上,将Employee的所有age属性映射成一个List集合。

6.8.7 归约

  • 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));
}

6.9 数值流

基本类型在内存中有固定字节长度,包装类型是对象,其字节长度要大于基本类型,如果基本类型都采用包装类型

参与运算会造成更大的系统内存开销。

基本类型转换成包装类型(装箱),包装类型转换成基本类型(开箱),也会造成额外的计算开销。

对应的Java 8中对常用的doubleintlong有相应的Stream提供,分别是DoubleStreamIntStreamLongStream

IntStream为例,学习如何使用基本类型对应的Stream操作。

2.5.1 生成流

  1. of(int value)of(int ...values)
IntStream of1 = IntStream.of(3);
IntStream of2 = IntStream.of(3, 2, 4, 8);

该方法是将传入的参数构建成一个对应的流。

  1. range(int startInclusive, int endExclusive)
// 取值 1-10 不能取到10
IntStream range = IntStream.range(1, 10);

生成指定范围(startInclusive到endExclusive)的值,生成的数不包含第二个参数。

  1. rangeClosed(int startInclusive, int endExclusive)
// 取值 1-10  可以取到10
IntStream rangeClosed = IntStream.rangeClosed(1, 10);

生成指定范围(startInclusive到endExclusive)的值,生成的数包含第二个参数。

  1. mapToInt
@Test
public void test() {
  IntStream intStream = employees.stream().mapToInt(Employee::getAge);
}

将包装类型的流转换成基本类型的流。

2.5.2 生成统计信息

  1. summaryStatistics():
// 获取统计数据
IntSummaryStatistics statistics = of.summaryStatistics();
// 从统计数据中获取流中元素平均值
statistics.getAverage();
// 从统计数据中获取流中元素的个数
statistics.getCount();
// 从统计数据中获取流中元素的最大值
statistics.getMax();
// 从统计数据中获取流中元素的最小值
statistics.getMin();
// 从统计数据中获取流中元素的和
statistics.getSum();
// 添加一个元素,统计数据将重新被计算
statistics.accept(20);
// 联合另一个统计数据,统计数据将被重新计算
statistics.combine(statistics);

2.5.3 包装类型与基本类型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

2.5.4 基本类型Stream操作

要注意:流只能被终止操作一次。

  1. count()
IntStream intStream = IntStream.range(1, 10);
intStream.count();

获取流中元素个数。

  1. sum()
IntStream intStream = IntStream.range(1, 10);
intStream.sum();

计算流中元素的总和。

  1. average()
IntStream intStream = IntStream.range(1, 10);
intStream.average();

计算流中元素的平均数。

  1. min()
IntStream intStream = IntStream.range(1, 10);
intStream.min();

计算流中元素的最小值。

  1. max
@Test
public void test22() {
  IntStream intStream = IntStream.range(1, 10);
  intStream.max();
}

计算流中的最大值。

  1. boxed()
IntStream intStream = IntStream.range(1, 10);
Stream<Integer> boxed = intStream.boxed();

转换成对应的包装类型的流。

6.10 并行流

前面,我们所提到的流都是串行流,所有流操作都是依次执行。现在我们要说的并行流,就是将串行流分成几个部分,每个部分同时并行执行,执行后将结果汇总。

如果我们已经有一个串行流,我们要从这个串行流中获取并行流,我们只需要调用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;
  }
}

7. 时间日期

7.1 TemporalFieldTemporalUnit

Java 8中新的时间日期API中有点个重要的接口,分别是TemporalFieldTemporalUnit,它们在很多地方都有使用到,这两个接口常用的实现类分别是ChronoFieldChronoUnit

7.1.1 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();
}

7.2.2 ChronoUnit

ChronoUnit是一个枚举类,其中有许多枚举值,如ChronoUnit.DAYS表示天。

@Test
public void test43() {
  ChronoUnit days = ChronoUnit.DAYS;
  // 是否基于日期
  boolean dateBased = days.isDateBased();
  // 是否基于时间
  boolean timeBased = days.isTimeBased();
  // 获取持续时间
  Duration duration = days.getDuration();
}

7.2 日期、时间和日期时间

Java 8提供了新是时间日期API,其中java.time.LocalDate用于操作日期,java.time.LocalTime用于操作时间,java.time.LocalDateTime用于操作日期时间。

虽然日期、时间或日期时间是不同的类,但是他们的创建及操作方法是类似的。这三个类中,多个方法使用到了TemporalFieldTemporalUnit,并且也提供了isSupported方法来判断是否对TemporalFieldTemporalUnit支持。

boolean supported = now.isSupported(ChronoField.NANO_OF_DAY);
boolean supported1 = now.isSupported(ChronoUnit.DAYS);

7.3 创建

三者都提供了相同的创建方式,其中of方法接收指定的值用于创建,parse用于指定格式类型的值创建,now用于创建当前的结果。

7.3.1 关于日期

@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);
}

7.3.2 关于时间

@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); // 当天第多少毫秒
}

7.3.3 关于日期时间

@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));
}

7.4 获取值

7.4.1 关于日期

  • 日历系统、纪年系统
@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();
}

7.4.2 关于时间

  • 数据值
@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);
}

7.4.3 关于日期时间

日期时间综合和了日期时间的所有方法,即可以用于获取时间相关数据,也可以获取日期相关数据,例如:

@Test
public void test48() {
  LocalDateTime now = LocalDateTime.now();
  // 日期相关 日在年中的第几天
  int dayOfYear = now.getDayOfYear();
  // 获取 小时值
  int hour = now.getHour();
}

7.5 格式化

7.5.1 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);
}

7.5.2 格式化日期或时间

@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);

}

7.6 修改

日期或时间API中提供了withXxx方法用于修改对应的类型的值。

7.6.1 关于日期

@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);
}

7.6.2 关于时间

@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);
}

7.6.3 关于日期时间

LocalDateTim中的withXxx方法是LocaDateLocalTime的综合。

7.7 计算

7.7.1 关于日期

@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);
}

7.7.2 关于日期

@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);
}

7.7.3 关于日期时间

LocalDateTime中包含了所有LocalDateLocalTime中的plusXxxmiusXxx方法,如:

@Test
public void test57() {
  LocalDateTime now = LocalDateTime.now();
  // 加20天
  LocalDateTime localDateTime = now.plusDays(20);
  // 减20小时
  LocalDateTime localDateTime1 = now.minusHours(20);
}

7.8 比较

  • 关于日期
@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);
}

7.9 时间、日期和时间日期相互转换

@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);

}

7.9 机器日期时间Instant

7.9.1 创建

@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;
}

7.9.2 获取值

@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();
}

7.9.3 比较

@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);
}

7.9.4 计算

@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);
}

7.9.5 修改

@Test
public void test29() {
  Instant now = Instant.now();
  // 机器时间 就是时间戳 秒  所以修改秒的话 就相当于改成时间戳为1000 结果为1970-01-01 00:16:40.464Z、
  Instant with = now.with(ChronoField.INSTANT_SECONDS, 1000);
}

7.10 Duration

Duration可以用于获取两个相同类型时间或日期的时间段,或指定特定类型的时间段。如果第一个时间比第二个时间大,获取的值为负数。

7.10.1 创建

@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);
}

7.10.2 获取值

@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();
}

7.10.2 计算和修改

Duration同样支持计算和修改值,同时还增加了乘除操作:

@Test
public void test() {
  Duration duration = Duration.ofHours(10);
  Duration duration1 = duration.dividedBy(10);
  Duration duration2 = duration.multipliedBy(10);
}

7.10.3 参与时间运算

// 减去 20分钟
Instant minus = instant1.minus(Duration.ofMinutes(20));

7.11 扩展

7.11.1 Period

PeriodDuration一样,但Period主要是对LocalDate的操作。

7.11.2 TemporalAdjuster

TemporalAdjuster在时间日期中也有涉及,TemporalAdjusters中封装了许多静态方法快速生成TemporalAdjuster对象。

如:

@Test
public void test34() {
  LocalDate now = LocalDate.now();
  // 时间跳转到所在月第一天
  LocalDate with = now.with(TemporalAdjusters.firstDayOfMonth());
}

关于TemporalAdjuster更多用法,请自行参考TemporalAdjusters中的方法。

如果该文章对您有帮助,请打赏!

微信打赏
支付宝打赏
发布了78 篇原创文章 · 获赞 59 · 访问量 3万+
展开阅读全文

没有更多推荐了,返回首页

©️2019 CSDN 皮肤主题: 大白 设计师: CSDN官方博客

分享到微信朋友圈

×

扫一扫,手机浏览