JAVA8新特性
- 特点
-
速度更快
- hashmap(数组-链表-红黑树【二叉树的一种】)
- 取消永久区,将方法区改为了元空间,使用物理内存
-
代码更少(增加了新的语法Lambda表达式)
-
强大的Stream API
- 在java中操作数据比sql语句还简单
-
便于并行
-
最大化减少空指针异常 Optional
-
一、Lambda表达式
1、为什么使用Lambda表达式
-
Lambda是一个匿名函数,我们可以把Lambda表达式理解为是一段可以传递的代码(将代码像数据一样进行传递)。可以写出更简洁、更灵活的代码。作为一种更紧凑的代码风格,使java的语言表达能力得到了提升。
[外链图片转存失败(img-X52rH6fL-1563698660487)(https://s2.ax1x.com/2019/07/17/ZLp6u6.png)]
-
//Lambda 表达式 @Test public void test(){ list<Employee> list = filterEmployee(employees,(e) -> e.getSalary()>=5000); }
-
//Stream API @Test public void test(){ employees .stream() .filter((e) ->e.getSalary()>=5000) .limit(2) .forEach(System.out::println); employees.stream() .map(Employee::getName) .forEach(System.out::println); }
2、Lambda使用
-
基础语法
- java8中引入了一个新的操作符“->”,该操作符称为箭头操作符或Lambda操作符。
- 左侧:Lambda表达式的参数列表
- 右侧:Lambda表示式中所需要执行的功能,即Lambda体
- java8中引入了一个新的操作符“->”,该操作符称为箭头操作符或Lambda操作符。
-
语法格式
-
无参数,无返回值
-
()-> System.out.println("Hello Lambda!");
-
@Test public void test(){ int num = 0;//jdk1.7前,必须是final,这里隐式添加了final Runnable r = new Runnable(){ @Override public void run(){ System.out.println("Hello World!"+num); } }; r.run(); Runnable r1 = ()->System.out.println("Hello Lambda!"+num); r1.run; }
-
-
有一个参数,并且无返回值
-
(x)->System.out.println(x)
-
Consumer<String> con = (x)->System.out.println(x); con.accept("xxxxxxx");
-
-
若只有一个参数,小括号可以省略不写
-
x->System.out.println(x)
-
-
有两个以上的参数,有返回值,并且Lambda体中有多条语句
-
@Test public void test(){ Comparator<Integer> com=(x,y)->{ System.out.println("函数式接口"); return Integer.compare(x,y); }; }
-
-
若Lambda体中只有一条语句,return和大括号都可以省略不写
-
@Test public void test(){ Comparator<Integer> com=(x,y)->Integer.compare(x,y); }
-
-
Lambda表达式的参数列表的数据类型可以省略不写,因为JVM编译器通过上下文推断出数据类型,即“类型推断”
-
public void test(){ //jdk1.7推断不出尖括号中的类型,1.8可以 show(new HashMap<>()); } public void show(Map<String,Integer> map){ xxxxxxx; }
-
-
二、函数式接口
1、概念
-
若接口中只有一个抽象方法的接口,称为函数式接口。可以使用注解@FunctionalInterface修饰,可以检查是否是函数式接口
-
@FunctionalInterface public interface MyFun{ public Integer getValue(Integer num); } //需求:对一个数进行运算 @Test public void test(){ Integer num = operation(100,(x)->x*x); System.out.println(num); } public Integer operation(Integer num,MyFun mf){ return mf.getValue(num) }
2、java8内置的四大核心函数式接口
-
Consumer:消费型接口
-
void accept(T t);
@Test public void test(){ happy(10000,(m)->System.out.println("xxxxx")); } public void happy(double money,Consumer<Double> con){ con.accept(money); }
-
-
Supplier:供给型接口
-
T get();
@Test public void test(){ List<Integer> numList = getNumList(10,()->(int)(Math.random()*100)); for(Integer num:numList){ System.out.println(num); } } public List<Integer> getNumList(int num,Supplier<Integer> sup){ List<Integer> list = new ArrayList<>(); for(int i=0;i<num;i++){ Integer n = sup.get(); list.add(n); } return list; }
-
-
Function<T,R>:函数型接口
-
R apply(T t);
@Test public void test(){ String newStr = strHandler("\t\t\t xxxx ",(str)->str.trim()); System.out.println(newStr); } public String strHandler(String str,Function<String,String> fun){ return fun.apply(str); }
-
-
Predicate:断言型接口
-
Boolean test(T t);
@Test public void test(){ List<String> list = Arrays.asList("Hello","abc","Lambda","www","ok"); List<String> strList = filterStr(list,(s)->s.length()>3); for(String str:strList){ System.out.println(str); } } public List<String> filterStr(List<String> list,Predicate<String> pre){ List<String> strList = new ArrayList<>(); for(String str:list){ if(pre.test(str)){ strList.add(str) } } return strList; }
-
-
其他接口
[外链图片转存失败(img-zx6qNm3A-1563698660487)(https://s2.ax1x.com/2019/07/18/ZO5zT0.png)]
三、方法引用与构造器引用
1、方法引用
-
概念
- 若Lambda体中的内容有方法已经实现了,我们可以使用“方法引用”
- 可以理解为方法引用是Lambda表达式的另外一种表现形式
-
注意:
-
- Lambda体中调用方法的参数列表与返回值类型要与函数式接口中抽象方法的参数列表和返回值保持一致
- 若Lambda参数列表中的第一参数是实例方法的调用者,而第二个参数是实例方法的参数时,可以使用ClassName::method
-
-
主要的三种语法格式
-
对象::实例方法名
@Test public void test(){ PrintStream ps1 = System.out; Consumer<String> con = (x)->ps1.println(x); PrintStream ps = System.out; Consumer<String> con1 = ps::println; Consumer<String> con2 = System.out::println; con2.accept("xxxxxx"); } @Test public void test1(){ Employee emp = new Employee(); Supplier<String> sup = ()->emp.getName(); String str = sup.get(); System.out.println(str); Supplier<Integer> sup2 = emp::getAge; Integer num = sup2.get(); System.out.println(num); }
-
类::静态方法名
@Test public void test(){ Comparator<Integer> com = (x,y)->Integer.compare(x,y); Comparator<Integer> con1 = Integer::compare; }
-
类::实例方法名
@Test public void test(){ BiPredicate<String,String> bp = (x,y)->x.equals(y); BiPredicate<String,String> bp2 = String::equals; }
-
2、构造器引用
-
格式
- ClassName::new
@Test public void test(){ Supplier<Employee> sup = ()->new Employee(); Supplier<Employee> sup2 = Employee::new; Employee emp = sup2.get(); System.out.println(emp); } @Test public void test1(){ Function<Integer,Employee> fun = (x)->new Employee(x); Function<Integer,Employee> fun2 = Employee::new; Employee emp = fun2.apply(101); System.out.println(emp); }
-
注意:需要调用的构造器的参数列表要与函数式接口中抽象方法的参数列表保持一致!
3、数组引用
-
Type::new;
@Test public void test(){ Function<Integer,String[]> fun= (x)->new String[x]; String[] strs = fun.apply[10]; System.out.println(strs.length); Function<Integer,String[]> fun2 =String[]::new; fun2.apply(20); System.out.println(str2.length); }
四、Stream API
1、概念
- java8中有两大最为重要的改变,第一个是Lambda表达式;另外一个则是Stream API(java.util.stream.*)
- Stream是java8中处理集合的关键抽象概念,它可以指定你希望对集合进行的操作,可以执行非常复杂的查找、过滤和映射数据等操作。使用Stream API对集合数据进行操作,就类似于使用SQL执行的数据库查询。也可以使用Stream API来并行执行操作。简而言之,Stream API提供了一种高效且易于使用的处理数据的方式。
-
流(Stream)到底是什么呢?
- 是数据渠道,用于操作数据源(集合、数组等)所生成的元素序列。
- “集合讲的是数据,流讲的是计算!”
-
注意:
-
-
Stream自己不会存储元素
-
Stream不会改变源对象。相反,他们会返回一个持有结果的新Stream
-
Stream操作是延迟执行的。这意味着他们会等到需要结果的时候才执行
-
-
2、三个操作步骤
- 创建Stream
//创建Stream
@Test
public void test(){
//1.可以通过Collection系列集合提供的stream()或paralleStream()
List<String> list = new ArrayList<>();
Stream<String> stream1 = list.stream();
//2.通过Arrays中的静态方法stream()获取数组流
Employee[] emps = new Employee[10];
Stream<Employee> stream2 = Arrays.stream(emps);
//3.通过Stream类中的静态方法of()
Stream<String> stream3 = Stream.of("aa","bb","cc");
//4.创建无限流
//迭代
Stream<Integer> stream4 = Stream.iterate(0,(x)->x+2);
stream4
.limit(10)
.forEach(System.out::println);
//生成
Stream.generate(()->Math.random())
.limit(5)
.forEach(System.out::println);
}
-
Stream中间操作
-
多个中间操作可以连接起来形成一个流水线,除非流水线上触发终止操作,否则中间操作不会执行任何的处理!而在终止操作时一次性全部处理,称为“惰性求值”。
-
筛选与切片
-
//内部迭代:迭代操作由Stream API完成 @Test public void test(){ //中间操作:不会执行任何操作 Stream<Employee> stream = employees.stream() .filter((e)->{ System.out.println("Stream API的中间操作"); return e.getAge()>35; }); //终止操作:一次性执行全部内容,即“惰性求值” stream.forEach(System.out::println); } //外部迭代 @Test public void test1(){ Iterator<Employee> it = employees.iterator(); while(it.hasNext()){ System.out.println(it.next()); } }
-
@Test public void test(){ employees.stream() .filter((e)->{ System.out.println("短路!"); return e.getSalary()>5000}) .limit(2) .forEach(System.out::println); }
-
@Test public void test(){ employees.stream() .filter((e)->e.getSalary()>5000) .skip(2)//过滤前两个 .distinct()//去重(重写hashcode和equals方法) .forEach(System.out::println); }
-
-
映射
-
public Class TestStreamAPI2{ @Test public void test(){ List<String> list = Arrays.asList("aaa","bbb","ccc","ddd","eee"); list.stream() .map((str)->str.toUpperCase()) .forEach(System.out::println); employees.stream() .map(Employee::getName) .forEach(System.out::println); } }
-
@Test public void test(){ Stream<Stream<Character>> stream = list.stream() .map(TestStreamAPI2::filterCharacter);//{{a,a,a},{b,b,b}} stream.forEach((sm) ->{ sm.forEach(System.out::println); }); Stream<Character> sm = list.stream() .flatMap(TestStreamAPI2::filterCharacter);//{a,a,a,b,b,b} sm.forEach(System.out::println); } public Stream<Character> filterCharacter(String str){ List<Character> list = new ArrayList<>(); for(Character ch:str.toCharArray()){ list.add(ch); } return list.stream(); }
-
-
排序
-
//sorted()--自然排序(Comparable) //sorted(Comparator com)--定制排序 @Test public void test(){ List<String> list = Arrays.asList("ccc","aaa","bbb","ddd","eee"); list.stream() .sorted() .forEach(System.out::println); employees.stream() .sorted((e1,e2)->{ if(e1.getAge().equals(e2.getAge()){ return e1.getName().compareTo(e2.getName()); } else{ return e1.getAge().comapreTo(e2.getAge()); } }).forEach(System.out::println); }
-
-
-
Stream的终止操作
-
终端操作会从流的流水线生成结果。其结果可以是任何不是流的值,例如:List、Integer,甚至是void
-
@Test public void test(){ //所有匹配 boolean b1 = employees.stream() .allMatch((e)->e.getStatus().equals(Status.BUSY)); //任意一个匹配 boolean b2 = employees.steam() .anyMatch((e)->e.getStatus().equals(Status.BUSY)); //只要有一个匹配就返回false employees.stream() .noneMatch((e)->e.getStatus().equals(Status.BUSY)); //返回第一个元素,包装到Optional容器中 Optional<Employee> op = employees.stream() .sorted((e1,e2)->Double.compare(e1.getSalary(),e2.getSalary())) .findFirst(); System.out.println(op.get()); //并行获取任意元素,多个线程同时查找 Optional<Employee> op2 = employees.parallelStream().filter((e)->e.getStatus().equals(Status.FREE)) .findAny(); System.out.println(op2.get()); }
-
@Test public void test(){ Long count = employees.stream() .count();//返回流中元素的总个数 System.out.println(count); //返回流中最大值 Optional<Employee> op1 = employees.stream() .max((e1,e2)->Double.compare(e1.getSalary(),e2.getSalary())); System.out.println(op1.get()); Optional<Double> op2 = employees.stream() .map(Employee::getSalary) .min(Double::comapre); System.out.println(op2.get()); }
-
归约
-
reduce(T identity,BinaryOperator)/ reduce(BinaryOperator)–可以将流中元素反复结合起来,得到一个值
-
@Test public void test(){ List<Integer> list = Arrays.asList(1,2,3,4,5,6,7,8,9,10); Integer sum = list.stream() .reduce(0,(x,y)->x+y); System.out.println(sum); Optional<Double> op = employees.stream() .map(Employee::getSalary) .reduce(Double::sum); System.out.println(op.get()); }
-
收集
-
collect–将流转换为其他形式,接收一个Collector接口的实现,用于给Stream中元素做汇总的方法
-
@Test public void test(){ List<String> list = employees.stream() .map(Employee:getName) .collect(Collectors.toList()); list.forEach(System.out::println); HashSet<String> hs = employees.stream() .map(Employee::getName) .collect(Collectors.toCollection(HashSet::new)); hs.forEeach(System.out::println); }
-
@Test public void test(){ //总数 Long count = employees.stream() .collect(Collectors.counting()); //平均值 Double avg = employees.stream() .collect(Collectors.averagingDouble(Employee::getSalary)); //综合 Double sum = employees.stream() .collect(Collectors.summingDouble(Employee::getSalary)); //最大值 Optional<Employee> max = employees.stream() .collect(Collectors.maxBy((e1,e2)->Double.compare(e1.getSalary(),e2.getSalary()))); //最小值 Optional<Double> min = employees.stream() .map(Employee::getSalary) .collect(Collectors.minBy(Double::compare)); }
-
@Test public void test(){ //分组 Map<Status,List<Employee>> map = employees.stream() .collect(Collectors.groupingBy(Employee::getStatus)); //多级分组 Map<Status,Map<String,List<Employee>>> map = employees.stream() .collect(Collectors.groupingBy(Employee::getStatus),Collectors.groupingBy((e)->{ if(((Employee)e).getAge()<=35){ return "青年"; }else if(((Employee)e).getAge()<=50){ return "中年"; }else{ return "老年"; } }))); //分区 Map<Boolean,List<Employee>> map2 = employees.stream() .collect(Collectors.partitioningBy((e)->e.getSalary()>8000)); }
-
-
-
3、并行流与顺序流
-
并行流
-
就是把一个内容分成多个数据块,并用不同的线程分别处理每个数据块的流。
-
java8中将并行进行了优化,我们可以很容易的对数据进行并行操作。Stream API可以声明性地通过parallel()与sequential()在并行流与顺序流之间进行切换
-
[外链图片转存失败(img-tmkJCAhG-1563698660496)(https://s2.ax1x.com/2019/07/19/Zvu2g1.png)]
-
Fork/Join框架与传统线程池的区别
-
采用“工作窃取”模式(work-stealing):
- 当执行新的任务时它可以将其拆分分成更小的任务执行,并将小任务加到线程队列中,然后再从一个随机线程的队列中偷一个并把它放在自己的队列中。
-
-
-
-
相对于一般的线程池实现,fork/join框架的优势体现在对其中包含的任务的处理方式上。在一般的线程池中,如果一个线程正在执行的任务由于某些原因无法继续运行,那么该线程会处于等待状态,而在fork/join框架实现中,如果某个子问题由于等待另外一个子问题的完成而无法继续运行,那么处理该子问题的线程会主动寻找其他尚未运行的子问题来执行。这种方式减少了线程的等待时间,提高了性能
public class FockJoinTest extends RecursiveTask<Long>{ private final int DEFAULT_CAPACITY = 100; private int start ; private int end ; public FockJoinTest(int start,int end){ this.start= start; this.end = end; } @Override protected Long compute() { long sum = 0; if(end - start < DEFAULT_CAPACITY){ for(int i=start ;i<end;i++){ sum += i; } }else{ int middle = (start+end)/2; FockJoinTest fockJoinTest1 = new FockJoinTest(start,middle); FockJoinTest fockJoinTest2 = new FockJoinTest(middle,end); fockJoinTest1.fork(); fockJoinTest2.fork(); sum = fockJoinTest1.join() + fockJoinTest2.join(); } return sum; } public static void main(String[] args) throws InterruptedException, ExecutionException { ForkJoinPool forkJoinPool = new ForkJoinPool(); FockJoinTest fockJoinTest = new FockJoinTest(1,1000); long fockjoinStartTime = System.currentTimeMillis(); long result = forkJoinPool.invoke(fockJoinTest); System.out.println(result); System.out.println("fock/join计算结果耗时"+(System.currentTimeMillis() - fockjoinStartTime)); } }
4、Optional类
-
@Test public void test(){ //不能构建null值 Optional<Employee> op = Optional.of(new Employee()); //空的.get()方法会报空指针异常,只能构建一个null值 Optional<Employee> op1 = Optional.empty(); //为空.get()方法会报空值异常,是上两方法的综合 Optional<Employee> op2 = Optional.ofNullable(null); //判断是否包含值 if(op.isPresent()){ System.out.println(op.get()) } Employee emp = op.get(); //有值返回有值对象,没有则返回指定值,可以避免空指针 Employee emp = op.orElse(new Employee("xxx",30,8888)); //和orElse的区别是函数式接口Lambda体可以写相关逻辑处理 Employee emp = op.orElseGet(()->new Employee()); System.out.println(emp); //将容器中的对象应用到map函数中 Optional<Employee> op3 = Optional.ofNullable(new Employee("张三",40,6666)); Optional<String> str = op3.map((e)->e.getName()); System.out.println(str.get()); //相比map更进一步使用Optional防止空指针异常 Optional<String> str2 = op.flatMap((e)->Optional.of(e.getName())); System.out.println(str2.get()); }
五、接口中的默认方法与静态方法
-
public interface MyFun{ default String getName(){ return "xxxx"; } public static void show(){ System.out.println("接口中的静态方法") } }
-
接口默认方法的“类优先”原则
- 若一个接口中定义了一个默认方法,而另外一个父类或接口中又定义了一个同名的方法时
- 选择父类中的方法。如果一个父类提供了具体的实现,那么接口中具有相同名称和参数的默认方法会被忽略
- 接口冲突。如果一个父接口提供一个默认方法,而另一个接口也提供了一个具有相同名称和参数列表的方法(不管方法是否是默认方法),那么必须覆盖该方法来解决冲突
- 若一个接口中定义了一个默认方法,而另外一个父类或接口中又定义了一个同名的方法时
六、新时间日期API
-
java.time java.time.chrono java.time.format java.time.temporal java.time.zone
-
使用LocalDate、LocalTime、LocalDateTime
-
LocalDate、LocalTime、LocalDateTime类的实例是不可变的对象,分别表示使用ISO-8601日历系统的日期、时间、日期和时间。他们提供了简单的日期和时间,并不包含当前的时间信息。也不包含与时区相关的信息
-
@Test public void test(){ LocalDateTime ldt = LocalDateTime.now(); System.out.println(ldt); LocalDateTime ldt2 = LocalDateTime.of(2015,10,19,13,22,33); System.out.println(ldt2); LocalDateTime ldt3 = ldt.plusYears(2); System.out.println(ldt3); LocalDateTime ldt4 = ldt.minusMonths(2); System.out.println(ldt4); System.out.println(ldt.getYear()); }
-
-
Instant:时间戳(以Unix元年:1970年1月1日00:00:00到某个时间之间的毫秒值)
-
@Test public void test(){ //显示年月日时分秒 Instant ins1 = Instant.now();//默认获取UTC时区 System.out.println(ins1); //设置偏移量+8小时,显示年月日时分秒 OffsetDateTime odt = ins1.atOffset(ZoneOffset.ofHours(8)); System.out.println(odt); //获取毫秒值 System.out.println(ins1.toEpochMilli); //获取Unix元年+1秒的具体时间,年月日时分秒 Instant ins2 = Instant.ofEpochSecond(1); System.out.println(ins2); }
-
-
Duration:计算两个“时间”之间的间隔
Period:计算两个“日期”之间的间隔
-
@Test public void test(){ Instant ins1 = Instant.now(); try{ Tread.sleep(1000); }catch(InterruptedException e){ } Instant ins2 = Instant.now(); Duration duration = Duration.between(ins1,ins2); //获取毫秒值 System.out.println(duration.toMillis()); System.out.println("----------------------------------") LocalTime lt1 = LocalTime.now(); try{ Thread.sleep(1000); }catch(InterruptedException e){ } LocalTime lt2 = LocalTime.now(); //获取毫秒值 System.out.println(Duration.between(lt1,lt2).toMillis()); }
-
@Test public void test(){ LocalDate ld1 = LocalDate.of(2015,1,1); LocalDate ld2 = LocalDate.now(); Period period = Period.Between(ld1,ld2); //返回相差年月日 System.out.println(period); }
-
-
TemporalAdjuster:时间校正器。有时我们可能需要获取例如:将日期调整到“下个周日”等操作。
-
TemporalAdjusters:该类通过静态方法提供了大量的常用TemporalAdjuster的实现。
-
LocalDate nextSunday = LocalDate.now().with( TemporalAdjusters.next(DayOfWeek.SUNDAY) );
-
@Test public void test(){ LocalDateTime ldt = LocalDateTime.now(); System.out.println(ldt); //指定修改时间 LocalDateTime ldt2 = ldt.withDayOfMonth(10); System.out.println(ldt2); //指定下周星期日的时间 LocalDateTime ldt3 = ldt.with(TemporalAdjusters.next(DayOfWeek.SUNDAY)); System.out.println(ldt3); //自定义:下一个工作日 LocalDateTime ldt5 = ldt.with((l)->{ LocalDateTime ldt4 = (LocalDateTime)l; DayOfWeek dow = ldt4.getDayOfWeek(); if(dow.equals(DayOfWeek.FRIDAY)){ ldt4.plusDay(3); }else if(dow.equals(DayOfWeek.SATURDAY)){ return ldt4.plusDay(2); }else{ return ldt4.plusDay(1); } }); System.out.println(ldt5); }
-
-
DateTimeFormatter:格式化时间/日期
-
@Test public void test(){ DateTimeFormatter dtf = DateTimeFormatter.ISO_DATE; LocalDateTime ldt = LocalDateTime.now(); String strDate = ldt.format(dtf); System.out.println(strDate); System.out.println("-----------------"); DateTimeFormatter dtf2 = DateTimeFormatter.ofPattern("yyyy年MM月dd日 HH:mm:ss"); String strDate2 = dtf2.format(ldt); System.out.println(strDate2); LocalDateTime newDate = ldt.parse(strDate2,dtf2); System.out.println(newDate); }
-
-
java8中加入了对时区的支持,带时区的日期分别为:ZonedDate,ZonedTime,ZonedDateTime,其中每个时区都对应着ID,地区ID都为“{区域}/{城市}”的格式,例如:Asia/Shanghai等。
-
ZoneId:该类中包含了所有的时区信息
- getAvailableZoneIds():可以获取所有时区信息
- of(id):用指定的时区信息获取ZoneId对象
-
@Test public void test(){ //获取所有时区信息 Set<String> set = ZoneId.getAvailableZoneIds(); set.forEach(System.out::println); //指定时区构建时间 LocalDateTime ldt = LocalDateTime.now(ZoneId.of("Europe/Tallinn")); System.out.println(ldt); //带时区的具体时间 LocalDateTime ldt2 = LocalDateTime.now(ZoneId.of("Asia/Shanghai")); ZonedDateTime zdt = ldt2.atZone(ZoneId.of("Asia/Shanghai")); System.out.println(zdt); }
-
七、其他新特性
-
[外链图片转存失败(img-8jM4cUuy-1563698660497)(https://s2.ax1x.com/2019/07/21/epEejP.png)]
-
@Test public void test(){ Class<TestAnnotation> clazz = TestAnnotation.class; Method m1 = clazz.getMethod("show"); MyAnnotation[] mas = m1.getAnnotationsByType(MyAnnotation.class); for(MyAnnotation myAnnotation:mas){ System.out.println(myAnnotation.value()); } }
以获取所有时区信息
+ of(id):用指定的时区信息获取ZoneId对象
-
@Test public void test(){ //获取所有时区信息 Set<String> set = ZoneId.getAvailableZoneIds(); set.forEach(System.out::println); //指定时区构建时间 LocalDateTime ldt = LocalDateTime.now(ZoneId.of("Europe/Tallinn")); System.out.println(ldt); //带时区的具体时间 LocalDateTime ldt2 = LocalDateTime.now(ZoneId.of("Asia/Shanghai")); ZonedDateTime zdt = ldt2.atZone(ZoneId.of("Asia/Shanghai")); System.out.println(zdt); }
七、其他新特性
-
[外链图片转存中…(img-8jM4cUuy-1563698660497)]
-
@Test public void test(){ Class<TestAnnotation> clazz = TestAnnotation.class; Method m1 = clazz.getMethod("show"); MyAnnotation[] mas = m1.getAnnotationsByType(MyAnnotation.class); for(MyAnnotation myAnnotation:mas){ System.out.println(myAnnotation.value()); } }