关于java8的新特性
- 速度更快
- 修改底层数据结构:HashMap(数组-链表-红黑数),HashSet,ConcurrentHashMap(CAS算法)
- 修改垃圾回收机制(内存结构):取消堆中的永久区(PremGen)->回收条件苛刻,使用元空间(MetaSpace)->直接使用物理内存->加载类文件
底层数据结构:最核心HashMap:
HashMap如果不用哈希算法,用equals,效率极低。对比HashCode,没有直接进来。相同,通过equals内容比较不同,形成链表,后加的放前边,称为碰撞,但碰撞应该避免,因为链表元素过多,效率低。
equals和HashCode重写方法严谨一点,但还是避免不了,因为数组的索引值就几个。方法,提供加载因子,默认0,75,当元素到达哈希表的75%,进行扩容。把链表里面的元素进行重排序,新位置,碰撞概率变低。
但极端情况,查找元素,查找到链表,查找到最后一个,查找效率变低。
所以jdk1.8后,数组+链表+红黑数(二叉树一种)
链表元素长度(个数)大于8,容量大于64,转换为红黑树。
链表转为红黑树后,除了添加以外,其他的效率都高了。添加,链表直接在后面添加,在红黑树要比较大小后才添加。
扩容以后,换位置不用重新计算,在原来哈希表的总长度+当前的位置,原来5,+3,在第8个位置。
HashSet变了,ConcurrenHashMap变了,改为CAS算法(无锁算法)
底层数据结构:内存
永久区没有了,JVM调优,PremGenSize,MaxPremGenSize没有了。取而代之,MetaspaceSize,MaxMetaSpaceSize。
- 代码更少(增加了新的语法 Lambda 表达式)
- 强大的 Stream API
- 便于并行
- 最大化减少空指针异常 Optional
1.Lambda 表达式
Lambda 是一个 匿名函数,我们可以把 Lambda表达式理解为是 一段可以传递的代码(将代码像数据一样进行传递)。可以写出更简洁、更灵活的代码。作为一种更紧凑的代码风格,使Java的语言表达能力得到了提升。
1-1.从匿名类到Lambda的转换:
1 | //原来的匿名内部类 |
代码例子:
1 | //原来的匿名内部类 |
1-2.具体例子:
一个实体类,接口,两个实现类,优化方式
1 | //实体类 |
主代码:
1 | //数组转集合,输入数据 |
优化方式:
1 | //优化方式一:策略设计模式,使用接口 |
1-3.Lambda表达式的基础介绍
Java8中引入了一个新的操作符 “->” 该操作符称为箭头操作符或 Lambda 操作符。箭头操作符将 Lambda 表达式拆分成两部分:
- 左侧:Lambda 表达式的参数列表(接口中抽象方法)
- 右侧:Lambda 表达式中所需执行的功能, 即 Lambda体。(接口中抽象方法的实现)
1.4.基础语法
- 语法格式一:无参数,无返回值
- () -> System.out.println(“Hello Lambda!”);
1 |
|
- 语法格式二:有一个参数,并且无返回值
- (x) -> System.out.println(x)
1 |
|
- 语法格式三:若只有一个参数,小括号可以省略不写
- x -> System.out.println(x)
- 语法格式四:有两个以上的参数,有返回值,并且 Lambda 体中有多条语句
- Comparator
com = (x, y) -> {System.out.println(“函数式接口”); - return Integer.compare(x, y);
- };
- Comparator
1 |
|
- 语法格式五:若 Lambda 体中只有一条语句, return 和 大括号都可以省略不写
- Comparator
com = (x, y) -> Integer.compare(x, y);
- Comparator
- 语法格式六:Lambda 表达式的参数列表的数据类型可以省略不写,因为JVM编译器通过上下文推断出,数据类型,即“类型推断”
- (Integer x, Integer y) -> Integer.compare(x, y);
1 |
|
- 左右遇一括号省
- 左侧推断类型省
- 能省则省
2.Lambda 表达式需要“函数式接口”的支持
- 函数式接口:接口中只有一个抽象方法的接口,称为函数式接口。
- 可以通过 Lambda 表达式来创建该接口的对象。(若 Lambda表达式抛出一个受检异常,那么该异常需要在目标接口的抽象方法上进行声明)。
- 可以使用注解 @FunctionalInterface 修饰,可以检查它是否是一个函数式接口,同时 javadoc 也会包含一条声明,说明这个接口是一个函数式接口。
1 | 函数式接口中使用泛型 |
为了将 Lambda 表达式作为参数传递,接收 Lambda 该 表达式的参数类型必须是与该 Lambda 表达式兼容的函数式接口的类型。
代码:
接口,需求实现方式
1 | //只能一个抽象方法,多余一个,报错 |
做习题:
1.
1 | //数据添加 |
2.
1 | //接口 |
3.
1 | //接口 |
2-2.Java提供接口
四大内置的四大核心函数式接口:
- Consumer
: 消费型接口 - void accept(T t);
1 | //Consumer<T> 消费型接口 : |
- Supplier
: 供给型接口 - T get();
1 | //Supplier<T> 供给型接口 : |
- Function<T, R> : 函数型接口
- R apply(T t);
1 | //Function<T, R> 函数型接口: |
- Predicate
: 断言型接口 - boolean test(T t);
1 | //Predicate<T> 断言型接口: |
3.方法引用与构造器引用
3-1.方法引用
若 Lambda 体中的功能,已经有方法提供了实现,可以使用方法引用(可以将方法引用理解为 Lambda 表达式的另外一种表现形式)
实现抽象方法的参数列表,必须与方法引用方法的参数列表保持一致.
方法引用:使用操作符 “ ::” 将方法名和对象或类的名字分隔开来。
1 | public void test1(){ |
注意:
方法引用所引用的方法的参数列表与返回值类型,需要与函数式接口中抽象方法的参数列表和返回值类型保持一致!
- 若Lambda 的参数列表的第一个参数,是实例方法的调用者,第二个参数(或无参)是实例方法的参数时,格式: ClassName::MethodName
- 对象的引用 :: 实例方法名
1 | //对象的引用 :: 实例方法名 |
- 类名 :: 静态方法名
1 | //类名 :: 静态方法名 |
- 类名 :: 实例方法名
1 | //类名 :: 实例方法名 |
3-2.构造器引用
与函数式接口相结合,自动与函数式接口中方法兼容。可以把构造器引用赋值给定义的方法,与构造器参数列表要与接口中抽象方法的参数列表一致!
类名 :: new
1 | //构造器引用 |
3-3.数组引用
类型[] :: new;
1 | //数组引用 |
4.Stream API
Stream 是 Java8 中处理集合的关键抽象概念,它可以指定你希望对集合进行的操作,可以执行非常复杂的查找、过滤和映射数据等操作。使用Stream API 对集合数据进行操作,就类似于使用 SQL 执行的数据库查询。也可以使用 Stream API 来并行执行操作。简而言之,Stream API 提供了一种高效且易于使用的处理数据的方式。
流 (Stream) 是数据渠道,用于操作数据源(集合、数组等)所生成的元素序列。
集合讲的是数据,流讲的是计算
注意:
- Stream 自己不会存储元素。
- Stream 不会改变源对象。相反,他们会返回一个持有结果的新Stream。
- Stream 操作是延迟执行的。这意味着他们会等到需要结果的时候才执行。
4-1.Stream 的操作三个步骤
- 创建 Stream
一个数据源(如:集合、数组),获取一个流 - 中间操作
一个中间操作链,对数据源的数据进行处理 - 终止操作( ( 终端操作) )
一个终止操作,执行中间操作链,并产生结果
4-2.创建 Stream
- Java8 中的 Collection 接口被扩展,提供了两个获取流的方法 :
1 | default Stream<E> stream() : 返回一个顺序流 |
- Java8 中的 Arrays 的静态方法 stream() 可以获取数组流
1
static <T> Stream<T> stream(T[] array): 返回一个流
- 重载形式,能够处理对应基本类型的数组
1 | public static IntStream stream(int[] array) |
- 可以使用静态方法 Stream.of(), 通过显示值创建一个流。它可以接收任意数量的参数
1 | public static<T> Stream<T> of(T... values) : 返回一个流 |
- 可以使用静态方法 Stream.iterate() 和Stream.generate(), 创建无限流
1 | //迭代 |
例子: Collection 接口,由数组创建流,由值创建流,由函数创建流:创建无限流
1 | //1. 创建 Stream |
4-3.Stream 的中间操作
多个 中间操作可以连接起来形成一个 流水线,除非流水线上触发终止操作,否则 中间操作不会执行任何的处理。
而在终止操作时一次性全部 处理,称为“惰性求值”
方法:筛选与切片,映射,排序
筛选与切片
1 | //2. 中间操作 |
映射
1 | /* |
排序
1 | /* |
4-4.Stream 的终止操作
操作会从流的流水线生成结果。其结果可以是任何不是流的值,例如:List、Integer,甚至是 void
方法:
查找与匹配
实体类加个方法:
1 | public class Employee { |
测试类:
1 | public class TestStreamAPI2 { |
归约::map 和 reduce 的连接通常称为 map-reduce 模式
1 | /* |
收集
- Collector 接口中方法的实现决定了如何对流执行收集操作(如收集到 List、Set、Map)。但是 Collectors 实用类提供了很多静态方法,可以方便地创建常见收集器实例,
1 | //collect——将流转换为其他形式。接收一个 Collector接口的实现,用于给Stream中元素做汇总的方法 |
4-5.练习题
Stream
1 | /* |
综合例子:实体类,解决问题类
1 | //交易员类 |
5.并行流与串行流
并行流,是把一个内容分成多个数据块,并用不同的线程分别处理每个数据块的流。
Java 8 中将并行进行了优化,对数据进行并行操作。Stream API 可以声明性地通过 parallel() 与
sequential() 在并行流与顺序流之间进行切换。
Fork/Join 框架
Fork/Join 框架:就是在必要的情况下,将一个大任务,进行拆分(fork)成若干个小任务(拆到不可再拆时),再将一个个的小任务运算的结果进行 join 汇总.
Fork/Join 框架与传统线程池的区别
采用 “工作窃取”模式(work-stealing):
当执行新的任务时它可以将其拆分分成更小的任务执行,并将小任务加到线程队列中,然后再从一个随机线程的队列中偷一个并把它放在自己的队列中。
相对于一般的线程池实现,fork/join框架的优势体现在对其中包含的任务的处理方式上。在一般的线程池中,如果一个线程正在执行的任务由于某些原因无法继续运行,那么该线程会处于等待状态。
而在fork/join框架实现中,如果某个子问题由于等待另外一个子问题的完成而无法继续运行。那么处理该子问题的线程会主动寻找其他尚未运行的子问题来执行,这种方式减少了线程的等待时间,提高了性能 。
ForkJoin框架
1 | public class ForkJoinCalculate extends RecursiveTask<Long>{ |
测试类:
1 | public class TestForkJoin { |
6.Optional 类
Optional
常用方法:
Optional.ofNullable(T t) | 若 t 不为 null,创建 Optional 实例,否则创建空实例 |
---|---|
Optional.of(T t) | 创建一个 Optional 实例 |
Optional.empty() | 创建一个空的 Optional 实例 |
isPresent() | 判断是否包含值 |
orElse(T t) | 如果调用对象包含值,返回该值,否则返回t |
orElseGet(Supplier s) | 如果调用对象包含值,返回该值,否则返回 s 获取的值 |
map(Function f) | 如果有值对其处理,并返回处理后的Optional,否则返回 Optional.empty() |
flatMap(Function mapper) | 与 map 类似,要求返回值必须是Optional |
Optional 容器类:用于尽量避免空指针异常
1 | /* |
例题:
1 | //类 |
1 | //例题 |
7.接口中的默认方法与静态方法
Java 8中允许接口中包含具有具体实现的方法,该方法称为“默认方法”,默认方法使用 default 关键字修饰。
如下面:MyFun
1 | public interface MyFun { |
接口默认方法的 ” 类优先 ”原则
若一个接口中定义了一个默认方法,而另外一个父类或接口中又定义了一个同名的方法时
- 选择父类中的方法。如果一个父类提供了具体的实现,那么接口中具有相同名称和参数的默认方法会被忽略。
- 接口冲突。如果一个父接口提供一个默认方法,而另一个接口也提供了一个具有相同名称和参数列表的方法(不管方法是否是默认方法),那么必须覆盖该方法来解决冲突
Java8 中,接口中允许添加静态方法。
例子:
1 | public interface MyFun { |
测试类:****
1 | public class SubClass extends MyClass implements MyFun{ |
8.新时间日期 API
例子:
1 | //调用的方法 |
8-1.使用 LocalDate 、LocalTime 、LocalDateTime
LocalDate、LocalTime、LocalDateTime 类的实例是不可变的对象,分别表示使用 ISO-8601日历系统的日期、时间、日期和时间。它们提供了简单的日期或时间,并不包含当前的时间信息。也不包含与时区相关的信息。
1 | //1. LocalDate、LocalTime、LocalDateTime |
8-2.Instant
用于“时间戳”的运算。它是以Unix元年(传统的设定为UTC时区1970年1月1日午夜时分)开始所经历的描述进行运算
1 | //2. Instant : 时间戳。 (使用 Unix 元年 1970年1月1日 00:00:00 所经历的毫秒值) |
8-3.Duration 和 Period
- Duration:用于计算两个“时间”间隔
- Period:用于计算两个“日期”间隔
1 | //Duration : 用于计算两个“时间”间隔 |
8-4.日期的操纵
- TemporalAdjuster : 时间校正器。有时我们可能需要获取例如:将日期调整到“下个周日”等操作。
- TemporalAdjusters : 该类通过静态方法提供了大量的常
用 TemporalAdjuster 的实现。
1 | //4. TemporalAdjuster : 时间校正器 |
8-5.解析与格式化
java.time.format.DateTimeFormatter 类:该类提供了三种格式化方法:
- 预定义的标准格式
- 语言环境相关的格式
- 自定义的格式
1 | //5. DateTimeFormatter : 解析和格式化日期或时间 |
8-6.时区的处理
带时区的时间为分别为:ZonedDate、ZonedTime、ZonedDateTime
其中每个时区都对应着 ID,地区ID都为 “{区域}/{城市}”的格式
例如 :Asia/Shanghai 等
- ZoneId:该类中包含了所有的时区信息
- getAvailableZoneIds() : 可以获取所有时区时区信息
- of(id) : 用指定的时区信息获取 ZoneId 对象
1 | //6.ZonedDate、ZonedTime、ZonedDateTime : 带时区的时间或日期 |
8-7.与传统日期处理的转换
9.重复注解与类型注解
Java 8对注解处理:可重复的注解及可用于类型的注解
1 | //类型注解 TYPE_PARAMETER |
测试类
1 | /* |