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>: 消费型接口,表示一个接受单个输入参数并返回没有结果的操作。对类型为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 表达式。
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)到底是什么?
是数据渠道,用于操作数据源(集合、数组等)所生成的元素序列。“集合讲的是数据,流讲的是计算!”
注意一下三点:
- Stream 自己不会存储元素。
- Stream 不会改变源对象。相反,他们会返回一个持有结果的新 Stream。
- Stream 操作是延迟执行的。这也意味着他们会等到需要结果的时候才执行。
Stream 的操作三个步骤:
- 创建 Stream: 一个数据源(如:集合、数组),获取一个流。
- 中间操作: 一个中间操作链,对数据源的数据进行一系列处理。
- 终止操作(终端操作): 一个终止操作,执行中间操作链,并产生结果。
创建 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
重复注解
在某些特定的情况下,您希望将相同的注解应用于声明或类型用途。思考如下示例:
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