JDK8 新特性

想更详细的了解JDK8新特性可以浏览官方介绍

JDK8 新特性目录导航:

  • Lambda 表达式
  • 函数式接口
  • 方法引用、构造器引用和数组引用
  • 接口支持默认方法和静态方法
  • Stream API
  • 增强类型推断
  • 新的日期时间 API
  • Optional 类
  • 重复注解和类型注解

Lambda 表达式

Lambda 是一个匿名函数,我们可以把 Lambda 表达式理解为是一段可以传递的代码(将代码像数据一样进行传递)。可以写出更简洁、更灵活的代码。作为一种更紧凑的代码风格,使Java的语言表达能力得到了提升。

如下示例,将一个匿名类转换为Lambda表达式:

//匿名内部类
Runnable runnable = new Runnable() {
    @Override
    public void run() {
        System.out.println("Hello world!");
    }
};
//Lambda 表达式
Runnable runnable = () -> System.out.println("Hello world!");

第一个匿名内部类的写法new一个Runnable接口并重写run方法打印Hello world!需要写一堆代码,但核心代码就一句System.out.println("Hello world!"),然而Lambda表达式仅需要一句代码() -> System.out.println("Hello world!")就可以替代上面的匿名内部类整个代码。从这个转换来看,Lambda表达式可以让代码更简洁,更灵活。

Lambda 表达式语法

Lambda 表达式在Java语言中引入了一个新的语法元素和操作符。这个操作符为 "->" ,该操作符被称为Lambda 操作符号或箭头操作符。它将Lambda分为两部分:

  • 左侧: 指定了Lambda 表达式需要的所有参数。
  • 右侧: 指定了Lambda 体,即Lambda 表达式要执行的功能。

语法格式一: 无参,无返回值。Lambda 体只需要一条语句

Runnable runnable = () -> System.out.println("Hello world!");

语法格式二: 一个参数无返回值。注:一个参数时,扩符可以省略

Consumer<String> consumer = (e) -> System.out.println(e);//一个参数时,参数的扩号可以省略。

语法格式三: 两个参数并且有返回值。注:当Lambda 体只有一条语句时,可以省略 return 和 大括号。参数类型是可以省略的。通过编译器类型上下文推断出。同时也建议省略。如不省略则所有参数都必须加上类型。

BinaryOperator<Integer> bo = (x,y) ->{
    System.out.println("实现函数式接口方法!");
    return x  + y;
};
//当Lambda 体只有一条语句时,可以省略 return大括号
BinaryOperator<Integer> bo1 = (x,y) -> x  + y;
//Lambda 的参数类型是可以省略的。通过编译器类型上下文推断出。同时也建议省略。如不省略则所有参数都必须加上类型。
BinaryOperator<Integer> bo2 = (Integer x,Integer y) -> x  + y;

语法格式四: 作为参数传递Lambda 表达式:为了将 Lambda 表达式作为参数传递,接收Lambda 表达式的参数类型必须是与该Lambda 表达式兼容的函数式接口的类型。

 1 import java.util.function.Function;
 2 
 3 public class TestLambda {
 4 
 5     public static void main(String[] args) {
 6         String str = toUpperString("abcdefg", (e) -> e.toUpperCase());
 7         System.out.println(str);
 8     }
 9 
10     public static String toUpperString(String string, Function<String, String> function) {
11         return function.apply(string);
12     }
13 
14 }

函数式接口

只包含一个抽象方法的接口,称为函数式接口。你可以通过Lambda 表达式来创建该接口的对象。我们可以在任意函数式接口上使用 @FunctionalInterface 注解,这样做可以检查它是否是一个函数式接口,同时 javadoc 也会包含一条声明,说明这个接口是一个函数式接口。

如下所示,自定义函数式接口:

1 @FunctionalInterface
2 public interface MyInterface {
3     public void getValue();
4 }

Java 内置四大核心函数式接口

