想更详细的了解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 表达式兼容的函数式接口的类型。
 import java.util.function.Function;
 public class TestLambda {
     public static void main(String[] args) {
         String str = toUpperString("abcdefg", (e) -> e.toUpperCase());
         System.out.println(str);
     }
     public static String toUpperString(String string, Function<String, String> function) {
         return function.apply(string);
     }
 }
函数式接口
只包含一个抽象方法的接口,称为函数式接口。你可以通过Lambda 表达式来创建该接口的对象。我们可以在任意函数式接口上使用 @FunctionalInterface 注解,这样做可以检查它是否是一个函数式接口,同时 javadoc 也会包含一条声明,说明这个接口是一个函数式接口。
如下所示,自定义函数式接口:
 @FunctionalInterface
 public interface MyInterface {
     public void getValue();
 }
Java 内置四大核心函数式接口
因为Lambda 表达式必须依赖函数式接口,然为了避免Lambda 表达式特意去书写函数式接口。Java 内置了如下四大核心函数式接口:
- Consumer
: 消费型接口,表示一个接受单个输入参数并返回没有结果的操作。对类型为T的对象应用操作。接口方法: void accept(T t)  - Supplier
: 供给型接口,类似一个供应商,返回一个类型为T的对象。接口方法: T get()  - Function<T, R>: 函数型接口,表示一个接受一个参数并产生结果的函数。接口方法: R apply(T t)
 - Predicate
: 断言型接口,确定类型为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
: Function<T, T>的子接口。对类型为T的对象进行一元运算,并返回T类型的结果。接口方法:T apply(T t);  - BinaryOperator
: 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 表达式。
 public class MyClass {
     Integer i;
     public MyClass() {
     }
     public MyClass(Integer i) {
         this.i = i;
     }
     @Override
     public String toString() {
         return "MyClass{" +
                 "i=" + i +
                 '}';
     }
 }
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修饰的方法,则可以在接口中进行具体实现,如下所示:
 public interface MyInterface {
     //接口中的常规方法是不能实现的。
     int getValue();
     //接口中的具体实现默认方法:getName
     default String getName(){
         return "Hello JDK8!";
     }
     //接口中的具体实现默认方法:getAge
     default int getAge(){
         return 8;
     }
 }
 public interface MyFunc {
     default String getName(){
         return "Hello MyFunc!";
     }
 }
 public class MyClass {
     public String getName(){
         return "Hello MyClass!";
     }
 }
public class SubClass extends MyClass implements MyFunc{
}
输出结果:
Hello MyClass!
上面的示例可以看到,MyFunc接口中有默认方法getName、MyClass中也有getName()方法。然SubClass对象继承MyClass对象,同时实现MyFunc接口,调用SubClass对象的getName方法,实际执行的是MyClass对象的方法。接口的默认方法实现“类优先”原则。
若一个接口中定义了一个默认方法,而另外一个父类又定义了一个同名的方法时,选择父类中的方法。如果一个父类提供了具体的实现,那么接口中具有相同名称和参数的默认方法会被忽略。
因接口可以多实现,则会出现如下示例:
 public class TestClass implements MyFunc, MyInterface {
     @Override
     public int getValue() {
         return 0;
     }
     @Override
     public String getName() {
         //因为接口可以多实现,然MyFunc接口 和 MyInterface接口 都有getName默认方法。于是需要使用一下方法进行指定调用。
 //        return MyInterface.super.getName();
         return MyFunc.super.getName();
     }
     @Override
     public int getAge() {
         return 0;
     }
 }
TestClass testClass = new TestClass();
System.out.println(testClass.getName());
输出结果:
Hello MyFunc!
上面的示例可以看出。MyFunc接口和MyInterface接口中都有getName默认方法,然TestClass同时实现以上两个接口时,必须覆盖该方法来解决冲突。
静态方法
在JDK8 中,接口中允许使用静态方法。和类一样通过接口名称点静态方法去调用,如下示例所示:
 public interface MyFunction {
     //接口中使用静态方法
     static void show(){
         System.out.println("Hello static!");
     }
 }
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
stream : Collection 接口的默认方法,返回一个顺序流。  - default Stream
parallelStream: Collection 接口的的默认放,返回一个并行了。  
同时JDK8 在Arrays类中提供许多重载的Stream()静态方法 ,可以获取数组流。如下所示,可以处理很多类型的数组。
- public static 
Stream stream(T[] array)  - public static 
Stream 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
Stream of(T t)  - public static
Stream of(T… values)  
Stream接口还提供了iterate和generate方法创建无限流。
- public static
Stream iterate(final T seed, final UnaryOperator f)  - public static
Stream generate(Supplier s)  
Stream 的中间操作
Stream 可以将多个中间操作连接起来形成一条流水线,除非流水线上触发终止操作,否则中间操作不会执行任何的处理。而在终止操作一次性全部处理,称为“惰性求值”
Stream 的中间操作有以下几种:
- 筛选与切片: 将Stream流进行筛选或截断处理。
- Stream
filter(Predicate<? super T> predicate): 接收Lambda 表达式,从流中排出某些元素;  - Stream
distinct(): 筛选,通过流所生成元素的 hashCode() 和 equals() 去除重复元素;  - Stream
limit(long maxSize): 截断流,使其元素不超过给定数量;  - Stream
skip(long n): 跳过元素,返回一个扔掉了前n个元素的流。若流中元素不足n个,则返回一个空流。与limit方法互补。  
 - Stream
 - 映射: 将Stream流映射到一个新的元素上。
