java 8 新特性
java8 是一个有里程碑的一个版本,提供了很多的新特性,但这些新特性是实打实有用的,而不是一些鸡肋
Interface
接口新特性
java8 之前,往接口里新加一个方法,那么所有的实现类都需要变动,都需要同步实现这个方法。
java8 给接口新增了两个关键字:default static
使得可以往接口里添加默认方法,子类可以无需变动。
interface InterfaceOne {
/**
* 实现类必须要实现的方法
*/
void method1();
/**
* 接口方法,子类可以选择实现也可以选择不实现
*/
default void method2() {
System.out.println("this is InterfaceOne#method2");
}
/**
* 静态方法,子类无需实现
*/
static void method3() {
System.out.println("this is InterfaceOne#method3");
}
}
interface InterfaceTwo {
/**
* 接口方法,子类可以选择实现也可以选择不实现
*/
default void method2() {
System.out.println("this is InterfaceTwo#method2");
}
/**
* 接口方法,子类可以选择实现也可以选择不实现
*/
default void method3() {
System.out.println("this is InterfaceTwo#method3");
}
default void method4() {
System.out.println("this is InterfaceTwo#method4");
}
}
public class InterfaceDemo implements InterfaceOne, InterfaceTwo {
/**
* 由于 method1 是 InterfaceOne 接口里的抽象方法,因此必须实现
*/
@Override
public void method1() {
}
/**
* 由于 InterfaceOne InterfaceTwo 都有一个 default 方法 method2,因此也必须重新实现
*/
@Override
public void method2() {
InterfaceOne.super.method2();
}
public static void main(String[] args) {
InterfaceDemo demo = new InterfaceDemo();
demo.method4();
InterfaceOne.method3();
}
}
如上代码块,InterfaceOne#method1 是一个抽象方法,因此 InterfaceDemo 实现类
必须重写该类,InterfaceOne 接口有一个 default method2 方法,InterfaceTwo 也有一个 default method2 方法
,InterfaceDemo 实现类不知道使用哪个,因此我们需要重写该方法。
其中,default 方法可以通过实现类的实例直接调用,这个 default 方法就很像继承特性,没有重写的话直接调用父类方法,
但是接口不是继承,而是实现,两者概念上还是有区别的。static 方法可以通过 接口.方法 直接调用,跟平时的静态方法一样
接口和抽象类的区别
这样看下来,接口和抽象类越来越像了,这样下去,抽象类会不会被淘汰呢?
其实抽象类和接口本质上还是不同的
- 接口是一个更加抽象的概念,一个实现类可以实现多个接口,并且接口里的方法默认都是抽象方法,字段默认都是静态常量
- 抽象类是一个父类,只能单继承,抽象类中的抽象方法必须手动指定,抽象类底层还是一个类
Lambda
函数式接口
定义
函数式接口是指一个接口内只有一个抽象方法,若干个 default 方法的接口,一般函数式接口都会使用 @FunctionalInterface 注解进行标注,但是并不是说没有该注解就不是一个函数式接口了,该注解只是用于编译期检查,如果有多个抽象方法,那么编译将不同过。
先来看下怎么定义一个函数式接口:
@FunctionalInterface
interface FunctionInterfaceOne {
void methodOne(String msg);
}
public class FunctionInterfaceDemo {
public static void main(String[] args) {
// 匿名类
FunctionInterfaceOne firstInstance = new FunctionInterfaceOne() {
@Override
public void methodOne(String msg) {
System.out.println("this is " + msg);
}
};
// lambda 表达式
FunctionInterfaceOne secondInstance = msg -> System.out.println("this is " + msg);
firstInstance.methodOne("firstInstance");
secondInstance.methodOne("secondInstance");
}
}
如上代码,FunctionInterfaceOne 就是一个函数式接口,在 main 方法中使用两种方式定义了一个 FunctionInterfaceOne 对象,一个是以前常用的匿名类形式,一个就是 java8 的 lambda 表达式。该函数式接口表达的是接受一个参数,并且不输出返回值。
目前我们常用的函数式接口主要有:
- BiConsumer<T,U>:代表了一个接受两个输入参数的操作,并且不返回任何结果
- BiFunction<T,U,R>:代表了一个接受两个输入参数的方法,并且返回一个结果
- BinaryOperator
:代表了一个作用于于两个同类型操作符的操作,并且返回了操作符同类型的结果 - BiPredicate<T,U>:代表了一个两个参数的boolean值方法
- BooleanSupplier:代表了boolean值结果的提供方
- Consumer
:代表了接受一个输入参数并且无返回的操作 - Function<T,R>:接受一个输入参数,返回一个结果。
- Predicate
:接受一个输入参数,返回一个布尔值结果。 - Supplier
:无参数,返回一个结果。 - java.lang.Runnable
- java.util.concurrent.Callable
- java.util.Comparator
除了几个 java8 前就有的复合函数式接口定义的接口,函数式接口大都定义在 java.utils.function 包下
Lambda 实战
-
Lambda 表达式,也可称为闭包,它是推动 Java 8 发布的最重要新特性。
-
Lambda 允许把函数作为一个方法的参数(函数作为参数传递进方法中),用代码来展示会更加清晰明了
@FunctionalInterface
interface FunctionInterfaceOne {
void methodOne(String msg);
}
public class FunctionInterfaceDemo {
public void methodOne(FunctionInterfaceOne functionInterfaceOne) {
functionInterfaceOne.methodOne("FunctionInterfaceDemo#methodOne");
}
public static void main(String[] args) {
FunctionInterfaceDemo demo = new FunctionInterfaceDemo();
demo.methodOne(msg -> System.out.println(msg));
}
}
如上代码,又一个函数式接口 FunctionInterfaceOne,类 FunctionInterfaceDemo#methodOne 接收一个 FunctionInterfaceOne 类型的实例,在 main 方法中,直接通过 lambda 表达式(msg -> System.out.println(msg))构建出来一个实例传入该方法中。按照以前的写法就是设计一个匿名内部类,传入该方法,或者为该接口定义一个实现类,然后生成一个实例,将实例作为参数传入 FunctionInterfaceDemo#methodOne 方法中。
- 使用 Lambda 表达式可以使代码变的更加简洁紧凑。
语法
(parameters) -> expression
或
(parameters) ->
- 可选类型声明:不需要声明参数类型,编译器可以统一识别参数值。
() -> 5 // 不需要参数,返回值为 5
- 可选的参数圆括号:一个参数无需定义圆括号,但多个参数需要定义圆括号。
msg -> System.out.println(msg)
(a,b) -> a+b
- 可选的大括号:如果主体包含了一个语句,就不需要使用大括号。
(a, b) -> { System.out.println(a + b); return a + b }
- 可选的返回关键字:如果主体只有一个表达式返回值则编译器会自动返回值,大括号需要指定表达式返回了一个数值。
(a,b) -> a+b
或
(a,b) -> return a+b
Stream
stream 是 java8 卓越的新特性之一,这种风格是将要处理的数据看成一种管道流,在管道中进行一系列的中间操作,然后使用最终操作得到最终想要的数据以及数据结构。由于中间操作以及最终操作的很多方法的入参都是函数式接口,因此,stream 往往配合 lambda 表达式进行使用。
+--------------------+ +------+ +------+ +---+ +-------+
| stream of elements +-----> |filter+-> |sorted+-> |map+-> |collect|
+--------------------+ +------+ +------+ +---+ +-------+
生成流
流的数据来源可以是数组、集合等
- stream() -> 生成串行流
- ParallelStream -> 生成并行流
public class StreamDemo {
public static void main(String[] args) {
List<Integer> list = Arrays.asList(1, 3, 1, 2, 3, 4, 1, 2, 3, 41, 1, 23, 1, 23213, 43);
list.stream().sorted().collect(Collectors.toList());
list.parallelStream().sorted().collect(Collectors.toList());
}
}
中间操作
这里简单记录下几个常见的中间操作
Stream<T> filter(Predicate<? super T> predicate)
<R> Stream<R> map(Function<? super T, ? extends R> mapper);
Stream<T> distinct();
Stream<T> sorted();
Stream<T> sorted(Comparator<? super T> comparator);
Stream<T> peek(Consumer<? super T> action);
Stream<T> limit(long maxSize);
Stream<T> skip(long n);
public class StreamDemo {
/**
* 塞选出大于 10 的元素并且打印出来
* filter 入参是一个 Predicate 函数式接口,接收一个参数并且返回一个布尔值,这里可以使用 lambda 表达式配合使用
*/
public static void filterDemo(Stream<Integer> stream) {
System.out.println(stream.filter(item -> item > 10).collect(Collectors.toList()));
}
/**
* 将流内元素都乘 2 并且打印出来
* map 入参是一个 Function 函数式接口,接收一个参数并且返回另一个参数
*/
public static void mapDemo(Stream<Integer> stream) {
System.out.println(stream.map(item -> item * 2).collect(Collectors.toList()));
}
/**
* 将流元素去重(使用元素的 equal 方法判断元素是否相等)并且打印出来
*/
public static void distinctDemo(Stream<Integer> stream) {
System.out.println(stream.distinct().collect(Collectors.toList()));
}
/**
* 将元素排序,如果 sorted 方法没有传参数,那么是按照自然顺序生序排,否则按照 Comparator 接口接口定义的规则排
* 一个 Stream 对象不能重复利用,否则会报错
*/
public static void sortedDemo(Stream<Integer> stream) {
// System.out.println(stream.sorted().collect(Collectors.toList()));
System.out.println(stream.sorted((one, two) -> two - one).collect(Collectors.toList()));
}
/**
* peek 方法的入参是一个 Consumer 函数式接口,该接口接收一个参数但是不返回参数,因此,peek 方法只能根据元素做一些操作,但是无法返回,与遍历有点像
* 此方法的存在主要是为了支持调试,在调试中,我们希望在元素流过管道中的某个点时看到它们:
*/
public static void peekDemo(Stream<Integer> stream) {
// stream.peek(System.out::println).collect(Collectors.toList());
}
/**
* limit 方法用于取出前 n 个元素,然后基于这 n 个元素生成一个新的流用于后续的操作,和 sql 中的 limit 关键字用法一样
*/
public static void limitDemo(Stream<Integer> stream) {
System.out.println(stream.limit(2).collect(Collectors.toList()));
}
/**
* skip 方法用于跳过前 n 个元素,将剩下的元素生成一个新的流用于后续操作,和 sql 中的 offset 关键字用法一样
*/
public static void skipDemo(Stream<Integer> stream) {
System.out.println(stream.skip(2).collect(Collectors.toList()));
}
public static void main(String[] args) {
List<Integer> list = Arrays.asList(1, 3, 1, 2, 3, 4, 1, 2, 3, 41, 1, 23, 1, 23213, 43);
filterDemo(list.stream());
mapDemo(list.stream());
distinctDemo(list.stream());
sortedDemo(list.stream());
peekDemo(list.stream());
limitDemo(list.stream());
skipDemo(list.stream());
}
}
这里需要注意的一个点是流内所有的中间操作都必须遇到终端操作才会执行,否则就是一个定义,不会真正跑,如下代码
public static void main(String[] args) {
List<Integer> list = Arrays.asList(1, 3, 1, 2, 3, 4, 1, 2, 3, 41, 1, 23, 1, 23213, 43);
Stream<Integer> integerStream = list.stream().map(item -> {
int i = item * 2;
System.out.println(i);// 1
return i;
});
System.out.println("this is a temp");// 2
integerStream.collect(Collectors.toList());// 3
}
按照代码的自上而下执行的顺序原则,1 处的打印应该先执行,2 处的打印应该后执行,但是事实是 2 处的打印反而先执行,如果我们将 3 处的代码注释掉,那么 1 处的代码就不会执行了,因此,所有流的中间操作都必须遇到终端操作才会真正执行。
终端操作
流的终端操作是得到之前经过一系列中间操作后的结果
常见的终端操作有
<R, A> R collect(Collector<? super T, A, R> collector);
void forEach(Consumer<? super T> action);
Object[] toArray();
<A> A[] toArray(IntFunction<A[]> generator);
long count();
boolean anyMatch(Predicate<? super T> predicate);
boolean allMatch(Predicate<? super T> predicate);
boolean noneMatch(Predicate<? super T> predicate);
Optional<T> findFirst();
Optional<T> findAny();
Optional<T> max(Comparator<? super T> comparator);
Optional<T> min(Comparator<? super T> comparator);
T reduce(T identity, BinaryOperator<T> accumulator);
Optional<T> reduce(BinaryOperator<T> accumulator);
public class StreamDemo {
/**
* 将流以集合的形式输出
*/
public static void collectDemo(Stream<Integer> stream) {
System.out.println(stream.collect(Collectors.toList()));
// System.out.println(stream.collect(Collectors.toSet()));
}
/**
* 对此流的每个元素执行操作,也就是遍历
*/
public static void forEachDemo(Stream<Integer> stream) {
stream.forEach(item -> System.out.println(item));
}
/**
* 将流转化成数组元素
*/
public static void toArrayDemo(Stream<Integer> stream) {
// Object[] objects = stream.toArray();
Integer[] integers = stream.toArray(Integer[]::new);
}
/**
* 获取流中的元素个数
*/
public static void countDemo(Stream<Integer> stream){
System.out.println(stream.count());
}
/**
* 流中元素是否有任意一个满足条件
*/
public static void anyMatchDemo(Stream<Integer> stream) {
System.out.println(stream.anyMatch(item -> Objects.equals(item, 1)));
}
/**
* 流中元素是否都满足条件
*/
public static void allMatchDemo(Stream<Integer> stream) {
System.out.println(stream.allMatch(item -> Objects.equals(item, 1)));
}
/**
* 流中元素是否没有一个满足条件
*/
public static void noneMatchDemo(Stream<Integer> stream) {
System.out.println(stream.noneMatch(item -> Objects.equals(item, 1)));
}
/**
* 获取流中的第一个元素
*/
public static void findFirstDemo(Stream<Integer> stream) {
Optional<Integer> first = stream.findFirst();
first.ifPresent(System.out::println);
}
/**
* 获取流中的任意一个元素
*/
public static void findAnyDemo(Stream<Integer> stream) {
Optional<Integer> element = stream.findAny();
element.ifPresent(System.out::println);
}
/**
* 获取流中最大元素(根据自己定义的 Comparator 接口来)
*/
public static void maxDemo(Stream<Integer> stream) {
Optional<Integer> max = stream.max((a, b) -> a - b);
max.ifPresent(System.out::println);
}
/**
* 获取流中最小元素(根据自己定义的 Comparator 接口来)
*/
public static void minDemo(Stream<Integer> stream) {
Optional<Integer> max = stream.min((a, b) -> a - b);
max.ifPresent(System.out::println);
}
/**
* 聚合操作
*/
public static void reduceDemo(List<Integer> list) {
// 对 list 元素求和,然后加上 0
System.out.println(list.stream().reduce(0, Integer::sum));
// 对 list 元素求和,然后加上 10000
System.out.println(list.stream().reduce(10000, Integer::sum));
System.out.println(list.stream().reduce(0, (a, b) -> a - b));
System.out.println(list.stream().reduce(0, (a, b) -> a / b));
System.out.println(list.stream().reduce(0, (a, b) -> a * b));
// 最大和最小
System.out.println(list.stream().reduce(0, Integer::min));
System.out.println(list.stream().reduce(0, Integer::max));
}
}
Optional
optional 是 java 8 新的判空特性,
老的判空方法
public class OptionalDemo {
static class A {
B b;
}
static class B {
Integer c;
}
private static A getA() {
return new A();
}
public static void main(String[] args) {
A a = getA();
if(a != null){
B b = a.b;
if(b != null){
System.out.println(a.b.c);
}
}
}
}
optional 判空方法
public class OptionalDemo {
@Data
@NoArgsConstructor
@AllArgsConstructor
static class A {
B b;
}
@Data
@NoArgsConstructor
@AllArgsConstructor
static class B {
Integer c;
}
private static A getA() {
return new A(new B(1));
}
public static void main(String[] args) {
A a = getA();
Optional.ofNullable(a).map(A::getB).map(B::getC).ifPresent(System.out::println);
}
}
optional 特性
optional
// 空对象对应的 Optional 实例
private static final Optional<?> EMPTY = new Optional<>();
// Optional 实例的操作对象,可能为 null,也可能有值
private final T value;
// 构造函数,value 不能为空,为空抛出 npe 异常
private Optional(T value) {
this.value = Objects.requireNonNull(value);
}
// 如果 value 为空,则返回 empty 对象,否则构造一个 Optional 实例
public static <T> Optional<T> ofNullable(T value) {
return value == null ? empty() : of(value);
}
// 返回一个 EMPTY 对象
public static<T> Optional<T> empty() {
@SuppressWarnings("unchecked")
Optional<T> t = (Optional<T>) EMPTY;
return t;
}
// 构造一个 Optional 实例,如果 value 为空,则抛出 npe 异常
public static <T> Optional<T> of(T value) {
return new Optional<>(value);
}
常用的构造 Optional 对象通常使用 ofNullable 方法,如果想要让异常透传出来,才使用 of 方法
map
// 通过入参 mapper 构造一个新的 Optional 对象
// 如果入参 mapper 为null,抛出 npe 异常,如果对应的 Optional 实例的 value 为 null,返回 EMPTY 对象,否则生成新的 Optional // 对象用于后续操作
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));
}
}
isPresent
// 如果操作的 Optional 对象的 value 值不为空,返回 true,否则返回 false
public boolean isPresent() {
return value != null;
}
// 如果操作的 Optional 对象的 value 值不为空,则执行对应的逻辑
public void ifPresent(Consumer<? super T> consumer) {
if (value != null)
consumer.accept(value);
}
// 用法
Optional.ofNullable(a).map(A::getB).map(B::getC).ifPresent(System.out::println);
获取值
// 如果 Optional 对象的 value 不为 null,返回 value,否则返回 other
public T orElse(T other) {
return value != null ? value : other;
}
Integer integer = Optional.ofNullable(a).map(A::getB).map(B::getC).orElse(0);
// 如果 Optional 对象的 value 不为 null,返回 value,否则执行 other 的逻辑
public T orElseGet(Supplier<? extends T> other) {
return value != null ? value : other.get();
}
Integer value = Optional.ofNullable(a).map(A::getB).map(B::getC).orElseGet(() -> {
System.out.println("value is null");
return 0;
});
// 如果value != null 返回value,否则抛出参数返回的异常
public <X extends Throwable> T orElseThrow(Supplier<? extends X> exceptionSupplier) throws X {
if (value != null) {
return value;
} else {
throw exceptionSupplier.get();
}
}
try {
Integer value = Optional.ofNullable(a).map(A::getB).map(B::getC).orElseThrow(() -> {
System.out.println("value is null");
return new Exception("value is null");
});
} catch (Exception e) {
e.printStackTrace();
}
/**
* value为null抛出NoSuchElementException,不为空返回value。
*/
public T get() {
if (value == null) {
throw new NoSuchElementException("No value present");
}
return value;
}
Integer integer = Optional.ofNullable(a).map(A::getB).map(B::getC).get();
过滤值
/**
* 1. 如果是empty返回empty
* 2. predicate.test(value)==true 返回this,否则返回empty
*/
public Optional<T> filter(Predicate<? super T> predicate) {
Objects.requireNonNull(predicate);
if (!isPresent())
return this;
else
return predicate.test(value) ? this : empty();
}
常见用法
Optional.ofNullable(a).map(A::getB).map(B::getC).filter(v->v==1).orElse(0);
Date-Time
Java 8 新的时间特性
LocalDateTime.class //日期+时间 format: yyyy-MM-ddTHH:mm:ss.SSS
LocalDate.class //日期 format: yyyy-MM-dd
LocalTime.class //时间 format: HH:mm:ss
格式化
//format yyyy-MM-dd HH:mm:ss
LocalDateTime dateTime = LocalDateTime.now();
DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
String dateTimeStr = dateTime.format(dateTimeFormatter);
System.out.println(String.format("dateTime format : %s", dateTimeStr));
字符串转日期
LocalDate date = LocalDate.of(2021, 1, 26);
LocalDate.parse("2021-01-26");
LocalDateTime dateTime = LocalDateTime.of(2021, 1, 26, 12, 12, 22);
LocalDateTime.parse("2021-01-26 12:12:22");
LocalTime time = LocalTime.of(12, 12, 22);
LocalTime.parse("12:12:22");
参考
https://github.com/CSanmu/JavaGuide/blob/main/docs/java/new-features/java8-common-new-features.md