因为Lambda 表达式必须依赖函数式接口,然为了避免Lambda 表达式特意去书写函数式接口。Java 内置了如下四大核心函数式接口:

  • Consumer<T>: 消费型接口,表示一个接受单个输入参数并返回没有结果的操作。对类型为T的对象应用操作。接口方法: void accept(T t)
  • Supplier<T>: 供给型接口,类似一个供应商,返回一个类型为T的对象。接口方法: T get()
  • Function<T, R>: 函数型接口,表示一个接受一个参数并产生结果的函数。接口方法: R apply(T t)
  • Predicate<T>: 断言型接口,确定类型为T的对象是否满足某约束,并返回boolean 值。接口方法: boolean test(T t)

除了以上四大内置接口外还有许许多多的函数式接口在 java.util.function 包下,比如:

  • BiFunction<T, U, R>: 与Function<T,R>类似,对类型为 T, U 参数应用操作,返回 R 类型的结果。接口方法:R apply(T t, U u);
  • UnaryOperator<T>: Function<T, T>的子接口。对类型为T的对象进行一元运算,并返回T类型的结果。接口方法:T apply(T t);
  • BinaryOperator<T>: BiFunction<T,T,T>的子接口,对类型为T的对象进行二元运算,并返回T类型的结果。接口方法:T apply(T t1, T t2);
  • ......
  • BiConsumer<T, U>: 对类型为T, U 参数应用操作。接口方法:void accept(T t, U u);

 

方法引用、构造器引用和数组引用

当要传递给Lambda体的操作,已经有实现的方法了,可以使用方法引用!(实现抽象方法的参数列表,必须与方法引用方法的参数列表保持一致!)

方法引用

使用操作符 “ ::” 将方法名和对象或类的名字分隔开来。有如下三种格式:

  • 引用静态方法: 类 :: 静态方法 
  • 引用特定对象的实例方法: 对象 :: 实例方法
  • 引用特定类型任意对象的实例方法: 特定类型 :: 实例方法

如下示例,调用Math类的静态对象pow方法,可以直接使用Math::pow(格式:类::静态方法),引用静态方法代替Lambda 表达式。

BinaryOperator<Double> bo = (x, y) -> Math.pow(x, y);
System.out.println(bo.apply(2d, 3d));
//Math::pow 可以替代 (x, y) -> Math.pow(x, y)
BinaryOperator<Double> bo2 = Math::pow;
System.out.println(bo2.apply(2d, 4d));

输出结果:

8.0
16.0

如下所示:调用System.out静态方法获取PrintStream对象再调用printf方法,可以直接使用System.out::printf(格式:对象::实例方法),引用特定对象的实例方法代替Lambda 表达式。

Consumer<String> consumer = (x) -> System.out.println(x);
consumer.accept("Hello");
//System.out::printf 可以替代 (x) -> System.out.println(x)
Consumer<String> consumer2 = System.out::printf;
consumer2.accept("world");

输出结果:

Hello
world

如下所示:String特定类型的实例方法equals,可以直接使用String::equals(格式:特定类型::实例方法), 引用特定类型任意对象的实例方法代替Lambda 表达式。

BiPredicate<String, String> bp = (x, y) -> x.equals(y);
System.out.println(bp.test("abcdef", "abcdef"));
//String::equals 可以替代 (x, y) -> x.equals(y)
BiPredicate<String, String> bp2 = String::equals;
System.out.println(bp2.test("abcdef", "abcdef"));

输出结果:

true
true

构造器引用

格式: 类::new  如下示例所示:new MyClass(n)构造器,可以直接使用MyClass::new。构造器引用可以直接代替Lambda 表达式。

 1 public class MyClass {
 2     Integer i;
 3 
 4     public MyClass() {
 5     }
 6 
 7     public MyClass(Integer i) {
 8         this.i = i;
 9     }
10 
11     @Override
12     public String toString() {
13         return "MyClass{" +
14                 "i=" + i +
15                 '}';
16     }
17 }
Function<Integer, MyClass> myClass = (n) -> new MyClass(n);
System.out.println(myClass.apply(15).toString());
//MyClass::new 可以替代 (n) -> new MyClass(n)
Function<Integer, MyClass> myClass2 = MyClass::new;
System.out.println(myClass2.apply(10).toString());

输出结果:

MyClass{i=15}
MyClass{i=10}

数组引用

格式:type[] :: new  如下示例所示:new Integer[n] 数组可以直接使用Integer[]::new代替。数组引用可以直接代替Lambda 表达式。