Stream 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。
 Stream flatMap(Function<? super T, ? extends Stream<? extends R>> mapper): 接受一个函数作为参数,将流中的每一个值都换成另一个流,然后把所有流连接成一个流。flatMapToDouble、flatMapToInt和flatMapToLong以上差不多。只是获得具体的新流。 
 - 排序: 将Stream流进行排序处理。
- Stream
sorted(): 返回一个新流,按自然顺序排序。  - Stream
sorted(Comparator<? super T> comparator): 返回一个新流,按comparator比较器进行排序。  
 - Stream
 
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
findFirst(): 返回流的第一个元素。  - Optional
findAny(): 返回随机的一个元素。  - long count(): 返回流的总数。
 - Optional
max(Comparator<? super T> comparator): 返回流中的最大值。  - Optional
min(Comparator<? super T> comparator): 返回流中的最小值。  - void forEach(Consumer<? super T> action): 内部迭代(Stream API 使用了内部迭达。相反,使用Collection 接口需要用户做的迭代是外部迭代)。
 
 - 归约: 将流中的元素反复结合返回一个新值。(备注:map 和 reduce 的连接通常称为 map-reduce 模式,因为Google 用它来进行网络搜索而出名)
- Optional
reduce(BinaryOperator accumulator): 将流中的元素反复结合,并返回一个新值 Optional 。  - T reduce(T identity, BinaryOperator
accumulator): 将流中的元素反复结合,并返回一个新值T。  
 - Optional
 - 收集: 将流转换为其他形式,用于将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

常用的方法:
- public static 
Optional of(T value): 创建一个Optional 实例。  - public static
Optional empty(): 创建一个空的Optional 实例。  - public static 
Optional 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 Optional map(Function<? super T, ? extends U> mapper): 如果有值对其处理,并返回处理后的Optional,否则返回 Optional.empty()。
 - public Optional flatMap(Function<? super T, Optional> mapper): 与 map 类似,要求返回值必须是Optional。
 - public T get(): 获取Optional对象的值。
 
重复注解和类型注解
重复注解
在某些特定的情况下,您希望将相同的注解应用于声明或类型用途。思考如下示例:
 import java.lang.annotation.Repeatable;
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.lang.annotation.Target;
 import static java.lang.annotation.ElementType.*;
 //使用Repeatable注解指定可以重复注解,注解容器为MyAnnotations
 @Repeatable(MyAnnotations.class)
 @Target({TYPE,FIELD,METHOD,PARAMETER})
 @Retention(RetentionPolicy.RUNTIME)
 public @interface MyAnnotation {
     String value();
 }
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.lang.annotation.Target;
 import static java.lang.annotation.ElementType.*;
 @Target({TYPE,FIELD,METHOD,PARAMETER})
 @Retention(RetentionPolicy.RUNTIME)
 public @interface MyAnnotations {
     MyAnnotation[] value();
 }
 import org.junit.Test;
 import java.lang.reflect.Method;
 public class TestClass {
     @Test
     public void test() throws Exception {
         Class clazz = TestClass.class;
         Method method = clazz.getMethod("show");
         MyAnnotation[] myAnnotations = method.getDeclaredAnnotationsByType(MyAnnotation.class);
         for (MyAnnotation myAnnotation : myAnnotations) {
             System.out.println(myAnnotation.value());
         }
     }
     @MyAnnotation("Hello")
     @MyAnnotation("World")
     public void show(){
     }
 }
输出结果:
Hello
World
JDK8中就可以这样使用,出于兼容性原因,重复注解存储在编译器自动生成的注解容器中。重复注解需要包含两个声明:
- 重复注解必须使用@Repeatable注解标记,并指定容器类注解。如上示例中MyAnnotation注解声明了Repeatable标记并指定容器注解为MyAnnotations。
 - 容器类注解必须包含一个注解数组的value。如上示例中的:MyAnnotation[] value()。
 
类型注解
JDK8中,可以在类型上进行注解,以确保更强大的类型检查,JDK8并不提供类型检查框架,但它允许您编写一个类型检查框架,例如,你要确保程序中的特定变量永远不会分配一个null;你想避免抛出一个NullPointException。你可以写一个自定义插件来检查这个。然后,你将修改你的代码注解特定变量,表明它从未分配给null,变量声明如下:@NonNull String str,当你编译代码时,编译器会检测到这个警告,从而使程序在运行时不会发生错误。注JDK8并没提供具体的检测框架,只提供了该注解功能,在类型上进行注解。