jdk1.8 新特性
本文摘抄自网络,作为个人学习笔记
知识点:
- jdk1.8 对 hashmap 的优化
- Lambda 表达式
- 函数式接口
- 方法引用和构造器调用
- Stream API
- 接口中的默认方法和静态方法
- 新时间日期 API
map 新原理
在 jdk1.8 中对 hashMap 等 map 集合的数据结构进行了优化。原来的 hashMap 采用的数据结构是哈希表(数组+链表),hashMap 默认大小是 16,一个 0-15 索引的数组。怎么往里面存储元素呢?首先调用元素的 hashcode 方法,计算出 哈希码值,经过 哈希算法算成数组的索引值,如果对应的索引处没有元素,直接存放,如果有对象在,那么比较他们的 equals 方法,如果内容一样,后一个 value 会将前一个 value 的值覆盖,如果不一样,在 1.7 的时候,后加的放在前面形成一个链表,这就造成了“碰撞”,在某些情况下,如果链表无限下去,,那么效率极低,碰撞是避免不了的。
加载因子:0.75,数组扩容,达到总容量的 75%,就进行扩容,但是无法避免碰撞的情况发生。
在 1.8 之后,在 数组 + 链表 + 红黑树 来实现 hashmap,当碰撞的元素大于 8 & 总容量大于 64,会有红黑树的引入。除了添加之外,效率都比链表高,1.8 之后链表新进元素加到末尾。
ConcurrentHashMap(锁分段机制),concurrentLevel ,jdk1.8 采用 CAS 算法(无锁算法,不再使用锁分段)。
数组 + 链表 中也引入了红黑树的使用。
Lambda 表达式
Lambda 表达式本质上是一段匿名内部类,也可以是一段传递的代码
Comparartor<Integer> cpt = (x, y) -> Integer.compare(x, y);
TreeSet<Integer> set = new TreeSet<>(cpt);
Lambda 表达式的语法总结:() -> ();
注:当一个接口中存在多个抽象方法时,如果使用 lambda 表达式,并不能智能地匹配对应的抽象方法,因此引入了函数式接口的概念
函数式接口
函数式接口的提出是为了给 Lambda 表达式的使用提供更好的支持。
什么是函数式接口?
简单来说就是只定义了一个抽象方法的接口(Object 类的 public 方法除外),就是函数式接口,并且还提供了注解:@FunctionalInterface
常见的四大函数式接口:
- Consumer<T>:消费型接口,有参无返回值
// 消费型
public static void changeStr(String str, Consumer<String> con){
con.accept(str);
}// 调用
changeStr("hello", (str) -> System.out.println(str));
说明:这里方法的参数是字符串和 Consumer,Consumer 是接口,调用时需要实现这个接口,即实现了消费这个操作。
- Supplier<T>:供给型接口,无参有返回值
// 供给型
public static String getValue(Supplier<String> sup){ return sup.get(); }
// 调用
System.out.println(getValue(() -> "1111111"));说明:这里同上,返回的是个字符串,按理有个 return,但是 lambda 表达式说,按照我的规范,可以省略这个 return。
- Function<T, R>:函数式接口,有参数有返回值
// 函数式接口
public static Long changeNum(Long num, Function<Long, Long> function){ return function.apply(num); }// 调用
Long result = changeNum(100L, (x) -> x+200L); - Predicate<T>:断言型接口,有参有返回值,返回值是 boolean 类型
// 断言型 public static boolean changeBoolean(String str, Predicate<String> pre){ return pre.test(str); }
// 调用
boolean reboolean = changeBoolean("hello", (str)->str.length() > 5);
总结:函数式接口的提出是为了让我们更加方便的使用 lambda 表达式,不需要自己再手动创建一个函数式接口,直接拿来用就好了。
方法引用
若 lambda 体中的内容有方法已经实现了,那么可以使用 “方法引用”。也可以理解为方法引用是 lambda 表达式的另外一种表现形式,并且其语法比 lambda 表达式更加简单。
1)方法引用
三种表现形式:
1. 对象 :: 实例方法名
2. 类 :: 静态方法名
3. 类 :: 实例方法名(lambda 参数列表中第一个参数是实例方法的调用者,第二个参数是实例方法的参数时可用)
// 方法引用 /** * 1. lambda 体中调用方法的参数列表与返回值类型, * 要与函数式接口中抽象方法的参数列表和返回值保持一致 * 2. 若 lambda 参数列表中的第一个参数是实例方法的调用者, * 而第二个参数是实例方法的参数时,可以使用 ClassName::method */ Consumer<Integer> consumer = (x) -> System.out.println("x"); consumer.accept(100); // 方法引用 - 类名 :: 静态方法名 BiFunction<Integer, Integer, Integer> biFun = (x, y) -> Integer.compare(x, y); BiFunction<Integer, Integer, Integer> biFun2 = Integer::compare; Integer resBi = biFun2.apply(1200, 200); System.out.println("resBi = " + resBi); // 方法引用 - 类名 :: 实例方法名 BiFunction<String, String, Boolean> fun1 = (str1, str2) -> str1.equals(str2); BiFunction<String, String, Boolean> fun2 = String::equals; Boolean resB2 = fun2.apply("hello", "hello"); System.out.println("resB2 = " + resB2);
2)构造器引用
格式:ClassName :: new
Supplier<Product> sup = () -> new Product(); System.out.println("sup.get() = " + sup.get()); Supplier<Product> sup2 = Product::new; System.out.println("sup2.get() = " + sup2.get());
// 一个参数的情况 Function<String, Product> bifun2 = Product::new; BiFunction<Integer, String, Product> bifun = (x, y) -> new Product(y, x);
3)数组引用
格式:Type[] :: new
// 数组引用 Function<Integer, String[]> func = (x) -> new String[x]; Function<Integer, String[]> func2 = String[] :: new ; String[] strArray = func2.apply(10); Arrays.stream(strArray).forEach(System.out::println);
Strean API
Stream 操作的三个步骤
- 创建 stream
- 中间操作(过滤、map)
- 终止操作
stream 的创建:
// Stream 操作 // 1. 通过 Collection 系列集合提供的 stream() 或者 paralleStream() List<String> slist = new ArrayList<>(); Stream<String> stream1 = slist.parallelStream(); Stream<String> stream11 = slist.stream(); // 2. 通过 Arrays 的静态方法 stream() 获取数组流 String[] strArr = new String[10]; Stream<String> stream2 = Arrays.stream(strArr); // 3. 通过 Stream 类的静态方法 of Stream<String> stream3 = Stream.of("aa", "nn", "cc"); // 4. 创建无限流 // 迭代 Stream<Integer> stream4 = Stream.iterate(0, (x)->x+2); // 生成 Stream.generate(()->Math.random());
stream 的中间操作(Intermediate):一个流可以后面跟随 0 个或者多个 intermediate 操作。其目的主要是打开流,做出某种程度的数据映射 / 过滤,然后返回一个新的流,交给下一个操作者使用。这类操作都是惰性化的(lazy),就是说,仅仅调用到这类方法,并没有真正开始流的遍历。
// 筛选、过滤、去重 list.stream() .filter(e -> e.getPrice()>20) .limit(3) .skip(1) .forEach(System.out::println); System.out.println("---------------------"); // 通过 map 映射,生成新的流 list.stream() .map(e -> {if(e.getPrice()>20){return e;}else{return null;}}) .forEach(System.out::println); System.out.println("++++++++++++++++++++++"); // 做排序 list.stream() .sorted((p111, p222) -> p111.getPrice().compareTo(p222.getPrice()) ) .forEach(System.out::println);
stream 的终止操作(Terminal):一个流只能有一个 terminal 操作,当这个操作执行后,流就被用光了,无法再被操作。所以这必定是流的最后一个操作。Terminal 操作的执行,才会真正的开始流的遍历,并且会生成一个结果。
/** * 查找和匹配 * allMatch 检查是否匹配所有元素 * anyMatch 检查是否至少匹配一个元素 * noneMatch 检查是否没有匹配所有的元素 * findFirst 返回第一个元素 * findAny 返回当前流中的任意元素 * count 返回流中元素的总个数 * max 返回流中最大值 * min 返回流中最小值 */ boolean allMatchRes = list.stream().allMatch((e) -> e.getPrice()>30); System.out.println("allMatchRes = " + allMatchRes); boolean anyMatchRes = list.stream().anyMatch(e -> e.getPrice()>30); System.out.println("anyMatchRes = " + anyMatchRes); boolean noneMatchRes = list.stream().noneMatch(e ->e.getPrice() > 50); System.out.println("noneMatchRes = " + noneMatchRes); Optional<Product> findFirstRes = list.stream().findFirst(); System.out.println("findFirstRes = " + findFirstRes.get());
还有两个强大的终止操作 reduce 和 collect 。
reduce 操作是将流中元素反复结合起来,得到一个值
/** * reduce : 规约操作 */ List<Integer> numList = Arrays.asList(1,2,3,4,5,6,7,8,9,10); Integer count = numList.stream().reduce(0, (x, y) -> x+y); System.out.println("count = " + count); Optional<Integer> reduce = list.stream()
.map(Product::getPrice)
.reduce(Integer::sum); System.out.println("reduce = " + reduce);
collect 操作:collect 操作将流转换成其他形式,接收一个 Collection 接口的实现,用于给 Stream 中元素做汇总的方法
/** * collect 收集操作 */ List priceList = list.stream() .map(Product :: getPrice) .collect(Collectors.toList()); priceList.stream().forEach(System.out :: println);
在对于一个 Stream 进行多次转换操作(Intermediate操作),每次都对 Stream 的每个元素进行转换,而且是执行多次,这样的时间复杂度就是 N 个 for 循环里把所有操作都做完的总和吗?其实不是这样的,转换操作都是 lazy 的,多个转换操作只会在 Terminal 操作的时候融合起来,一次循环完成。我们可以这样理解:Stream 里有个操作函数的集合,每次转换操作就是把转换函数放入集合中,在 Terminal 操作的时候循环 Stream 对应的集合,然后对每个元素执行所有的函数。so easy !
并行流和串行流
在 jdk1.8 新的 stream 包中,针对集合的操作提供了并行操作流和串行操作流。并行流就是把内容切割成多个数据块,并且使用多个线程分别处理每个数据块的内容。Stream api 中声明可以通过 parallel() 与 sequential() 方法在并行流和串行流之间进行切换。
jdk1.8 并行流使用的是 fork / join 框架进行并行操作。
ForkJoin 框架
Fork / Join 框架:就是在必要的情况下,将一个大的任务,进行拆分(fork)成若干小任务(拆分到不能拆分时),再将一个个的小任务运算结果进行 join 汇总。
关键字:递归分合,分而治之
减少了线程的等待时间,提高了性能。
Optional 容器
使用 Optional 容器可以快速的定位 NPE,并且在一定程度上可以减少对参数非空检验的代码量。
接口中可以定义默认实现方法和静态方法
在接口中可以使用 default 和 static 关键字来修饰接口中的普通方法。
在 jdk1.8 中很多接口会新增方法,为了保证 1.8 向下兼容, 1.7 版本中的接口实现类不用每个都重新实现新添加的接口方法,引入了 default 默认实现,static 的用法是直接用接口调用方法即可。当一个类继承父类又实现接口时,若后两者方法名相同,则优先继承父类中的同名方法,即“类优先”,如果是实现两个同名方法的接口,则要求实现类必须手动声明默认实现哪个接口中的方法。
新的日期 API LocalDate | LocalTime | LocalDateTime
新的日期 API 都是不可变的,更适用于多线程的使用环境中
LocalDateTime dateTime = LocalDateTime.now(); System.out.println("dateTime = " + dateTime); System.out.println(dateTime.getYear()); System.out.println(dateTime.getMonthValue()); System.out.println(dateTime.getDayOfMonth()); System.out.println(dateTime.getHour()); System.out.println(dateTime.getMinute()); System.out.println(dateTime.getSecond()); System.out.println(dateTime.getNano()); // 纳秒 LocalDateTime dateTime2 = LocalDateTime.of(2020, 3, 31, 20, 9); System.out.println("dateTime2 = " + dateTime2); LocalDateTime dateTime3 = dateTime2.plusHours(3); System.out.println("dateTime3 = " + dateTime3); Instant ins = Instant.now(); System.out.println("ins = " + ins);
... 未完不续了 ...
表示日期的 LocalDate
表示时间的 LocalTime
表示日期时间的 LocalDateTime
新的日期 API 的几个优点:
之前使用的 java.util.Date 月份从 0 开始,我们一般会 加1 使用,很不方便,java.time.LocalDate 月份和星期都改成 enum
java.util.Date 和 SimpleDateFormate 都不是线程安全的,而 LocalDate 和 LocalTime 和最基本的 String 一样,是不变类型,不但线程安全,而且不能修改
java.util.Date 是一个“万能接口”,它包含日期、时间、毫秒数,新接口更好用的原因是考虑到了日期的操作,经常发生往前或往后推几天的情况,用 java.util.Date 配合Calendar 代码较繁琐。
https://blog.csdn.net/qq_29411737/article/details/80835658