Function<Integer, Integer[]> function = (n) -> new Integer[n];
System.out.println(function.apply(15).length);
//Integer[]::new 可以替代 (n) -> new Integer[n]
Function<Integer, Integer[]> function2 = Integer[]::new;
System.out.println(function2.apply(10).length);

输出结果:

15
10

 

接口支持默认方法和静态方法

JDK8 中允许接口中包含具体的实现方法,该方法称为默认方法。同时接口中还支持静态方法。

默认方法

默认方法使用 default 关键字修饰。使用default修饰的方法,则可以在接口中进行具体实现,如下所示:

 1 public interface MyInterface {
 2 
 3     //接口中的常规方法是不能实现的。
 4     int getValue();
 5 
 6     //接口中的具体实现默认方法:getName
 7     default String getName(){
 8         return "Hello JDK8!";
 9     }
10 
11     //接口中的具体实现默认方法:getAge
12     default int getAge(){
13         return 8;
14     }
15 
16 }
1 public interface MyFunc {
2 
3     default String getName(){
4         return "Hello MyFunc!";
5     }
6 }
1 public class MyClass {
2 
3     public String getName(){
4         return "Hello MyClass!";
5     }
6 }
public class SubClass extends MyClass implements MyFunc{
}

输出结果:

Hello MyClass!

上面的示例可以看到,MyFunc接口中有默认方法getName、MyClass中也有getName()方法。然SubClass对象继承MyClass对象,同时实现MyFunc接口,调用SubClass对象的getName方法,实际执行的是MyClass对象的方法。接口的默认方法实现“类优先”原则。

若一个接口中定义了一个默认方法,而另外一个父类又定义了一个同名的方法时,选择父类中的方法。如果一个父类提供了具体的实现,那么接口中具有相同名称和参数的默认方法会被忽略。

因接口可以多实现,则会出现如下示例:

 1 public class TestClass implements MyFunc, MyInterface {
 2 
 3     @Override
 4     public int getValue() {
 5         return 0;
 6     }
 7 
 8     @Override
 9     public String getName() {
10         //因为接口可以多实现,然MyFunc接口 和 MyInterface接口 都有getName默认方法。于是需要使用一下方法进行指定调用。
11 //        return MyInterface.super.getName();
12         return MyFunc.super.getName();
13     }
14 
15     @Override
16     public int getAge() {
17         return 0;
18     }
19 }
TestClass testClass = new TestClass();
System.out.println(testClass.getName());

输出结果:

Hello MyFunc!

上面的示例可以看出。MyFunc接口和MyInterface接口中都有getName默认方法,然TestClass同时实现以上两个接口时,必须覆盖该方法来解决冲突。

静态方法

在JDK8 中,接口中允许使用静态方法。和类一样通过接口名称点静态方法去调用,如下示例所示:

1 public interface MyFunction {
2 
3     //接口中使用静态方法
4     static void show(){
5         System.out.println("Hello static!");
6     }
7 }
MyFunction.show();

输出结果:

Hello static!

Stream API

Java8中有两大最为重要的改变。第一个是 Lambda 表达式;另外一个则是 Stream API( java.util.stream .*) 。

Stream 是JDK8 中处理集合的关键抽象概念,他可以指定你希望对集合进行的操作,可以执行非常复杂的查找、过滤和映射数据等操作。使用Stream API 对集合数据进行操作,就类似于使用SQL 执行的数据库查询。也可以使用Stream API 来并行执行操作。简而言之,Stream API 提供了一种非常高效且易于使用的处理数据的方式。

流(Stream)到底是什么?

是数据渠道,用于操作数据源(集合、数组等)所生成的元素序列。“集合讲的是数据,流讲的是计算!”

注意一下三点:

  1. Stream 自己不会存储元素。
  2. Stream 不会改变源对象。相反,他们会返回一个持有结果的新 Stream。
  3. Stream 操作是延迟执行的。这也意味着他们会等到需要结果的时候才执行。

Stream 的操作三个步骤:

  1. 创建 Stream: 一个数据源(如:集合、数组),获取一个流。
  2. 中间操作: 一个中间操作链,对数据源的数据进行一系列处理。
  3. 终止操作(终端操作): 一个终止操作,执行中间操作链,并产生结果。 

创建 Stream

