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方法遍历集合。