Java8 新特性

日期处理

在Java8之前,操作日期的话不是很方便,有些地方需要自己编写实现。在Java8中的java.time包下新增很多关于日期处理的类,通过这些类可以使开发者更便捷的操作日期,这些类都是final修饰的,并且是线程安全的。

java8新增的关于日期处理的包:

    • java.time
      主要的日期处理相关的类都在这个包下
    • java.time.chrono
      这个包中的类主要是对应不同国家的日历记录的方式,国际上通用的日历是2018-01-01这种,但是有些国家有自己独特的记录日历的方式,比如我们国家的农历,不过可惜的是在这个包下没有农历相关的类。
    • java.time.format
      日期格式化相关的类在这个包下
    • java.time.temporal
      该包下存放着不同国家记录时间的方式相关的类
    • java.time.zone
      这个包下存放着设置时区相关的类,目前我们国家是在东八区。

 

LocalDate:只能处理日期相关的数据,不包含时间

 1     public static void main(String[] args) {
 2         //获取当前日期
 3         LocalDate date1 = LocalDate.now();
 4         System.out.println(date1);
 5         
 6         int year = date1.getYear();
 7         int month = date1.getMonthValue();
 8         int day = date1.getDayOfMonth();
 9         
10         //日期格式化
11         String date2 = date1.format(DateTimeFormatter.ofPattern("yyyy年MM月dd日"));
12         System.out.println(date2);
13         
14         //是否是闰年
15         boolean leap = date1.isLeapYear();
16         
17         //判断当前月份的天数
18         int len = date1.lengthOfMonth();
19         
20         //自定义日期方式一
21         LocalDate date3 = LocalDate.parse("2017-07-11");
22         //自定义日期方式二
23         LocalDate date4 = LocalDate.of(2017, 7, 11);
24         
25         //判断日期是否相等
26         if(date3.equals(date4)){
27             System.out.println("日期相等");
28         }
29         
30         //获取一周后的日期
31         LocalDate date5 = date1.plus(1, ChronoUnit.WEEKS);
32         System.out.println("一周后的日期:" + date5);
33     }

 

* LocalTime:只能处理时间,不包括日期

 1     public static void main(String[] args) {
 2         //获取当前时间
 3         LocalTime time1 = LocalTime.now();
 4         //获取当前时间,不包含毫秒
 5         LocalTime time2 = LocalTime.now().withNano(0);
 6         System.out.println(time1);
 7         System.out.println(time2);
 8         
 9         //一小时后
10         LocalTime time3 = time1.plusHours(1);
11         System.out.println(time3);
12         
13         //自定义时间
14         LocalTime time4 = LocalTime.parse("06:12:30");
15         LocalTime time5 = LocalTime.of(6, 30, 12);
16         
17     }

 

* LocalDateTime:可以处理日期和时间

1     public static void main(String[] args) {
2         LocalDateTime date1 = LocalDateTime.now();
3         System.out.println(date1);
4         
5         LocalDateTime date2 = LocalDateTime.of(2017, 7, 20, 13, 20, 15);
6     }

 

* Duration和period

 1     public static void main(String[] args) {
 2         LocalDate date1 = LocalDate.of(2017, 7, 11);
 3         LocalDate date2 = LocalDate.of(2017, 7, 13);
 4         //Period处理LocalDate
 5         Period period = Period.between(date1, date2);
 6         System.out.println(period.getDays());
 7         
 8         
 9         LocalTime time1 = LocalTime.of(18, 20, 10);
10         LocalTime time2 = LocalTime.of(18, 30, 10);
11         
12         Duration duration = Duration.between(time1, time2);
13         System.out.println(duration.getSeconds());
14     }

 

设定时区

通过ZoneId这个类中的of方法可以设定时区:

1     //获取本地的时区时间
2     ZonedDateTime now = ZonedDateTime.now();
3     System.out.println(now);
4 
5     //设定时区为美国洛杉矶
6     ZoneId zone = ZoneId.of("America/Los_Angeles");
7     //获取指定的时区时间
8     ZonedDateTime usa = ZonedDateTime.now(zone);
9     System.out.println(usa);

 