JDK8 中的 Collection 接口被拓展,提供了两个获取流的方法:

  • default Stream<E> stream : Collection 接口的默认方法,返回一个顺序流。
  • default Stream<E> parallelStream: Collection 接口的的默认放,返回一个并行了。

同时JDK8 在Arrays类中提供许多重载的Stream()静态方法 ,可以获取数组流。如下所示,可以处理很多类型的数组。

  • public static <T> Stream<T> stream(T[] array)
  • public static <T> Stream<T> stream(T[] array, int startInclusive, int endExclusive)
  • public static IntStream stream(int[] array)
  • public static IntStream stream(int[] array, int startInclusive, int endExclusive)
  • public static LongStream stream(long[] array)
  • public static LongStream stream(long[] array, int startInclusive, int endExclusive)
  • public static DoubleStream stream(double[] array)
  • public static DoubleStream stream(double[] array, int startInclusive, int endExclusive) 

Stream接口中提供了of静态方法,来创建一个流。

  • public static<T> Stream<T> of(T t)
  • public static<T> Stream<T> of(T... values)

Stream接口还提供了iterate和generate方法创建无限流。

  • public static<T> Stream<T> iterate(final T seed, final UnaryOperator<T> f)
  • public static<T> Stream<T> generate(Supplier<T> s)

Stream 的中间操作

Stream 可以将多个中间操作连接起来形成一条流水线,除非流水线上触发终止操作,否则中间操作不会执行任何的处理。而在终止操作一次性全部处理,称为“惰性求值”

Stream 的中间操作有以下几种:

  • 筛选与切片: 将Stream流进行筛选或截断处理。 
    • Stream<T> filter(Predicate<? super T> predicate): 接收Lambda 表达式,从流中排出某些元素;
    • Stream<T> distinct(): 筛选,通过流所生成元素的 hashCode() 和 equals() 去除重复元素;
    • Stream<T> limit(long maxSize): 截断流,使其元素不超过给定数量;
    • Stream<T> skip(long n): 跳过元素,返回一个扔掉了前n个元素的流。若流中元素不足n个,则返回一个空流。与limit方法互补。
  • 映射: 将Stream流映射到一个新的元素上。
    • <R> Stream<R> map(Function<? super T, ? extends R> mapper): 接受一个函数作为参数,该函数被应用到每一个元素上,并将其映射成一个新的元素。
    • IntStream mapToInt(ToIntFunction<? super T> mapper):接受一个函数作为参数,该函数被应用到每一个元素上,并将其映射成一个新的IntStream。
    • LongStream mapToLong(ToLongFunction<? super T> mapper):接受一个函数作为参数,该函数被应用到每一个元素上,并将其映射成一个新的LongStream。
    • DoubleStream mapToDouble(ToDoubleFunction<? super T> mapper):接受一个函数作为参数,该函数被应用到每一个元素上,并将其映射成一个新的DoubleStream。
    • <R> Stream<R> flatMap(Function<? super T, ? extends Stream<? extends R>> mapper): 接受一个函数作为参数,将流中的每一个值都换成另一个流,然后把所有流连接成一个流。flatMapToDouble、flatMapToInt和flatMapToLong以上差不多。只是获得具体的新流。
  • 排序: 将Stream流进行排序处理。 
    • Stream<T> sorted(): 返回一个新流,按自然顺序排序。
    • Stream<T> sorted(Comparator<? super T> comparator): 返回一个新流,按comparator比较器进行排序。

Stream 的终止操作

Stream 的终止操作会从流的流水线操作操作上获取一个新流。其结果可以是任何不是流的值。例如:List、Integer。甚至可以是 void。

