1. 了解Open JDK 和 Oracle JDK
2. JDK 8新特性
Lambda 表达式
集合之 Stream流式操作
接口的增强
并行数组排序
Optional 中避免Null检查
新的时间和日期 API
可重复注解
1.Lambda 表达式介绍
1.1使用匿名内部类存在的问题
当需要启动一个线程去完成任务时,通常会通过 Runnable 接口来定义任务内容,并使用 Thread 类来启动该线程。
传统写法,代码如下:
public class Demo01LambdaIntro { public static void main(String[] args) { new Thread(new Runnable() { @Override public void run() { System.out.println("新线程任务执行!"); } }).start(); } }
由于面向对象的语法要求,首先创建一个 Runnable 接口的匿名内部类对象来指定线程要执行的任务内容,再将其交给一个线程来启动。
代码分析:对于 Runnable 的匿名内部类用法,可以分析出几点内容:Thread 类需要 Runnable 接口作为参数,
其中的抽象 run 方法是用来指定线程任务内容的核心为了指定 run 的方法体,
不得不需要 Runnable 接口的实现类为了省去定义一个 Runnable 实现类的麻烦,
不得不使用匿名内部类必须覆盖重写抽象 run 方法,所以方法名称、方法参数、方法返回值不得不再写一遍,且不能写错而实际上,
似乎只有方法体才是关键所在
1.2Lambda体验
Lambda是一个匿名函数,可以理解为一段可以传递的代码。
Lambda表达式写法,代码如下:
借助Java 8的全新语法,上述 Runnable 接口的匿名内部类写法可以通过更简单的Lambda表达式达到相同的效果
public class Demo01LambdaIntro {
public static void main(String[] args) {
new Thread(() -> System.out.println("新线程任务执行!")).start(); // 启动线程
}
}
这段代码和刚才的执行效果是完全一样的,可以在JDK 8或更高的编译级别下通过。从代码的语义中可以看出:我们
启动了一个线程,而线程任务的内容以一种更加简洁的形式被指定。
我们只需要将要执行的代码放到一个Lambda表达式中,不需要定义类,不需要创建对象。
1.3Lambda的优点
简化匿名内部类的使用,语法更加简单。
小结:了解了匿名内部类语法冗余,体验了Lambda表达式的使用,发现Lmabda是简化匿名内部类的简写
1.4Lambda 的标准格式
Lambda省去面向对象的条条框框,Lambda的标准格式格式由3个部分组成:
( 参数类型 参数名称) -> {
代码体;
}
格式说明:
( 参数类型 参数名称):参数列表
{ 代码体;}:方法体
- > :箭头,分隔参数列表和方法体
Lambda与方法的对比
匿名内部类
public void run() {
System.out.println("aa");
}
Lambda
() -> System.out.println("bb!")
1.4.1练习无参数无返回值的Lambda
interface Swimmable { public abstract void swimming(); }
package com.itheima.demo01lambda; public class Demo02LambdaUse {
public static void main(String[] args) {
goSwimming(new Swimmable() {
@Override
public void swimming() {
System.out.println("匿名内部类游泳");
}
});
goSwimming(() -> {
System.out.println("Lambda游泳");
});
}
public static void goSwimming(Swimmable swimmable) {
swimmable.swimming();
}
}
1.4.2练习有参数有返回值的Lambda
下面举例演示 java.util.Comparator<T> 接口的使用场景代码,其中的抽象方法定义为:
public abstract int compare(T o1, T o2);
当需要对一个对象集合进行排序时, Collections.sort 方法需要一个 Comparator 接口实例来指定排序的规则。
传统写法
如果使用传统的代码对 ArrayList 集合进行排序,写法如下:
public class Person { private String name; private int age; private int height; // 省略其他 }
package com.itheima.demo01lambda; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Comparator; public class Demo03LambdaUse { public static void main(String[] args) { ArrayList<Person> persons = new ArrayList<>(); persons.add(new Person("刘德华", 58, 174)); persons.add(new Person("张学友", 58, 176)); persons.add(new Person("刘德华", 54, 171)); persons.add(new Person("黎明", 53, 178)); Collections.sort(persons, new Comparator<Person>() { @Override public int compare(Person o1, Person o2) { return o1.getAge() - o2.getAge(); } }); for (Person person : persons) { System.out.println(person); } } }
这种做法在面向对象的思想中,似乎也是“理所当然”的。其中 Comparator 接口的实例(使用了匿名内部类)代表 了“按照年龄从小到大”的排序规则。
Lambda写法
package com.itheima.demo01lambda; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Comparator; public class Demo03LambdaUse {
public static void main(String[] args) {
ArrayList<Person> persons = new ArrayList<>();
persons.add(new Person("刘德华", 58, 174));
persons.add(new Person("张学友", 58, 176));
persons.add(new Person("刘德华", 54, 171));
persons.add(new Person("黎明", 53, 178)); Collections.sort(persons, (o1, o2) -> {
return o1.getAge() - o2.getAge();
}); for (Person person : persons) {
System.out.println(person);
}
System.out.println("-----------------"); List<Integer> list = Arrays.asList(11, 22, 33, 44); list.forEach(new Consumer<Integer>() {
@Override
public void accept(Integer integer) {
System.out.println(integer);
}
});
System.out.println("-----------------");
list.forEach((s) -> { System.out.println(s); });
} }
1.4.3Lambda表达式的标准格式
(参数列表) -> { 方法体; }
1.5Lambda的实现原理
我们现在已经会使用Lambda表达式了。现在同学们肯定很好奇Lambda是如何实现的,现在我们就来探究Lambda 表达式的底层实现原理。
@FunctionalInterface
interface Swimmable {
public abstract void swimming();
} public class Demo04LambdaImpl {
public static void main(String[] args) {
goSwimming(new Swimmable() {
@Override
public void swimming() {
System.out.println("使用匿名内部类实现游泳");
}
});
} public static void goSwimming(Swimmable swimmable) {
swimmable.swimming();
}
}
我们可以看到匿名内部类会在编译后产生一个类: Demo04LambdaImpl$1.class
使用 XJad反编译这个类,得到如下代码:
package com.itheima.demo01lambda; import java.io.PrintStream; // Referenced classes of package com.itheima.demo01lambda: // Swimmable, Demo04LambdaImpl static class Demo04LambdaImpl$1 implements Swimmable { public void swimming() { System.out.println("使用匿名内部类实现游泳"); } Demo04LambdaImpl$1() { } }
我们再来看看Lambda的效果,修改代码如下
public class Demo04LambdaImpl { public static void main(String[] args) { goSwimming(() -> { System.out.println("Lambda游泳"); }); } public static void goSwimming(Swimmable swimmable) { swimmable.swimming(); } }
运行程序,控制台可以得到预期的结果,但是并没有出现一个新的类,也就是说Lambda并没有在编译的时候产生一
个新的类。使用XJad对这个类进行反编译,发现XJad报错。使用了Lambda后XJad反编译工具无法反编译。我们使用
JDK自带的一个工具: javap ,对字节码进行反汇编,查看字节码指令。
在DOS命令行输入:
javap -c -p 文件名.class
-c:表示对代码进行反汇编
-p:显示所有类和成员
反汇编后效果如下:
C:Users>javap -c -p Demo04LambdaImpl.class Compiled from "Demo04LambdaImpl.java" public class com.itheima.demo01lambda.Demo04LambdaImpl { public com.itheima.demo01lambda.Demo04LambdaImpl(); Code: 0: aload_0 1: invokespecial #1 // Method java/lang/Object."<init>":()V 4: return public static void main(java.lang.String[]); Code: 0: invokedynamic #2, 0 // InvokeDynamic #0:swimming:()Lcom/itheima/demo01lambda/Swimmable; 5: invokestatic #3 // Method goSwimming:(Lcom/itheima/demo01lambda/Swimmable;)V 8: return public static void goSwimming(com.itheima.demo01lambda.Swimmable); Code: 0: aload_0 1: invokeinterface #4, 1 // InterfaceMethodcom/itheima/demo01lambda/Swimmable.swimming:()V 6: return private static void lambda$main$0(); Code: 0: getstatic #5 // Field java/lang/System.out:Ljava/io/PrintStream; 3: ldc #6 // String Lambda游泳 5: invokevirtual #7 // Method java/io/PrintStream.println: (Ljava/lang/String;)V 8: return }
可以看到在类中多出了一个私有的静态方法 lambda$main$0 。这个方法里面放的是什么内容呢?我们通过断点调试
可以确认 lambda$main$0 里面放的就是Lambda中的内容,我们可以这么理解 lambda$main$0 方法:
public class Demo04LambdaImpl { public static void main(String[] args) { ... } private static void lambda$main$0() { System.out.println("Lambda游泳"); } }
关于这个方法 lambda$main$0 的命名:以lambda开头,因为是在main()函数里使用了lambda表达式,所以带有
$main表示,因为是第一个,所以$0。
如何调用这个方法呢?其实Lambda在运行的时候会生成一个内部类,为了验证是否生成内部类,可以在运行时加
上 - Djdk.internal.lambda.dumpProxyClasses ,加上这个参数后,运行时会将生成的内部类class码输出到一个文
件中。使用java命令如下:
java -Djdk.internal.lambda.dumpProxyClasses 要运行的包名.类名
eg:C:Users>java -Djdk.internal.lambda.dumpProxyClasses com.itheima.demo01lambda.Demo04LambdaImpl
匿名内部类在编译的时候会一个 class文件
Lambda在程序运行的时候形成一个类
1. 在类中新增一个方法,这个方法的方法体就是Lambda表达式中的代码
2. 还会形成一个匿名内部类,实现接口,重写抽象方法
3. 在接口的重写方法中会调用新生成的方法.
1.6Lambda 省略格式
在Lambda标准格式的基础上,使用省略写法的规则为:
1. 小括号内参数的类型可以省略
2. 如果小括号内有且仅有一个参数,则小括号可以省略
3. 如果大括号内有且仅有一个语句,可以同时省略大括号、return关键字及语句分号
(int a) -> { return new Person(); }
省略后
a -> new Person()
1.7Lambda 的前提条件
Lambda的语法非常简洁,但是Lambda表达式不是随便使用的,使用时有几个条件要特别注意:
1. 方法的参数或局部变量类型必须为接口才能使用Lambda
2. 接口中有且仅有一个抽象方法
函数式接口
函数式接口在Java中是指:有且仅有一个抽象方法的接口。
函数式接口,即适用于函数式编程场景的接口。而Java中的函数式编程体现就是Lambda,所以函数式接口就是可以
适用于Lambda使用的接口。只有确保接口中有且仅有一个抽象方法,Java中的Lambda才能顺利地进行推导。
FunctionalInterface注解
与 @Override 注解的作用类似,Java 8中专门为函数式接口引入了一个新的注解: @FunctionalInterface 。该注
解可用于一个接口的定义上
@FunctionalInterface public interface Operator { void myMethod(); }
一旦使用该注解来定义接口,编译器将会强制检查该接口是否确实有且仅有一个抽象方法,否则将会报错。不过,即
使不使用该注解,只要满足函数式接口的定义,这仍然是一个函数式接口,使用起来都一样。
1.8Lambda 和匿名内部类对比
1. 所需的类型不一样
匿名内部类,需要的类型可以是类,抽象类,接口
Lambda表达式,需要的类型必须是接口
2. 抽象方法的数量不一样
匿名内部类所需的接口中抽象方法的数量随意
Lambda表达式所需的接口只能有一个抽象方法
3. 实现原理不同
匿名内部类是在编译后会形成class
Lambda表达式是在程序运行的时候动态生成class
小结
当接口中只有一个抽象方法时,建议使用Lambda表达式,其他其他情况还是需要使用匿名内部类
2.JDK 8 接口新增的两个方法
JDK 8以前的接口
interface 接口名 { 静态常量; 抽象方法; }
JDK 8以前的接口
interface 接口名 { 静态常量; 抽象方法; 默认方法; //接口中的默认方法实现类不必重写,可以直接使用,实现类也可以根据需要重写。这样就方便接口的扩展。 静态方法; }
2.1接口默认方法的定义格式
interface 接口名 { 修饰符 default 返回值类型 方法名() { 代码; } }
接口默认方法的使用
方式一:实现类直接调用接口默认方法
方式二:实现类重写接口默认方法
2.2接口静态方法的定义格式
interface 接口名 { 修饰符 static 返回值类型 方法名() { 代码; } }
接口静态方法的使用
直接使用接口名调用即可:接口名.静态方法名();
接口默认方法和静态方法的区别
1. 默认方法通过实例调用,静态方法通过接口名调用。
2. 默认方法可以被继承,实现类可以直接使用接口默认方法,也可以重写接口默认方法。
3. 静态方法不能被继承,实现类不能重写接口静态方法,只能使用接口名调用
3.常用内置函数式接口
内置函数式接口来由来
我们知道使用Lambda表达式的前提是需要有函数式接口。而Lambda使用时不关心接口名,抽象方法名,只关心抽
象方法的参数列表和返回值类型。因此为了让我们使用Lambda方便,JDK提供了大量常用的函数式接口。
常用内置函数式接口介绍
它们主要在 java.util.function 包中。下面是最常用的几个接口
1. Supplier接口
@FunctionalInterface public interface Supplier<T> { public abstract T get(); }
2. Consumer接口
@FunctionalInterface public interface Consumer<T> { public abstract void accept(T t); }
3. Function接口
@FunctionalInterface public interface Function<T, R> { public abstract R apply(T t); }
4. Predicate接口
@FunctionalInterface public interface Predicate<T> { public abstract boolean test(T t); } Predicate接口用于做判断,返回boolean类型的值
4.Stream 流介绍
集合处理数据的弊端
当我们需要对集合中的元素进行操作的时候,除了必需的添加、删除、获取外,最典型的就是集合遍历。我们来体验
集合操作数据的弊端,需求如下:
一个 ArrayList集合中存储有以下数据:张无忌,周芷若,赵敏,张强,张三丰
需求:1.拿到所有姓张的 2.拿到名字长度为3个字的 3.打印这些数据
代码如下
public static void main(String[] args) { // 一个ArrayList集合中存储有以下数据:张无忌,周芷若,赵敏,张强,张三丰 // 需求:1.拿到所有姓张的 2.拿到名字长度为3个字的 3.打印这些数据 ArrayList<String> list = new ArrayList<>(); Collections.addAll(list, "张无忌", "周芷若", "赵敏", "张强", "张三丰"); // 1.拿到所有姓张的 ArrayList<String> zhangList = new ArrayList<>(); // {"张无忌", "张强", "张三丰"} for (String name : list) { if (name.startsWith("张")) { zhangList.add(name); } } // 2.拿到名字长度为3个字的 ArrayList<String> threeList = new ArrayList<>(); // {"张无忌", "张三丰"} for (String name : zhangList) { if (name.length() == 3) { threeList.add(name); } } // 3.打印这些数据 for (String name : threeList) { System.out.println(name); } }
循环遍历的弊端
这段代码中含有三个循环,每一个作用不同:
1. 首先筛选所有姓张的人;
2. 然后筛选名字有三个字的人;
3. 最后进行对结果进行打印输出。
每当我们需要对集合中的元素进行操作的时候,总是需要进行循环、循环、再循环。这是理所当然的么?不是。循环
是做事情的方式,而不是目的。每个需求都要循环一次,还要搞一个新集合来装数据,如果希望再次遍历,只能再使
用另一个循环从头开始。
那Stream能给我们带来怎样更加优雅的写法呢?
Stream的更优写法
public class Demo03StreamFilter { public static void main(String[] args) { List<String> list = new ArrayList<>(); list.add("张无忌"); list.add("周芷若"); list.add("赵敏"); list.add("张强"); list.add("张三丰"); list.stream() .filter(s -> s.startsWith("张")) .filter(s -> s.length() == 3) .forEach(System.out::println); } }
直接阅读代码的字面意思即可完美展示无关逻辑方式的语义:获取流、过滤姓张、过滤长度为3、逐一打印。我们真
正要做的事情内容被更好地体现在代码中。
4.1获取 Stream流的两种方式
获取一个流非常简单,有以下几种常用的方式:
所有的 Collection 集合都可以通过 stream 默认方法获取流;
Stream 接口的静态方法 of 可以获取数组对应的流。
方式1 : 根据Collection获取流
首先, java.util.Collection 接口中加入了default方法 stream 用来获取流,所以其所有实现类均可获取流。
public interface Collection { default Stream<E> stream() }
import java.util.*; import java.util.stream.Stream; public class Demo04GetStream { public static void main(String[] args) { // 集合获取流 // Collection接口中的方法: default Stream<E> stream() 获取流 List<String> list = new ArrayList<>(); // ... Stream<String> stream1 = list.stream(); Set<String> set = new HashSet<>(); // ... Stream<String> stream2 = set.stream(); Vector<String> vector = new Vector<>(); // ... Stream<String> stream3 = vector.stream(); } }
java.util.Map 接口不是 Collection 的子接口,所以获取对应的流需要分key、value或entry等情况:
import java.util.HashMap; import java.util.Map; import java.util.stream.Stream; public class Demo05GetStream { public static void main(String[] args) { // Map获取流 Map<String, String> map = new HashMap<>(); // ... Stream<String> keyStream = map.keySet().stream(); Stream<String> valueStream = map.values().stream(); Stream<Map.Entry<String, String>> entryStream = map.entrySet().stream(); } }
方式2 : Stream中的静态方法of获取流
由于数组对象不可能添加默认方法,所以 Stream 接口中提供了静态方法 of ,使用很简单:
import java.util.stream.Stream; public class Demo06GetStream { public static void main(String[] args) { // Stream中的静态方法: static Stream of(T... values) Stream<String> stream6 = Stream.of("aa", "bb", "cc"); String[] arr = {"aa", "bb", "cc"}; Stream<String> stream7 = Stream.of(arr); Integer[] arr2 = {11, 22, 33}; Stream<Integer> stream8 = Stream.of(arr2); // 注意:基本数据类型的数组不行 int[] arr3 = {11, 22, 33}; Stream<int[]> stream9 = Stream.of(arr3); } }
备注: of 方法的参数其实是一个可变参数,所以支持数组。
4.2Stream常用方法
终结方法 :返回值类型不再是 Stream 类型的方法,不再支持链式调用。本小节中,终结方法包括 count 和
forEach 方法。
非终结方法 :返回值类型仍然是 Stream 类型的方法,支持链式调用。(除了终结方法外,其余方法均为非终结
方法。)
Stream注意事项(重要)
1. Stream只能操作一次
2. Stream方法返回的是新的流
3. Stream不调用终结方法,中间的操作不会执行
Stream 流的forEach方法
forEach 用来遍历流中的数据
void forEach(Consumer<? super T> action);
该方法接收一个 Consumer 接口函数,会将每一个流元素交给该函数进行处理。例如:
@Test public void testForEach() { List<String> one = new ArrayList<>(); Collections.addAll(one, "迪丽热巴", "宋远桥", "苏星河", "老子", "庄子", "孙子"); /*one.stream().forEach((String s) -> { System.out.println(s); });*/ // 简写 // one.stream().forEach(s -> System.out.println(s)); one.stream().forEach(System.out::println); }
5.学习 JDK 8新的日期和时间 API
旧版日期时间 API 存在的问题
1. 设计很差: 在java.util和java.sql的包中都有日期类,java.util.Date同时包含日期和时间,而java.sql.Date仅包
含日期。此外用于格式化和解析的类在java.text包中定义。
2. 非线程安全:java.util.Date 是非线程安全的,所有的日期类都是可变的,这是Java日期类最大的问题之一。
3. 时区处理麻烦:日期类并不提供国际化,没有时区支持,因此Java引入了java.util.Calendar和
java.util.TimeZone类,但他们同样存在上述所有的问题。
新日期时间 API介绍
JDK 8中增加了一套全新的日期时间API,这套API设计合理,是线程安全的。新的日期及时间API位于 java.time 包中,下面是一些关键类。
LocalDate :表示日期,包含年月日,格式为 2019-10-16
LocalTime :表示时间,包含时分秒,格式为 16:38:54.158549300
LocalDateTime :表示日期时间,包含年月日,时分秒,格式为 2018-09-06T15:33:56.750
DateTimeFormatter :日期时间格式化类。
Instant:时间戳,表示一个特定的时间瞬间。
Duration:用于计算2个时间(LocalTime,时分秒)的距离
Period:用于计算2个日期(LocalDate,年月日)的距离
ZonedDateTime :包含时区的时间
Java中使用的历法是ISO 8601日历系统,它是世界民用历法,也就是我们所说的公历。平年有365天,闰年是366天。此外Java 8还提供了4套其他历法,分别是:
ThaiBuddhistDate :泰国佛教历
MinguoDate :中华民国历
JapaneseDate :日本历
HijrahDate :伊斯兰历
5.1JDK 8 的日期和时间类
LocalDate 、LocalTime、LocalDateTime类的实例是不可变的对象,分别表示使用 ISO -8601 日历系统的日期、时
间、日期和时间。它们提供了简单的日期或时间,并不包含当前的时间信息,也不包含与时区相关的信息
// LocalDate: 获取日期时间的信息。格式为 2019-10-16 @Test public void test01() { // 创建指定日期 LocalDate fj = LocalDate.of(1985, 9, 23); System.out.println("fj = " + fj); // 1985-09-23 // 得到当前日期 LocalDate nowDate = LocalDate.now(); System.out.println("nowDate = " + nowDate); // 2019-10-16 // 获取日期信息 System.out.println("年: " + nowDate.getYear()); System.out.println("月: " + nowDate.getMonthValue()); System.out.println("日: " + nowDate.getDayOfMonth()); System.out.println("星期: " + nowDate.getDayOfWeek()); } // LocalTime类: 获取时间信息。格式为 16:38:54.158549300 @Test public void test02() { // 得到指定的时间 LocalTime time = LocalTime.of(12,15, 28, 129_900_000); System.out.println("time = " + time); // 得到当前时间 LocalTime nowTime = LocalTime.now(); System.out.println("nowTime = " + nowTime); // 获取时间信息 System.out.println("小时: " + nowTime.getHour()); System.out.println("分钟: " + nowTime.getMinute()); System.out.println("秒: " + nowTime.getSecond()); System.out.println("纳秒: " + nowTime.getNano()); } // LocalDateTime类: 获取日期时间信息。格式为 2018-09-06T15:33:56.750 @Test public void test03() { LocalDateTime fj = LocalDateTime.of(1985, 9, 23, 9, 10, 20); System.out.println("fj = " + fj); // 1985-09-23T09:10:20 // 得到当前日期时间 LocalDateTime now = LocalDateTime.now(); System.out.println("now = " + now); // 2019-10-16T16:42:24.497896800 System.out.println(now.getYear()); System.out.println(now.getMonthValue()); System.out.println(now.getDayOfMonth());
System.out.println(now.getHour());
System.out.println(now.getMinute());
System.out.println(now.getSecond());
System.out.println(now.getNano());
}
对日期时间的修改,对已存在的LocalDate对象,创建它的修改版,最简单的方式是使用withAttribute方法。
withAttribute方法会创建对象的一个副本,并按照需要修改它的属性。以下所有的方法都返回了一个修改属性的对
象,他们不会影响原来的对象。
// LocalDateTime 类: 对日期时间的修改 @Test public void test05() { LocalDateTime now = LocalDateTime.now(); System.out.println("now = " + now); // 修改日期时间 LocalDateTime setYear = now.withYear(2078); System.out.println("修改年份: " + setYear); System.out.println("now == setYear: " + (now == setYear)); System.out.println("修改月份: " + now.withMonth(6)); System.out.println("修改小时: " + now.withHour(9)); System.out.println("修改分钟: " + now.withMinute(11)); // 再当前对象的基础上加上或减去指定的时间 LocalDateTime localDateTime = now.plusDays(5); System.out.println("5天后: " + localDateTime); System.out.println("now == localDateTime: " + (now == localDateTime)); System.out.println("10年后: " + now.plusYears(10)); System.out.println("20月后: " + now.plusMonths(20)); System.out.println("20年前: " + now.minusYears(20)); System.out.println("5月前: " + now.minusMonths(5)); System.out.println("100天前: " + now.minusDays(100)); }
日期时间的比较
// 日期时间的比较 @Test public void test06() { // 在JDK8中,LocalDate类中使用isBefore()、isAfter()、equals()方法来比较两个日期,可直接进行比较。 LocalDate now = LocalDate.now(); LocalDate date = LocalDate.of(2018, 8, 8); System.out.println(now.isBefore(date)); // false System.out.println(now.isAfter(date)); // true }
6.JDK 8重复注解与类型注解
重复注解的使用
自从Java 5中引入 注解 以来,注解开始变得非常流行,并在各个框架和项目中被广泛使用。不过注解有一个很大的限
制是:在同一个地方不能多次使用同一个注解。JDK 8引入了重复注解的概念,允许在同一个地方多次使用同一个注
解。在JDK 8中使用@Repeatable注解定义重复注解。
重复注解的使用步骤:
1. 定义重复的注解容器注解
@Retention(RetentionPolicy.RUNTIME) @interface MyTests { MyTest[] value(); }
2. 定义一个可以重复的注解
@Retention(RetentionPolicy.RUNTIME) @Repeatable(MyTests.class) @interface MyTest { String value(); }
3. 配置多个重复的注解
@MyTest("tbc") @MyTest("tba") @MyTest("tba") public class Demo01 { @MyTest("mbc") @MyTest("mba") public void test() throws NoSuchMethodException { } }
4. 解析得到指定注解
// 3. 配置多个重复的注解 @MyTest("tbc") @MyTest("tba") @MyTest("tba")
public class Demo01 {
@Test
@MyTest("mbc")
@MyTest("mba")
public void test() throws NoSuchMethodException {
// 4.解析得到类上的指定注解
MyTest[] tests = Demo01.class.getAnnotationsByType(MyTest.class);
for (MyTest test : tests) {
System.out.println(test.value());
}
// 得到方法上的指定注解
Annotation[] tests1 =
Demo01.class.getMethod("test").getAnnotationsByType(MyTest.class);
for (Annotation annotation : tests1) {
System.out.println("annotation = " + annotation);
}
}
}
类型注解的使用
JDK 8为@Target元注解新增了两种类型: TYPE_PARAMETER , TYPE_USE 。
TYPE_PARAMETER :表示该注解能写在类型参数的声明语句中。 类型参数声明如: <T> 、
TYPE_USE :表示注解可以再任何用到类型的地方使用。
TYPE_PARAMETER的使用
@Target(ElementType.TYPE_PARAMETER) @interface TyptParam { }
public class Demo02<@TyptParam T> {
public static void main( String[] args) {
}
public <@TyptParam E> void test( String a) {
}
}
TYPE_USE的使用
@Target(ElementType.TYPE_USE) @interface NotNull { }
public class Demo02<@TyptParam T extends String> {
private @NotNull int a = 10;
public static void main(@NotNull String[] args) {
@NotNull int x = 1;
@NotNull String s = new @NotNull String();
}
public <@TyptParam E> void test( String a) {
}
}