函数式接口

jdk8中新增了函数式接口,在一个接口中只有一个抽象方法的接口被称为函数式接口,例如java.lang.Runnable,java.util.concurrent.Callable。jdk8中新增了@FunctionalInterface注解来标注一个函数式接口。

default方法

jdk8中新增了default方法,jdk8之前接口中的方法必须都是抽象的,在jdk8中允许接口中定义非抽象方法,在接口中的非抽象方法上使用default修饰即可,比如在jdk8中新增了一个函数式接口:java.util.function。java8中打破了接口中的方法必须都是抽象的这一规范,有一个好处就是可以提高程序的兼容性,在java.lang.Iterable接口中新增了forEach方法,该方法就是使用default修饰的。
在接口中定义default方法可以变相的让java支持"多继承"

自定义函数接口:

1 @FunctionalInterface
2 public interface MyInterface {
3     void m1(int a, int b);
4 
5     default String m2(String s){return null;};
6 
7     default void m3(){};
8 }

 

lambda表达式格式

可以将lambda看做是一个匿名方法,lambda只能用于函数式接口

(类型 参数, 类型 参数, …, 类型 参数) -> {
代码块;

return 结果;
}

主要由这几个符合构成:
() -> {}

lambda优点:使用lambda表达式可以编写出比匿名内部类更简洁的代码

 1 public class LambdaTest01 {
 2 
 3     public static void main(String[] args) {
 4 
 5         // 不使用lambda
 6         MyInterface oldType = new MyInterface() {
 7 
 8             @Override
 9             public void m1(int a, int b) {
10                 System.out.println(a + b);
11             }
12 
13         };
14 
15         oldType.m1(10, 10);
16 
17 
18 
19 
20         // 使用lambda
21         MyInterface newType = (int a, int b) -> {
22         System.out.println(a+b);
23         };
24 
25 
26         // 编译器都可以从上下文环境中推断出lambda表达式的参数类型,因此可以省略参数类型
27 /*        LambdaInterface newType = (a, b) -> {
28             System.out.println(a + b);
29         };*/
30 
31         newType.m1(20, 20);
32     }
33 
34 }

 