Stream 的终止操作有如下几种:

  • 查找与匹配: 查找流中的数据和进行匹配。
    • boolean allMatch(Predicate<? super T> predicate): 检查所有元素是否匹配该规则,返回一个布尔值。
    • boolean anyMatch(Predicate<? super T> predicate): 检查是否至少有一个匹配该规则,返回一个布尔值。
    • boolean noneMatch(Predicate<? super T> predicate): 检查该规则没有匹配所有元素,返回一个布尔值。
    • Optional<T> findFirst(): 返回流的第一个元素。
    • Optional<T> findAny(): 返回随机的一个元素。
    • long count(): 返回流的总数。
    • Optional<T> max(Comparator<? super T> comparator): 返回流中的最大值。
    • Optional<T> min(Comparator<? super T> comparator): 返回流中的最小值。
    • void forEach(Consumer<? super T> action): 内部迭代(Stream API 使用了内部迭达。相反,使用Collection 接口需要用户做的迭代是外部迭代)。
  • 归约:  将流中的元素反复结合返回一个新值。(备注:map 和 reduce 的连接通常称为 map-reduce 模式,因为Google 用它来进行网络搜索而出名)
    • Optional<T> reduce(BinaryOperator<T> accumulator): 将流中的元素反复结合,并返回一个新值 Optional<T>。
    • T reduce(T identity, BinaryOperator<T> accumulator): 将流中的元素反复结合,并返回一个新值T。
  • 收集: 将流转换为其他形式,用于将Stream中的元素做汇总。
    • <R, A> R collect(Collector<? super T, A, R> collector): 将流转换为其他形式。接收一个Collector 接口的实现,用于给Stream 中元素做汇总的方法。

collector 接口中方法的实现决定了如何对流执行收集操作(如收集到 List 、Set、Map)。但是Collectors 实用类提供了很多静态方法,可以方便地创建收集器实例,具体方法与实例如下表:

方法 返回类型 作用 示例
toList List<T> 把流中的元素收集到List List<Employee> emps= list.stream().collect(Collectors.toList());
toSet Set<T> 把流中的元素收集到Set Set<Employee> emps= list.stream().collect(Collectors.toSet());
toCollection Collection<T> 把流中的元素收集到创建的集合 Collection<Employee>emps=list.stream().collect(Collectors.toCollection(ArrayList::new));
counting Long 计算流中元素的个数 long count = list.stream().collect(Collectors.counting());
summingInt Integer 对流中元素的整数进行求和 inttotal=list.stream().collect(Collectors.summingInt(Employee::getSalary));
averagingInt Double  计算流中元素Integer属性的平均值 doubleavg= list.stream().collect(Collectors.averagingInt(Employee::getSalary));
summarizingInt IntSummaryStatistics 收集流中Integer属性的统计值。如:平均值 IntSummaryStatisticsiss= list.stream().collect(Collectors.summarizingInt(Employee::getSalary));
joining String 连接流中每个字符串 String str= list.stream().map(Employee::getName).collect(Collectors.joining());
maxBy Optional<T> 根据比较器选择最大值 Optional<Emp>max= list.stream().collect(Collectors.maxBy(comparingInt(Employee::getSalary)));
minBy Optional<T> 根据比较器选择最小值 Optional<Emp> min = list.stream().collect(Collectors.minBy(comparingInt(Employee::getSalary)));
reducing 归约产生的类型

从一个作为累加器的初始值开始,利用BinaryOperator

与流中元素逐个结合,从而归约成单个值

inttotal=list.stream().collect(Collectors.reducing(0, Employee::getSalar, Integer::sum));
collectingAndThen 转换函数返回的类型 包裹另一个收集器,对其结果转换函数 inthow= list.stream().collect(Collectors.collectingAndThen(Collectors.toList(), List::size));
groupingBy Map<K, List<T>>  根据某属性值对流分组,属性为K,结果为V Map<Emp.Status, List<Emp>> map= list.stream().collect(Collectors.groupingBy(Employee::getStatus));
partitioningBy Map<Boolean, List<T>> 根据true或false进行分区 Map<Boolean,List<Emp>>vd= list.stream().collect(Collectors.partitioningBy(Employee::getManage));

并行流与串行流

并行流就是把一个内容分为多个数据块,并使用不同的线程分别处理每个数据块的流。JDK8 中将并行进行了优化,我们可以很容易的对数据进行并行操作。Stream API 可以声明性地通过parallel方法 与 sequential方法在并行流与串行流中进行切换。

增强类型推断

JDK8 中,编译器利用目标类型来推断泛型方法调用的类型参数。表达式的目标类型是编译器期望的数据类型,这取决于表达式出现的位置。例如:在JDK7 中使用赋值语句的目标类型进行类型推断。但是,在JDK8中,可以在更多上下文中使用目标类型进行类型推断。最显著的例子是使用方法调用的目标类型来推断其参数的数据类型。思考下面例子:

//JDK7中,可以通过目标类型 stringList 的类型为String 推断出 ArrayList() 泛型类型为String。
List<String> stringList = new ArrayList<>();
stringList.add("A");
//JDK8中,可以通过方法addALL的String类型,推断出 Arrays.asList()泛型类型为String。
//在JDK7中,编译器是不能接受这段代码。因为它不支持目标方法调用来推断参数类型。
//所以在JDK7 中必须这样写: stringList.addAll(Arrays.<String>asList());
stringList.addAll(Arrays.asList());

如上示例大概可以看出,增强的类型推断主要就是,可以通过调用泛型而通过调用者stringList的类型String。推断出Arrays.asList泛型的类型。

新的日期时间 API

JDK8中提供了一套全新的时间日期API(java.time.*)包下。使用了final修饰类,是起不可变,每次修改都是重新创建对象,类始于String对象,解决了线程安全问题。

LocalDate、LocalTime 和 LocalDateTime类

三个类的实例都是不可变的,每次修改操作都是新建一个实例对象。分别表示使用ISO-8601日历系统的日期、时间、日期和时间。它们提供了简单的日期或时间,并不包含当前的时间信息。也不包含与时区相关的信息。注:ISO-8601日历系统是国际标准化组织制定的现代公民的日期和时间的表示法。

详细的方法如下表:

方法 描述
now() 静态方法,根据当前时间创建对象
of() 静态方法,根据指定日期/时间创建对象

plusDays

plusWeeks

plusMonths

plusYears

向当前 LocalDate 对象添加几天、
几周、几个月、几年

plus

minus

添加或减少一个 Duration 或 Period

withDayOfMonth

withDayOfYear

withMonth

withYear

将月份天数、年份天数、月份、年份修改为指定 的值 并返回新的LocalDate 对象

getDayOfMonth

获得月份天数(1-31)
getDayOfYear 获得年份天数(1-366)
getDayOfWeek 获得星期几(返回一个 DayOfWeek枚举值)
getMonth 获得月份, 返回一个 Month 枚举值
getMonthValue 获得月份(1-12)
getYear 获得年份
until 获得两个日期之间的Period 对象,或者指定 ChronoUnits 的数字

isBefore

isAfter

比较两个 LocalDate
isLeapYear 判断是否是闰年

列举以下几个例子:

LocalDate localDate = LocalDate.now();//获取当前日期
LocalTime localTime = LocalTime.now();//获取当前时间
LocalDateTime localDateTime = LocalDateTime.now();//获取当前日期时间
LocalDateTime localDateTime1 = LocalDateTime.of(2018, 12, 19, 17, 00, 50);//通过指定数据去获取日期时间
LocalDate localDate1 = localDate.plusDays(1);
System.out.println("localDate: " + localDate);
System.out.println("localTime: " + localTime);
System.out.println("localDateTime: " + localDateTime);
System.out.println("localDateTime1: " + localDateTime1);
System.out.println("localDate1: " + localDate1);
System.out.format("%s年%s月%s日 %s:%s:%s", localDateTime.getYear(),localDateTime.getMonthValue(),localDateTime.getDayOfMonth(),
        localDateTime.getHour(),localDateTime.getMinute(),localDateTime.getSecond());
System.out.println("localDateTime1 isBefore localDateTime" + localDateTime1.isBefore(localDateTime));
System.out.println("是否闰年:"+ localDate.isLeapYear());

输出结果:

localDate: 2018-06-19
localTime: 17:12:37.701
localDateTime: 2018-06-19T17:12:37.701
localDateTime1: 2018-12-19T17:00:50
localDate1: 2018-06-20
2018年6月19日 17:12:37localDateTime1 isBefore localDateTimefalse
是否闰年:false

Instant 时间戳

Instant 用于 “时间戳” 的运算。它是在Unix元年(传统的设定为UTC时区1970年1月1日午夜时分)开始进行计算。常用方法如下:

  • public int getNano(): 获得纳秒值。
  • public long getEpochSecond(): 获得秒数。
  • public long toEpochMilli(): 获得分钟数。

Duration 和 Period

duration用来计算两个时间的间隔。period用于计算两个日期的间隔。

Optional 类

