一、Lambda表达式
Lambda表达式引入了一个新的操作符" -> "。Lambda表达式将函数当成参数传递给某个方法,或是把代码本身当作数据处理。
Lambda表达式分为两部分:
() -> { //执行的功能 }
左侧:Lambda表达式的参数列表
右侧:Lambda表达式中所需要执行的功能
1、Lambda表达式格式
下面讲解Lambda的写法:
- Lambda表达式参数列表的数据类型可以省略不写,JVM编译器能通过上下文推断出来
void test(){
(x,y) -> { //语句 }
(String x,String y) -> { //语句 }
}
上面两种写法时一样的。
- 无参数、无返回值
() -> { //语句 }
- 有一个参数,无返回值
当只有一个参数时,小括号可以不写。
(x) -> { //语句 }
x -> { //语句 }
上面两句等价。
- 有两个参数或多个参数,有返回值
当Lambda表达式中只有一条语句时,则大括号和return
都可以省略。
(x,y) -> x + y;
当Lambda表达式中有多条语句时,需加上大括号和return
。
(x,y) -> {
int i = x + y;
System.out.println(i);
return i;
}
2、Lambda表达式使用
void test(){
//不使用Lambda表达式
Runnable r = new Runnable() {
@Override
public void run() {
System.out.println("不使用Lambda表达式方式--->hello");
}
};
r.run;
//使用Lambda表达式
Runnable runnable = () -> System.out.println("使用Lambda表达式方式--->hello");
runnable.run;
}
上面的例子介绍了使用Lambda表达式和不使用时的区别。
3、Lambda表达式需要函数式接口的支持
- 什么是函数式接口
函数式接口:接口中只有一个抽象方法的接口,称为函数式接口。可以使用注解@FunctionalInterface标注,此注解可以检查接口是否是函数式接口,此注解可以加也可以不加。
java中给我们提供了一些函数式接口,我们也可以自定义函数式接口,下面先举个自定义函数式接口的例子,接着再讲解java给我们提供的部分函数式接口。
/**自定义函数式接口*/
@FunctionalInterface
public interface HandleData {
public T handle(T t);
}
下面是自定义函数接口的使用:
需求:计算数组中元素的和,正常情况步骤如下:
int[] ints = {1,2};
HandleData h = new HandleData() {
@Override
public Object handle(int[] ints) {
return ints[0] + ints[1];
}
};
//调用handle方法,将数组ints传进去,获得计算结果
int result = (int) h.handle(ints);
接下来我们来看看使用Lambda表达式的写法:
int[] ints = {1,2};
HandleData h = (arr) -> arr[0] + arr[1];
int result = (int) h.handle(ints);
上面代码中的(arr) -> arr[0] + arr[1];
其实就是重写的handle(int[] ints)
方法。是不是用Lambda表达式更简洁一些,但是需要注意的是Lambda表达式需要函数式接口的支持。
- java给我们提供的部分函数式接口:
Function
:函数型接口.
R apply(T t);
T作为输入,返回的R作为输出,输入和返回的类型可以一致,T、R可以不一致。
int i = 0;
//如果需要处理的代码是多行,就需要加大括号
Function fun = (x) -> x + 1;
int result = fun.apply(i);
Predicate
:判断型接口。
boolean test(T t);
T作为输入,返回boolean值的输出。
Consumer
:消费型接口。 void accpet(T t);
T作为输入没有输出。
Supplier
:供给型接口。 T get();
没有输入,T作为输出。
BinaryOperator
:二元运算型接口。 R apply(T t, U u);
两个T作为输入,T同样是输出,是BiFunction的子接口。
UnaryOperator
:一元运算型接口。 T apply(T t);
是Function的变种,输入输出者都是T,与Function区别是,UnaryOperator输入和返回的类型一致,在此种情况下可以使用UnaryOperator替换Function。
4、方法引用和构造器引用
方法引用:若Lambda体中的功能有方法以及实现了,我们可以使用"方法引用",可以理解为方法引用是Lambda表达式的另外一种表现形式
使用条件:方法引用所引用的方法的参数列表和返回值必须和函数式接口中抽象方法的参数列表和返回值完全一致。
语法格式:主要有三种
对象::实例方法名 ---> Object::method
类名::静态方法名 ---> ClassName::static method
类名::实例方法名--->ClassName::method
注意:
类::静态方法名
与类::实例方法名
的区别:
类::实例方法名
方式中Lambda的第一个参数是实例方法的调用者,例如:x,第二个参数(或无参)是实例方法的参数,例如:y (参见下面ClassName::method中的代码所示)。
下面看代码来看如何使用:
- Object::method
Employee emp = new Employee("name",18);
//获取Employee中的age,因为getAge()方法没有输入只有输出,我们可以选择供给型接口Supplier供给型接口。接口方法:T get();Employee类中获取年龄方法:int getAge();
Supplier sup = () -> emp.getAge();
System.out.println("Lambda表达式方式:" + sup.get());
//emp对象::getAge
Supplier supplier = emp::getAge;
System.out.println("方法引用方式:" + supplier.get());
- ClassName::static method
//Integer类的compare方法
Comparator c = (x,y) -> Integer.compare(x,y);
System.out.println("Lambda表达式方式:" + c.compare(1,2));
//Integer类名::compare
Comparator com = Integer::compare;
System.out.println("方法引用方式:" + com.compare(1,2));
- ClassName::method
BiPredicate b = (x, y) -> x.equals(y);
System.out.println("Lambda表达式方式:" + b.test("a","b"));
BiPredicate bip = String::equals;
System.out.println("方法引用方式:" + bip.test("a","b"));
构造器引用:构造器能与函数式接口中的方法相兼容。
语法格式:
- 类名::new--->ClassName::new
使用条件:构造器参数列表与函数式接口中抽象方法的参数列表一致。
//默认无参构造器
Supplier s = () -> new Employee();
System.out.println("Lambda表达式方式:" + s.get());
Supplier sup = Employee::new;
System.out.println("构造器引用方式:" + sup.get());
//有参数构造器,也可以使用BiFunction或是自己写函数式接口来实现两个或多个构造器引用
Function f = (x) -> new Employee(x);
System.out.println("Lambda表达式方式:" + f.apply("z"));
Function fun = Employee::new;
System.out.println("构造器引用方式:" + fun.apply("z"));
//数组引用
Function a = (x) -> new String[x];
System.out.println("Lambda表达式方式:" + a.apply(10).length);
Function arr = String[]::new;
System.out.println("数组引用方式:" + arr.apply(10).length);
二、Stream流
1、Stream流是什么?
是数据渠道,用于操作数据源(集合、数组等)所生成的元素序列。
- Stream不会存储元素。
- Stream不会改变源对象。它会返回一个持有结果的新Stream。
- Stream操作是延迟执行的。它会等到需要结果的时候才执行。
2、Stream的操作步骤
- 创建Stream:从一个数据源、集合、数组中获取流。
- 中间操作:一个操作的中间链,对数据源的数据进行操作。
- 终止操作(终端操作):一个终止操作,执行中间操作链,并产生结果。
3、创建Stream流的方式
-
通过Collection系列集合提供的串行流stream()方法或并行流parallelStream()方法进行创建
List
list = new ArrayList<>(); Stream stream = list.stream(); Stream parallelStream = list.parallelStream(); -
通过Arrays中的静态方法stream()方法
Employee[] emps = new Employee[10]; Stream
stream = Arrays.stream(emps); -
通过Stream接口中的静态方法of()
Stream
emps1 = Stream.of(emps); -
通过Stream提供的iterate()和generate()创建无限流
-
Stream
iterate(final T seed, final UnaryOperator f) T seed:初始元素。
UnaryOperator
f:一元运算型接口,输入和输出者都需要一样,对seed进行运算。 方法含义:返回一个无限有序的流,以seed为种子,以f为迭代方法,产生的一个由seed,f(seed),f(f(seed))等组成的Stream,也就是位置n的元素,是由位置n-1的元素通过f方法产生的。
Stream
stream = Stream.iterate(0, (x) -> x + 2); stream.forEach(System.out::println); //以0为基础,以2递增,一直 -
Stream
generate(Supplier s) Supplier
s:供给型接口,没有输入,T作为输出 方法含义:返回一个无限无序的流,其中每个元素都由Supplier生成,适用于生成恒定元素流、随机元素流等。
Stream
stream = Stream.generate(() -> Math.random()); stream.forEach(System.out::println); -
4、中间操作-筛选切片
filter(Predicate p)
,limit(long maxSize)
,skip(long n)
,distinct()
,下面介绍这四个方法:
//class Employee {name,age,salary}
List list = Arrays.asList(new Employee("emp1",1,1000.00),new Employee("emp2",2,2000.00),new Employee("emp3",3,3000.00),new Employee("emp4",4,4000.00),new Employee("emp4",4,4000.00),
);
- filter(Predicate p):接收Lambda,从流中排除某些元素。
Stream stream = empList.stream()
.filter((e) -> {
return e.getAge() > 2;
});
//只会显示年龄大于2的
- limit(long maxSize):截断流,使其元素不超过给定数量。
Stream stream = empList.stream()
.limit(2);
//只会得到前两条
- skip(long n):跳过元素,返回一个扔掉了前n个元素的流。若流中元素不足n个,则返回一个空流。与limit(n)互补
Stream stream = empList.stream()
.skip(2);
//只会得到后两条
- distinct():筛选,通过流所生成元素的hashCode()和equals()去除重复元素
Stream stream = empList.stream()
.distinct();
//会去除掉最后一个new Employee("emp4",4,4000.00)的数据
distinct()
方法是根据元素的hashCode()和equals()的规则来判断元素是否重复的,所以如果有需求就重写这两个方法。
5、中间操作-映射
map(Function f)
,mapToDouble(ToDoubleFunction f)
,mapToInt(ToIntFunction f)
,mapToLong(ToLongFunction f)
,flatMap(Function f)
,下面介绍这五个方法:
- map(Function f):接收一个函数作为参数,该函数会被应用到每个元素上,并将其映射成一个新的元素
//class StreamTest03
List list = Arrays.asList("aa","bb");
list.stream()
.map((str) -> str.toUpperCase())
.forEach(System.out::println);
//结果:AA BB
//
Stream> characterStream = list.stream()
.map(StreamTest03::filterCharacter);//结果:a a b b
//如何遍历characterStream,Stream>里的数据:{{a,a},{b,b}}
characterStream.forEach( (stream) -> {
stream.forEach(System.out::println);
});
//将字符串拆成char然后存到list的方法
public static Stream filterCharacter(String str){
List list = new ArrayList<>();
for (Character c : str.toCharArray()) {
list.add(c);
}
return list.stream();
}
- mapToDouble(ToDoubleFunction f):接收一个函数作为参数,该函数会被应用到每个元素上,产生一个新的DoubleStream
List list = Arrays.asList(1.1,2.2);
list.stream().mapToDouble((d) -> d+1).forEach(System.out::println);
//结果:2.1 3.1
- mapToInt(ToIntFunction f):接收一个函数作为参数,该函数会被应用到每个元素上,产生一个新的IntStream
- mapToLong(ToLongFunction f):接收一个函数作为参数,该函数会被应用到每个元素上,产生一个新的LongStream
- flatMap(Function f):接收一个函数作为参数,将流中的每个值都换成另一个流,然后把所有流连接成一个流
Stream cStream = list.stream()
.flatMap(StreamTest03::filterCharacter);//{a,a,b,b}
cStream.forEach(System.out::println);//结果:a a b b
如上代码所示,和map(Function f)
的区别是它会把所有的连接成一个流。
6、中间操作-排序
sorted()
,sorted(Comparator comp)
,下面介绍这两个方法:
- sorted():产生一个新流,其中按自然顺序排序(Comparable)
List list = Arrays.asList("bb","aa","cc");
list.stream()
.sorted()
.forEach(System.out::println);
//结果:aa bb cc
- sorted(Comparator comp):产生一个新流,其中按比较器顺序排序(Comparator)
//list是上面的List
list.stream()
.sorted((e1,e2) -> {
if (e1.getAge() == e2.getAge()) { //如果年龄相等则根据名字排序
return e1.getName().compareTo(e2.getName());
}else {
return e2.getAge() - e1.getAge(); //年龄不等从大往小排序
}
})
.forEach(System.out::println);
//结果:4 4 3 2 1
7、终止操作-查找与匹配
直接介绍有哪些方法,例子就不列举了。
- boolean allMatch(Predicate p):检查是否匹配所有元素。如果都匹配或流为空,返回true。
- boolean anyMatch(Predicate p):检查是否至少匹配一个元素。
- boolean noneMatch(Predicate p):检查是否没有匹配所有元素。如果没有任何元素匹配或流为空,返回true。
- Optional
findFirst(): 返回第一个元素。 - Optional
findAny(): 返回当前流中的任意元素。 - long count():返回流中元素的总个数。
- Optional
min(Comparator super T> comparator): 返回流中的最小值。 - Optional
max(Comparator super T> comparator): 返回流中的最大值。
8、归约reduce
- T reduce(T identity, BinaryOperator
accumulator): 可以将流中元素反复结合起来,得到一个值。- T identity:起始值。
- BinaryOperator
accumulator:二元运算型接口。
List list = Arrays.asList(1,2,3,4,5);
Integer reduce = list.stream()
.reduce(0, (x, y) -> x + y);
System.out.println(reduce);
//结果:15
其实它是将identity
这个起始值作为上面代码中的x
,然后从流中取出一个作为y
,然后进行x+y
运算。
- Optional
reduce(BinaryOperator 可以将流中元素反复结合起来,得到一个值。因为没有初始值可能为空,所以将结果封装到Optional中去。accumulator):
Optional op1 = list.stream().reduce((x, y) -> x - y);
System.out.println(op1.get());
//结果:-13
Optional op2 = employees.stream()
.map(Employee::getSalary)
.reduce(Double::sum);
System.out.println(op2.get());
//可以得到工资的总和
9、收集collect
-
R collect(Collector super T, A, R> collector): collect
将流转换成其他形式。接收一个Collector
接口的实现,用于Stream中元素做汇总的方法。
Collector
接口中方法的实现决定了如何对流执行收集操作(如收集到List、Set、Map)。
public interface Collector
{} 中:T:归约操作的输入元素的类型
A:归约操作的可变累积类型(通常作为一个实现细节隐藏)
R:归约操作结果的类型
Collectors
类是Collector的实现,它实现了各种有用的归约操作,例如将元素累积到集合中,根据各种标准对元素进行汇总等。提供了很多静态方法,可以方便的创建常见收集器实例,具体方法与实例如下表:
方法 | 返回值类型 | 作用 |
---|---|---|
toList() | List |
把流中元素收集到list |
toSet() | Set |
把流中元素收集到set |
toCollection(Supplier s) | Collection |
把流中的元素收集到创建的集合 |
counting() | Long | 计算流中元素的个数 |
summingInt(ToIntFunction mapper) | Integer | 对流中元素的整数属性求和 |
averagingInt(ToIntFunction mapper) | Double | 计算流中元素Integer属性的平均值 |
summarizingInt(ToIntFunction mapper) | IntSummaryStatistics | 收集流中Integer属性的统计值。可通过返回的对象获取平均值等 |
joining() | String | 连接流中每个字符串 |
maxBy(Comparator com) | Optional |
根据比较器选择最大值 |
minBy(Comparator com) | Optional |
根据比较器选择最小值 |
collectingAndThen(Collector c,Function f) | f返回的类型 | 包裹另一个收集器,对其结果转换函数 |
groupingBy(Function classifier) | Map |
根据某属性值对流分组,属性为K,结果为V |
partitioningBy(Predicate p) | Map |
根据true或false进行分区 |
暂且介绍上面几个方法,更多方法可以去看JDK8的API。
下面以Collectors
的collectingAndThen
为例来介绍collect方法
的使用。
Collector
collectingAndThen(Collector downstream,Function finisher)方法:
此方法中泛型:
:输入元素的类型 - : downstream收集器中的中间积累的类型
: downstream收集器的结果的类型 : 结果收集器的结果的类型 方法有两个参数:
- Collector
downstream:对数据进行处理 - Function
finisher:对downstream收集器最终的结果再处理一下 返回值:downstream收集器的操作完成后,最终的结果再通过Function再处理一下,然后返回一个Collector。
说明:finisher
是对downstream
的结果进行处理,finisher
接口的参数是downstream
的结果。collectingAndThen
方法的返回值是finisher
中返回的值。
//List list
Employee collect = list.stream()
.collect(Collectors.collectingAndThen(
Collectors.maxBy(Comparator.comparing(Employee::getName)),
(e) -> e.get()
));
List list = Arrays.asList("a","b");
String str = list.stream()
.collect(Collectors.collectingAndThen(
Collectors.joining(","),
String::toUpperCase
));
//结果是:A,B
上面代码,downstream
先用joining
方法将字符串连接成:"a,b"
,然后将使用String::toUpperCase
对"a,b"
转大写。
三、Optional类
Optional
类(java.util.Optional
)是一个容器类,代表一个值存在或不存在,原来用null
表示一个值不存在,现在Optional
可以更好的表达这个概念。并且可以避免空指针。
常用方法:
Optional.of(T t):创建一个Optional实例
Optional.empty():创建一个空的Optional实例
Optional.ofNullable(T t):若t不为null,创建Optional实例,否则创建空实例
isPresent():判断是否包含空值,空返回false
orElse(T t):如果调用对象包含值,返回该值,否则返回t
orElseGet(Supplier s):如果调用对象包含值,返回该值,否则返回s获取的值
map(Function f):如果有值对其处理,并返回处理后的Optional,否则返回Optional.empty()
flatMap(Function mapper):与map类似,要求返回值必须是Optional
四、接口中的默认方法和静态方法
1、接口中的默认方法
java8中接口里面包含具体的实现方法,该方法称为默认方法,默认方法使用default
关键字修饰。
public interface MyInterface {
default String getName(){
return "MyInterface";
}
}
对于默认的方法,遵循的原则:
若一个接口中定义了一个默认方法,另一个的父类或接口中又定义一个同名方法时:
- 选择父类中的方法。如果一个父类提供了具体实现,那么接口中具有相同名称和参数的默认方法会被忽略
public class MyClass {
public String getName(){
return "MyClass";
}
}
public interface MyInterface {
default String getName(){
return "MyInterface";
}
}
public class SubClass extends MyClass implements MyInterface {}
SubClass sub = new SubClass();
String name = sub.getName();
System.out.println(name);
//MyClass
- 接口冲突。如果一个父接口提供一个默认方法,而另一个接口也提供了一个具有相同名称和参数列表的方法(不管方法是否是默认方法),那么必须覆盖该方法来解决冲突
public interface MyInterface2 {
default String getName(){
return "MyInterface2";
}
}
public class SubClass implements MyInterface1,MyInterface2{
@Override
public String getName() {
return "SubClass重写该方法";
}
}
如上代码,实现类SubClass
里要重写该方法解决冲突。
2、接口中的静态方法
public interface MyInterface1 {
default String getName(){
return "MyInterface1";
}
public static void show(){
System.out.println("接口中的静态方法");
}
}
MyInterface1.show();
//接口中的静态方法
五、新时间日期API
JDK8新增的时间API主要有三种:
java.time.LocalDate:只对年月日做出处理
java.time.LocatTime:只对时分秒纳秒做出处理
java.time.LocalDateTime:同时可以处理年月日和时分秒
java.time.format.DateTimeFormatter:日期时间对象格式化和解析
1、LocalDate、LocalTime、LocalDateTime
LocalDate、LocalTime、LocalDateTime 类的实例是不可变的对象,分别表示使用 ISO-8601日系统的日期、时间、日期和时间。它们提供了简单的日期或时间,并不包含当前的时间信息。也不包含与时区相关的信息。
方法 | 描述 | 示例 |
---|---|---|
now() | 静态方法,根据当前时间创建对象 | LocalDate.now(); LocalTime.now(); LocalDateTime.now(); |
of() | 静态方法,根据指定日期/时间创建对象 | LocalDate.of(2020,01,01); |
plusDays plusWeeks plusMonths plusYears |
向当前LocalDate对象添加几天、几周、几个月、几年 | |
minusDays minusWeeks minusMonths minusYears |
从当前LocalDate对象减去几天、几周、几个月、几年 | |
plus,minus | 添加或减去一个Duration或Period | |
with | 通过TemporalAdjuster将日期修改为指定的值,并返回新的LocalDate对象 | |
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 | 判断是否是闰年 |
2、Instant时间戳
用于“时间戳”的运算。它是以Unix元年(传统的设定为UTC时区1970年1月1日午夜时分)开始所经历的描述进行运算,默认UTC时区。
- 获取当前时间:
Instant.now();
返回当前时间,默认获取UTC时区 - 调整偏移量:
Instant.now().atoffset(ZoneOffset.ofHours(8));
调整为北京时间 - 获取毫秒数:
Instant.now().toEpochMilli();
获取时间戳
3、Duration和Period计算时间/日期间隔
- Duration:用于计算两个“时间”间隔
Instant start = Instant.now();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
Instant end = Instant.now();
Duration d = Duration.between(start, end);
d.toMillis();//获取相差的毫秒
d.toMillis();//获取相差的秒
- Period:用于计算两个“日期”间隔
LocalDate old = LocalDate.of(2019,10,1);
LocalDate now = LocalDate.now();//2020-10-01
Period p = Period.between(old, now);
p.getYears();//两个日期之间间隔的年,结果为:1
p.getMonths();//两个日期之间间隔的月,结果为:0
p.getDays();//两个日期之间间隔的日,结果为:0
//这三个加起来就是两个日期之间相差的时间
4、TemporalAdjuster时间校正器
TemporalAdjuster : 时间校正器。有时我们可能需要获取,例如:将日期调整到下周一等操作。
**TemporalAdjusters : **该类通过静态方法提供了大量的常用 TemporalAdjuster 的实现。
LocalDateTime now = LocalDateTime.now();
now.with(TemporalAdjusters.next(DayOfWeek.MONDAY));//返回以今天为准的下周一的时间
自定义方式:
LocalDate result = now.with((time) -> {
LocalDate date = (LocalDate) time;
return date.plusDays(1);
});
//返回下一天的日期
5、DateTimeFormatter时间/日期格式化
DateTimeFormatter
是时间日期格式化的类,更复杂的格式化可以使用DateTimeFormatterBuilder
类。
LocalDateTime now = LocalDateTime.now();
DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
DateTimeFormatterBuilder dtfb = new DateTimeFormatterBuilder();
dtfb.append(dtf).appendLiteral(" 星期三");
String format = now.format(dtfb.toFormatter());
//2020-10-01 17:39:33 星期三
这个类提供了三种格式化方法:
- 使用预定义的标准格式,如ISO_local_date
- 使用语言环境相关的格式,如uuuu-MMM-dd
- 自定义的格式,如long
//格式化
LocalDateTime now = LocalDateTime.now();
DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
String formatDate = now.format(dtf);
System.out.println("formatDate--->" + formatDate);//formatDate--->2020-10-01 17:57:00
//解析
LocalDateTime parseDate = now.parse(formatDate, dtf);
System.out.println("parseDate--->" + parseDate);//parseDate--->2020-01-14T17:57
6、ZonedDateTime、ZoneId时区处理
带时区的时间分别为:ZonedDate
、ZonedTime
、ZonedDateTime
。
其中每个时区都对应着 ID,地区ID都为 “{区域}/{城市}”的格式:例如 :Asia/Shanghai
等。
ZoneId:该类中包含了所有的时区信息
getAvailableZoneIds()
: 可以获取所有时区时区信息of(id)
: 用指定的时区信息获取ZoneId
对象systemDefault()
:获取系统默认时区
LocalDateTime now = LocalDateTime.now(ZoneId.of("Asia/Shanghai"));
ZonedDateTime zdt = now.atZone(ZoneId.of("Asia/Shanghai"));
六、重复注解与类型注解
对注解处理提供了两点改进:可重复注解及可用于类型的注解。
@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE})
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotations {
MyAnnotation[] value();
}
@Repeatable(MyAnnotations.class)
@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE,ElementType.TYPE_PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation {
String value();
}
@MyAnnotation("Hello")
@MyAnnotation("World")
public void test(@MyAnnotation("a") String str){}