使用lambda在多线程中的写法:

 1     public static void main(String[] args) {
 2 
 3         //不使用lambda的写法
 4         new Thread(new Runnable() {
 5             @Override
 6             public void run() {
 7                 System.out.println("no lambda");
 8             }
 9         }).start();
10 
11 
12         //lambda写法
13         //因为Thread类中接收Runnable类型的对象,所以编译器会识别出lambda表达式是Runnable对象
14         new Thread(() -> {System.out.println("lambda");}).start();

lambda表达式为什么只能用于函数式接口中

lambda表达式实际上就是重写了接口中的抽象方法,在函数式接口中只有一个抽象方法,此时编译器会认定重写的就是该唯一的抽象方法。
倘若一个接口中有多个抽象方法,而lambda表达式是没有匿名的,编译器就无法判断其重写的是哪个抽象方法了。

 

forEach方法

在jdk8中的java.lang.Iterable接口中新增了非抽象的forEach方法。可以使用该方法配合lambda来遍历集合。

 1     public static void main(String[] args) {
 2 
 3         List<Integer> list = new ArrayList<>();
 4         list.add(1);
 5         list.add(2);
 6         list.add(3);
 7         list.add(4);
 8         list.add(5);
 9         list.add(6);
10 
11         //java8之前
12         for(Integer i : list){
13             System.out.println(i);
14         }
15 
16 
17         // Java 8之后,在Iterator接口中新增了非抽象的forEach方法:
18         list.forEach((Integer n) -> {System.out.println(n);});
19 
20         //如果在lambda表达式中只有一个参数一行语句的时候,可以简写为下面格式
21         list.forEach(n -> System.out.println(n));
22 
23         // Java 8新增方法引用,方法引用由::双冒号操作符标示
24         list.forEach(System.out::println);
25 
26     }

 

方法引用

方法引用主要是用来简写lambda表达式,有些lambda表达式里面仅仅是执行一个方法调用,这时使用方法引用可以简写lambda表达式。

 1     public static void main(String[] args) {
 2         Integer[] num = {5,8,13,6};
 3 
 4         //不使用lambda
 5         Arrays.sort(num, new Comparator<Integer>(){
 6 
 7             @Override
 8             public int compare(Integer i1, Integer i2) {
 9 
10                 return Integer.compare(i1, i2);
11             }
12 
13         });
14 
15         //使用lambda
16         Arrays.sort(num, (x,y) -> Integer.compare(x, y));
17 
18         //方法引用
19         Arrays.sort(num, Integer :: compare);
20 
21     }

 

一共有四种类型的方法引用
静态方法引用,类名::方法名
某个对象的引用,对象变量名::方法名
特定类的任意对象的方法引用,类名::方法名
构造方法,类名::new

 

下面代码演示构造方法的引用,先创建一个Car类型,添加一个buy方法,为了能够使用lambda表达式,这里使用java.util.function.supplier接口,这个接口是java8新增的一个函数式接口,里面只有一个抽象的get方法。

1 public class Car {
2 
3     public static Car buy(Supplier<Car> s){
4 
5         //通过get方法获取传入的Car类型的对象
6         return s.get();
7     }
8 }

 

创建一个测试类:

 1 /**
 2  * 构造方法引用
 3  *
 4  */
 5 public class LambdaTest05 {
 6 
 7     public static void main(String[] args) {
 8         Car car = Car.buy(Car :: new);
 9         System.out.println(car);
10     }
11 
12 }

 

 

Java8新特性之Stream

stream简介

jdk8中新增stream API,需要注意的是该stream跟之前学习的IO流没有关系,这个stream主要是用来处理集合数据的,可以将其看做是一个高级迭代器。在Collection接口中新增了非抽象的stream方法来获取集合的流。使用stream后可以写出更简洁的代码来处理集合中的数据。

 1     public static void main(String[] args) {
 2         List<Student> stuList = new ArrayList<>(10);
 3 
 4         stuList.add(new Student("刘一", 85));
 5         stuList.add(new Student("陈二", 90));
 6         stuList.add(new Student("张三", 98));
 7         stuList.add(new Student("李四", 88));
 8         stuList.add(new Student("王五", 83));
 9         stuList.add(new Student("赵六", 95));
10         stuList.add(new Student("孙七", 87));
11         stuList.add(new Student("周八", 84));
12         stuList.add(new Student("吴九", 100));
13         stuList.add(new Student("郑十", 95));
14 
15         //需求:列出90分以上的学生姓名,并按照分数降序排序
16 
17         //以前的写法,代码较多,每个操作都需要遍历集合
18         List<Student> result1 = new ArrayList<>(10);
19 
20         //遍历集合获取分数大于90以上的学生并存放到新的List中
21         for(Student s : stuList){
22             if(s.getScore() >= 90){
23                 result1.add(s);
24             }
25         }
26 
27         //对List进行降序排序
28         result1.sort(new Comparator<Student>(){
29 
30             @Override
31             public int compare(Student s1, Student s2) {
32                 //降序排序
33                 return Integer.compare(s2.getScore(), s1.getScore());
34             }
35 
36         });
37 
38         System.out.println(result1);
39 
40         //使用Stream的写法
41         /*
42          * 1.获取集合的stream对象
43          * 2.使用filter方法完成过滤
44          * 3.使用sort方法完成排序
45          * 4.使用collect方法将处理好的stream对象转换为集合对象
46          */
47         result1 = stuList.stream()
48                 .filter(s -> s.getScore()>=90)
49                 //.sorted((s1,s2) -> Integer.compare(s2.getScore(), s1.getScore()))
50                 //使用Comparator中的comparing方法
51                 .sorted(Comparator.comparing(Student :: getScore).reversed())
52                 .collect(Collectors.toList());
53         System.out.println(result1);
54     }

 

map和reduce

  • map用来归类,结果一般是一组数据,比如可以将list中的学生分数映射到一个新的stream中
  • reduce用来计算值,结果是一个值,比如计算最高分
 1 /**
 2  * map和reduce
 3  *
 4  */
 5 public class StreamTest03 {
 6 
 7     public static void main(String[] args) {
 8         //初始化List数据同上
 9         List<Student> list = InitData.getStudent();
10 
11         //使用map方法获取list数据中的name
12         List<String> names = list.stream()
13                                     .map(Student::getName)
14                                     .collect(Collectors.toList());
15         System.out.println(names);
16 
17         //使用map方法获取list数据中的name的长度
18         List<Integer> length = list.stream()
19                                     .map(Student::getName)
20                                     .map(String::length)
21                                     .collect(Collectors.toList());
22         System.out.println(length);
23 
24         //将每人的分数-10
25         List<Integer> score = list.stream()
26                 .map(Student::getScore)
27                 .map(i -> i - 10)
28                 .collect(Collectors.toList());
29         System.out.println(score);
30 
31 
32         //计算学生总分
33         Integer totalScore1 = list.stream()
34                 .map(Student::getScore)
35                 .reduce(0,(a,b) -> a + b);
36         System.out.println(totalScore1);
37 
38         //计算学生总分,返回Optional类型的数据,改类型是java8中新增的,主要用来避免空指针异常
39         Optional<Integer> totalScore2 = list.stream()
40                 .map(Student::getScore)
41                 .reduce((a,b) -> a + b);
42         System.out.println(totalScore2.get());
43 
44         //计算最高分和最低分
45         Optional<Integer> max = list.stream()
46                                     .map(Student::getScore)
47                                     .reduce(Integer::max);
48         Optional<Integer> min = list.stream()
49                                     .map(Student::getScore)
50                                     .reduce(Integer::min);
51 
52         System.out.println(max.get());
53         System.out.println(min.get());
54 
55     }
56 
57 }

 

在java8中新增了三个原始类型流来解决这个问题:
IntStream、DoubleStream、LongStream

 1 /**
 2  * 数值流
 3  *
 4  */
 5 public class StreamTest04 {
 6 
 7     public static void main(String[] args) {
 8         List<Student> list = InitData.getStudent();
 9 
10         //将stream转换为IntStream
11         int totalScore = list.stream()
12                                 .mapToInt(Student::getScore)
13                                 .sum();
14         System.out.println(totalScore);
15 
16         //计算平均分
17         OptionalDouble avgScore = list.stream()
18                             .mapToInt(Student::getScore)
19                             .average();
20         System.out.println(avgScore.getAsDouble());
21 
22         //生成1~100之间的数字
23         IntStream num = IntStream.rangeClosed(1, 100);
24 
25         //计算1~100之间的数字中偶数的个数
26         long count = IntStream.rangeClosed(1, 100)
27                     .filter(n -> n%2 == 0)
28                     .count();
29         System.out.println(count);
30     }
31 
32 }

 

创建流

除了上面的流之外,我们还可以自己创建流。
下面代码中展示了三种创建流的方式:

 1    //使用Stream.of创建流
 2     Stream<String> str =  Stream.of("i","love","this","game");
 3     str.map(String::toUpperCase).forEach(System.out::println);
 4 
 5     //使用数组创建流
 6     int[] num = {2,5,9,8,6};
 7     IntStream intStream = Arrays.stream(num);
 8     int sum = intStream.sum();//求和
 9     System.out.println(sum);
10 
11     //由函数生成流,创建无限流
12     Stream.iterate(0, n -> n+2)
13             .limit(10)
14             .forEach(System.out::println);

 

Optional简介

空指针异常是在学习和开发中最常见的问题之一,为了解决这个问题,在java8中新增了Optional类。这个类在java.util包下,使用这个类可以更好的支持函数式编程,并且可以简化以前对null的判断。

 1 /**
 2  * Optional简介
 3  *
 4  */
 5 public class OptionalTest01 {
 6 
 7     public static void main(String[] args)   {
 8         List<Student> stuList = InitData.getStudent();
 9         Optional<Integer> count = stuList.stream()
10                 .filter(s -> s.getScore()<60)
11                 .map(Student::getScore)
12                 .reduce((a,b) -> a+b);
13         System.out.println(count.orElse(0));
14 
15 
16         Map<Integer,String> map = new HashMap<>();
17         map.put(1001, "篮球");
18         map.put(1002, "足球");
19         map.put(1003, "羽毛球");
20         map.put(1004, "乒乓球");
21         String sport = Optional.ofNullable(map.get(1005))
22                                         .orElse("无");
23         System.out.println(sport);
24     }
25 
26 
27 }

 

 

 

遍历集合的四种方式

下面以ArrayList为例展示一下遍历集合的四种方式,首先初始化一个ArrayList并填充一些测试数据

1     List<Integer> list = new ArrayList<>();
2 
3     for (int i = 0; i < 10; i++) {
4         list.add(i);
5     }

 

遍历集合方式一:使用普通for循环:

1     for (int i = 0; i < list.size(); i++) {
2         System.out.println(list.get(i));
3     }

 

遍历集合方式二:增强for循环,底层就是迭代器:

1     for (Integer i:list){
2         System.out.println(i);
3     }

 

遍历集合方式三:迭代器

1     Iterator<Integer> iterator = list.iterator();
2     while(iterator.hasNext()){
3         Integer i = iterator.next();
4         System.out.println(i);
5     }

 

遍历集合方式四:使用Iterable接口中jdk1.8新增的default方法forEach+lambda表达式

1     list.forEach((i) -> {System.out.println(i); });//普通lambda
2     list.forEach(i -> System.out.println(i));//只有一行语句时可以简写的形式
3     list.forEach(System.out :: println);//方法引用

 

四种迭代方式对比

  • 普通for循环
    此种方式在遍历ArrayList时效率会高一些,因为ArrayList底层使用的是数组实现的,所以可以认为ArrayList中的元素都是有下标的,而此种普通for循环中的变量i可以快速的定位到ArrayList中的元素。
  • 增强for循环和迭代器
    可以认为增强for循环是迭代器的一种简便的写法,而迭代器比较适合遍历LinkedList,因为它底层使用的是链表的数据结构。
  • 使用forEach方法+lambda表达式
    如果你使用的是jdk8以上的版本,那么建议使用此种方式,该方式内部默认的使用增强for循环去遍历集合,不过在ArrayList类中重写了forEach方法,里面使用了普通的for循环去遍历。不管你使用哪一种,这种方式底层会选择最优的遍历方式

Iterable接口中的forEach方法源码:

1 default void forEach(Consumer<? super T> action) {
2     Objects.requireNonNull(action);
3     for (T t : this) {
4         action.accept(t);
5     }
6 }

 

ArrayList中重写的forEach方法源码:

 1 @Override
 2 public void forEach(Consumer<? super E> action) {
 3     Objects.requireNonNull(action);
 4     final int expectedModCount = modCount;
 5     @SuppressWarnings("unchecked")
 6     final E[] elementData = (E[]) this.elementData;
 7     final int size = this.size;
 8     for (int i=0; modCount == expectedModCount && i < size; i++) {
 9         action.accept(elementData[i]);
10     }
11     if (modCount != expectedModCount) {
12         throw new ConcurrentModificationException();
13     }
14 }

 

因此建议使用jdk8中Iterable接口中新增的forEach方法遍历集合。

posted @ 2019-04-22 13:57  武士黄  阅读(224)  评论(0编辑  收藏  举报