JDK8 中新增一个Optional<T>类 (java.util.Optional) 是一个容器类,代表一个值存在或不存在,原来用 null 表示一个值不存在,现在用 Optional 可以更好的表达这个概念。并且可以避免空指针异常。下图是Optional类的大致内容:

常用的方法:

  • public static <T> Optional<T> of(T value): 创建一个Optional 实例。
  • public static<T> Optional<T> empty(): 创建一个空的Optional 实例。
  • public static <T> Optional<T> ofNullable(T value): 若T不为null,创建Optional实例,否则创建空实例。代码如下:return value == null ? empty() : of(value)。
  • public boolean isPresent(): 判断值是否为空。
  • public T orElse(T other): 如果值不为空返回该值,否则返回 other实例。
  • public T orElseGet(Supplier<? extends T> other): 如果调用该对象有值,返回该值,否则返回other的获取值。
  • public<U> Optional<U> map(Function<? super T, ? extends U> mapper): 如果有值对其处理,并返回处理后的Optional,否则返回 Optional.empty()。
  • public<U> Optional<U> flatMap(Function<? super T, Optional<U>> mapper): 与 map 类似,要求返回值必须是Optional。
  • public T get(): 获取Optional对象的值。

重复注解和类型注解

重复注解

在某些特定的情况下,您希望将相同的注解应用于声明或类型用途。思考如下示例:

 1 import java.lang.annotation.Repeatable;
 2 import java.lang.annotation.Retention;
 3 import java.lang.annotation.RetentionPolicy;
 4 import java.lang.annotation.Target;
 5 
 6 import static java.lang.annotation.ElementType.*;
 7 
 8 //使用Repeatable注解指定可以重复注解,注解容器为MyAnnotations
 9 @Repeatable(MyAnnotations.class)
10 @Target({TYPE,FIELD,METHOD,PARAMETER})
11 @Retention(RetentionPolicy.RUNTIME)
12 public @interface MyAnnotation {
13     String value();
14 }
 1 import java.lang.annotation.Retention;
 2 import java.lang.annotation.RetentionPolicy;
 3 import java.lang.annotation.Target;
 4 
 5 import static java.lang.annotation.ElementType.*;
 6 
 7 @Target({TYPE,FIELD,METHOD,PARAMETER})
 8 @Retention(RetentionPolicy.RUNTIME)
 9 public @interface MyAnnotations {
10     MyAnnotation[] value();
11 }
 1 import org.junit.Test;
 2 import java.lang.reflect.Method;
 3 
 4 public class TestClass {
 5 
 6     @Test
 7     public void test() throws Exception {
 8         Class clazz = TestClass.class;
 9         Method method = clazz.getMethod("show");
10         MyAnnotation[] myAnnotations = method.getDeclaredAnnotationsByType(MyAnnotation.class);
11         for (MyAnnotation myAnnotation : myAnnotations) {
12             System.out.println(myAnnotation.value());
13         }
14 
15     }
16 
17     @MyAnnotation("Hello")
18     @MyAnnotation("World")
19     public void show(){
20 
21     }
22 }

输出结果:

Hello
World

JDK8中就可以这样使用,出于兼容性原因,重复注解存储在编译器自动生成的注解容器中。重复注解需要包含两个声明:

  • 重复注解必须使用@Repeatable注解标记,并指定容器类注解。如上示例中MyAnnotation注解声明了Repeatable标记并指定容器注解为MyAnnotations。
  • 容器类注解必须包含一个注解数组的value。如上示例中的:MyAnnotation[] value()。

类型注解

JDK8中,可以在类型上进行注解,以确保更强大的类型检查,JDK8并不提供类型检查框架,但它允许您编写一个类型检查框架,例如,你要确保程序中的特定变量永远不会分配一个null;你想避免抛出一个NullPointException。你可以写一个自定义插件来检查这个。然后,你将修改你的代码注解特定变量,表明它从未分配给null,变量声明如下:@NonNull String str,当你编译代码时,编译器会检测到这个警告,从而使程序在运行时不会发生错误。注JDK8并没提供具体的检测框架,只提供了该注解功能,在类型上进行注解。

 

posted @ 2018-06-19 17:35  念念就忘  阅读(1810)  评论(0编辑  收藏