总览
- jdk1.8对hashMap等map集合的优化
- Lambda表达式
- 函数式接口
- 方法引用和构造器调用
- Stream API
- 并行流和串行流
- Optional容器
- Java 8引入Optional类来
防止空指针异常
, - Optional类最先是由Google的Guava项目引入的。
- Optional类实际上是个容器:它可
以保存类型T的值
,或者保存null
。 - 使用Optional类我们就
不用显式进行空指针检查
了。
- Java 8引入Optional类来
- 接口中的默认方法和静态方法
- 新时间日期API
- 定义可重复的注解
- 在
Java 5中使用注解有一个限制
,即相同的注解在同一位置只能声明一次
。 - Java 8引入
重复注解
,这样相同的注解在同一地方也可以声明多次
。 重复注解机制
本身需要用@Repeatable注解
。- Java 8在编译器层做了优化,相同注解会
以集合的方式保存
,因此底层的原理并没有变化。
- 在
- 扩展注解的支持
- Java 8扩展了
注解的上下文
, 几乎可以为任何东西
添加注解,包括局部变量
、泛型类
、父类与接口的实现
,连方法的异常
也能添加注解。
- Java 8扩展了
- jvm中的
方法区
变成了元数据区
- (
PermGen
变成了Metaspace
)
- (
更好的类型推测机制
- (不需要太多的强制类型转换了)
编译器优化
:- Java 8
将方法的参数名加入了字节码中
, - 这样在
运行时 通过反射 就能获取到参数名
, - 只需要在编译时使用-parameters参数。
- Java 8
对hashMap等map集合的优化
hashMap数据结构的优化:
原来的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表达式本质上是一段匿名内部类,也可以是一段可以传递的代码。
先来体验一下lambda最直观的优点:简洁代码
//匿名内部类
Comparator<Integer> cpt = new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
return Integer.compare(o1,o2);
}
};
TreeSet<Integer> set = new TreeSet<>(cpt);
System.out.println("=========================");
//使用lambda表达式
Comparator<Integer> cpt2 = (x,y) -> Integer.compare(x,y);
TreeSet<Integer> set2 = new TreeSet<>(cpt2);
只需要一行代码,极大减少代码量!!
我们发现实际上循环过滤方法的核心
就只有if语句中的条件判断
,
其他均为模版代码,
每次变更一下需求,都需要新增一个方法,
然后复制黏贴,假设这个过滤方法有几百行,那么这样的做法难免笨拙了一点。如何进行优化呢?
方法1:使用lamabda表达式
定义一个MyPredicate接口
public interface MyPredicate <T> {
boolean test(T t);
}
定义过滤方法:
//过滤方法。
public List<Product> filterProductByPredicate(List<Product> list, MyPredicate<Product> mp){
//定义一个list
List<Product> prods = new ArrayList<>();
//遍历参数中的list
for (Product prod : list){
//如果经过了 test方法的 返回为 true
if (mp.test(prod)){
//就 假如到 定义的 list中
prods.add(prod);
}
}
//返回 定义的list
return prods;
}
使用lambda表达式进行过滤
@Test
public void test4(){
//定义一个list
List<Product> proList = new ArrayList<>();
Product pp=new Product();
pp.setPrice(500);
proList.add(pp);
List<Product> products = filterProductByPredicate(proList, (p) -> p.getPrice() < 8000);
for (Product pro : products){
System.out.println(pro.getPrice());
}
}//500 是 满足 < 8000 ,会被加入到 list中。
//filterProductByPredicate 调用的 上面的方法。第一个参数为:proList。
//第二个参数为: MyPredicate<Product>,MyPredicate是接口接口中有test方法。boolean test(T t);
//而 (p) -> p.getPrice() < 8000 即是 对它的实现。
方法2:使用Stream API
甚至不用定义过滤方法,直接在集合上进行操作。
@Test
public void test(){
//定义一个list
List<Product> proList = new ArrayList<>();
Product pp=new Product();
pp.setPrice(500);
pp.setColor("红色");
pp.setName("张三");
proList.add(pp);
// 根据价格过滤
proList.stream()
.filter((p) -> p.getPrice() <8000)
.limit(2)
.forEach(System.out::println);
// 根据颜色过滤
proList.stream()
.filter((p) -> "红色".equals(p.getColor()))
.forEach(System.out::println);
// 遍历输出商品名称
proList.stream()
.map(Product::getName)
.forEach(System.out::println);
}
com.example.demo.lambdademo.Product@2ef5e5e3
com.example.demo.lambdademo.Product@2ef5e5e3
张三
Lamabda表达式的语法总结: () -> ();
口诀:左右遇一省括号,左侧推断类型省
注:当一个接口中存在多个抽象方法时,如果使用lambda表达式,并不能智能匹配对应的抽象方法,因此引入了函数式接口的概念
函数式接口
函数式接口的提出是为了给Lambda表达式的使用提供更好的支持。
什么是函数式接口?
简单来说就是只定义了一个抽象方法的接口(Object类的public方法除外),就是函数式接口,并且还提供了注解
:@FunctionalInterface
常见的四大函数式接口
Consumer 《T》:消费型接口,有参无返回值
public class TestMain {
@Test
public void test(){
changeStr("hello",(str) -> System.out.println(str));
}
/**
* Consumer<T> 消费型接口
* @param str
* @param con
*/
public void changeStr(String str, Consumer<String> con){
con.accept(str);
}
}
Supplier 《T》:供给型接口,无参有返回值
@Test
public void test2(){
String value = getValue(() -> "hello");
System.out.println(value);
}
/**
* Supplier<T> 供给型接口
* @param sup
* @return
*/
public String getValue(Supplier<String> sup){
return sup.get();
}
Function 《T,R》::函数式接口,有参有返回值
@Test
public void test3(){
Long result = changeNum(100L, (x) -> x + 200L);
System.out.println(result);
}
/**
* Function<T,R> 函数式接口
* @param num
* @param fun
* @return
*/
public Long changeNum(Long num, Function<Long, Long> fun){
return fun.apply(num);
}
Predicate《T》: 断言型接口,有参有返回值,返回值是boolean类型
public void test4(){
boolean result = changeBoolean("hello", (str) -> str.length() > 5);
System.out.println(result);
}
/**
* Predicate<T> 断言型接口
* @param str
* @param pre
* @return
*/
public boolean changeBoolean(String str, Predicate<String> pre){
return pre.test(str);
}
在四大核心函数式接口基础上,还提供了诸如BiFunction、BinaryOperation、toIntFunction等扩展的函数式接口,都是在这四种函数式接口上扩展而来的,不做赘述。
总结:函数式接口的提出是为了让我们更加方便的使用lambda表达式,不需要自己再手动创建一个函数式接口,直接拿来用就好了
方法引用
若lambda体中的内容有方法已经实现了,
那么可以使用“方法引用”也可以理解为
方法引用是lambda表达式的另外一种表现形式,
并且其语法比lambda表达式更加简单。
(a) 方法引用
三种表现形式:
★ 对象 : : 实例方法名
★ 类 : : 静态方法名
★ 类 : : 实例方法名
- (lambda参数列表中第一个参数是实例方法的调用者,
- 第二个参数是实例方法的参数时可用)
@Test
public void test() {
/**
*注意:
* 1.lambda体中调用方法的参数列表与返回值类型,要与函数式接口中抽象方法的函数列表和返回值类型保持一致!
* 2.若lambda参数列表中的第一个参数是实例方法的调用者,而第二个参数是实例方法的参数时,可以使用ClassName::method
*
*/
Consumer<Integer> con = (x) -> System.out.println(x);
con.accept(100); //打印100
// 方法引用-对象::实例方法
Consumer<Integer> con2 = System.out::println;
con2.accept(200); //打印200
// 方法引用-类名::静态方法名
BiFunction<Integer, Integer, Integer> biFun = (x, y) -> Integer.compare(x, y);
BiFunction<Integer, Integer, Integer> biFun2 = Integer::compare;
Integer result = biFun2.apply(100, 200);//不相同,返回-1
// 方法引用-类名::实例方法名
BiFunction<String, String, Boolean> fun1 = (str1, str2) -> str1.equals(str2);
BiFunction<String, String, Boolean> fun2 = String::equals;
Boolean result2 = fun2.apply("hello", "world");
System.out.println(result2); //false
}
(b)构造器引用
格式:ClassName::new
@Test
public void test2() {
// 构造方法引用 类名::new
Supplier<Employee> sup = () -> new Employee();
System.out.println(sup.get());
Supplier<Employee> sup2 = Employee::new;
System.out.println(sup2.get());
// 构造方法引用 类名::new (带一个参数)
Function<Integer, Employee> fun = (x) -> new Employee(x);
Function<Integer, Employee> fun2 = Employee::new;
System.out.println(fun2.apply(100));
}
public class Employee {
public Employee() {
}
public Employee(Integer x) {
System.out.println(x);
}
}
com.example.demo.methoddemo.Employee@6ea12c19
com.example.demo.methoddemo.Employee@7921b0a2
100
com.example.demo.methoddemo.Employee@6a2bcfcb
( c )数组引用
格式:Type[]::new
public void test(){
// 数组引用
Function<Integer, String[]> fun = (x) -> new String[x];
Function<Integer, String[]> fun2 = String[]::new;
String[] strArray = fun2.apply(10); //定义10个长度
Arrays.stream(strArray).forEach(System.out::println);//遍历
}
null
null
null
null
null
null
null
null
null
null
Stream API
Stream操作的三个步骤
★ 创建stream
★ 中间操作(过滤、map)
★ 终止操作
stream的创建:
// 1,校验通过Collection 系列集合提供的stream()或者paralleStream()
List<String> list = new ArrayList<>();
Stream<String> stream1 = list.stream();
// 2.通过Arrays的静态方法stream()获取数组流
String[] str = new String[10];
Stream<String> stream2 = Arrays.stream(str);
// 3.通过Stream类中的静态方法of
Stream<String> stream3 = Stream.of("aa","bb","cc");
// 4.创建无限流
// 迭代
Stream<Integer> stream4 = Stream.iterate(0,(x) -> x+2);
//生成
Stream<Double> generate = Stream.generate(() -> Math.random());
Stream的中间操作:
/**
* 筛选 过滤 去重
*/
emps.stream()
.filter(e -> e.getAge() > 10)
.limit(4)
.skip(4)
// 需要流中的元素重写hashCode和equals方法
.distinct()
.forEach(System.out::println);
//StuInfo [name=张三] 加了:limit(4) skip(4) 不会出现这些结果。
//StuInfo [name=张三2]
/**
* 生成新的流 通过map映射
*/
emps.stream()
.map((e) -> e.getAge())
.forEach(System.out::println);
/**
* 自然排序 定制排序
*/
emps.stream()
.sorted((e1 ,e2) -> {
if (e1.getAge().equals(e2.getAge())){
return e1.getName().compareTo(e2.getName());
} else{
return e1.getAge().compareTo(e2.getAge()); //正序
}
})
.forEach(System.out::println);
Stream的终止操作:
class Employee
{
private String name;
private Integer age;
private Integer status;
private Double salary;
}
List<Employee> emps=new ArrayList();
Employee e1=new Employee();
e1.setAge(300);
e1.setName("张三");
e1.setSalary(1D);
e1.setStatus(1);
Employee e2=new Employee();
e2.setAge(100);
e2.setName("张三2");
e2.setSalary(2D);
e2.setStatus(2);
emps.add(e1);
emps.add(e2);
/**
* 查找和匹配
* allMatch-检查是否匹配所有元素
* anyMatch-检查是否至少匹配一个元素
* noneMatch-检查是否没有匹配所有元素
* findFirst-返回第一个元素
* findAny-返回当前流中的任意元素
* count-返回流中元素的总个数
* max-返回流中最大值
* min-返回流中最小值
*/
/**
* 检查是否匹配元素
*/
boolean b1 = emps.stream()
.allMatch((e) -> e.getStatus().equals(1));
System.out.println(b1); //false 因为有一个 不匹配
boolean b2 = emps.stream()
.anyMatch((e) -> e.getStatus().equals(1));
System.out.println(b2); //true ,因为有一个 匹配
boolean b3 = emps.stream()
.noneMatch((e) -> e.getStatus().equals(1));
System.out.println(b3); //都不匹配吗? 为 false
Optional<Employee> opt = emps.stream()
.findFirst();
System.out.println(opt.get()); // 得到第一个 为张三
// 并行流
Optional<Employee> opt2 = emps.parallelStream()
.findAny();
System.out.println(opt2.get()); //并行得到任意一个。为 张三2
long count = emps.stream()
.count();
System.out.println(count); //数量为2
Optional<Employee> max = emps.stream()
.max((e1, e2) -> Double.compare(e1.getSalary(), e2.getSalary()));
System.out.println(max.get()); //求 最大的,为 张三2
Optional<Employee> min = emps.stream()
.min((e1, e2) -> Double.compare(e1.getSalary(), e2.getSalary()));
System.out.println(min.get()); //求 最小的,为 张三
还有功能比较强大的两个终止操作 reduce和collect
reduce操作: reduce:(T identity,BinaryOperator)/reduce(BinaryOperator)-可以将流中元素反复结合起来,得到一个值
/**
* reduce :规约操作
*/
List<Integer> list2 = Arrays.asList(1,2,3,4,5,6,7,8,9,10);
Integer count2 = list2.stream()
.reduce(0, (x, y) -> x + y);
System.out.println(count2);
Optional<Double> sum = emps.stream()
.map(Employee::getSalary)
.reduce(Double::sum);
System.out.println(sum);
55
Optional[3.0]
collect操作: Collect-将流转换为其他形式,接收一个Collection接口的实现,用于给Stream中元素做汇总的方法
/**
* collect:收集操作
*/
List<Integer> ageList = emps.stream()
.map(Employee::getAge)
.collect(Collectors.toList());
ageList.stream().forEach(System.out::println);
300
100
并行流和串行流
在jdk1.8新的stream包中针对集合的操作也提供了并行操作流和串行操作流。
并行流就是把内容切割成多个数据块,并且使用多个线程分别处理每个数据块的内容。
Stream api中声明可以通过parallel()**与**sequential()**方法在**并行流和串行流之间进行切换。
parallel
英 /ˈpærəlel/ 美 /ˈpærəlel/ 全球(英国)
简明 牛津 新牛津 韦氏 柯林斯 例句 百科
n. 平行线;对比
vt. 使…与…平行
adj. 平行的;类似的,相同的
sequential
英 /sɪˈkwenʃl/ 美 /sɪˈkwenʃl/ 全球(美国)
简明 牛津 新牛津 韦氏 柯林斯 例句 百科
adj. 连续的;相继的;有顺序的
支持对数组进行并行处理
,主要是parallelSort()方法
,它可以在多核机器上 极大提高数组排序的速度
。
jdk1.8并行流使用的是fork/join框架进行并行操作。
ForkJoin框架(JDK1.7 的特性)
Fork/Join 框架:就是在必要的情况下,将一个大任务,进行拆分(fork)成若干个小任务(拆到不可再拆时),再将一个个的小任务运算的结果进行 join 汇总。
关键字:递归分合、分而治之。
采用 “工作窃取”模式(work-stealing):
-
当执行新的任务时,它可以将其拆分分成更小的任务执行,
-
并将小任务加到线程队列中,
-
然后再从一个随机线程的队列中偷一个并把它放在自己的队列中。
-
相对于一般的线程池实现,
fork/join框架的优势
体现在对其中包含的任务的处理方式上
。- 在一般的线程池中,如果一个线程正在执行的任务,由于某些原因无法继续运行,那么该线程会处于等待状态。
- 而在fork/join框架实现中,如果某个子问题由于等待另外一个子问题的完成而无法继续运行,
- 那么处理该子问题的线程
会主动寻找其他尚未运行的子问题来执行
。 - 这种方式减少了线程的等待时间,提高了性能。
/**
* 要想使用Fork—Join,类必须继承
* RecursiveAction(无返回值)
* Or
* RecursiveTask(有返回值)
*
*/
public class ForkJoin extends RecursiveTask<Long> {
/**
* 要想使用Fork—Join,类必须继承RecursiveAction(无返回值) 或者
* RecursiveTask(有返回值)
*
* @author Wuyouxin
*/
private static final long serialVersionUID = 23423422L;//序列化
private long start;//开始
private long end;//结束
public ForkJoin() {//无参数构造
}
public ForkJoin(long start, long end) {//全参数构造
this.start = start;
this.end = end;
}
// 定义 阙值
private static final long THRESHOLD = 10000L;//1万
@Override
protected Long compute() {
//结束 - 开始 <= 1万
if (end - start <= THRESHOLD) {
long sum = 0;//就累加上 开始 到 结束的 值。比如 100 到 2 ,那就是 2+3+4... 100
for (long i = start; i < end; i++) {
sum += i;
}
return sum;
} else {
结束 - 开始 > 1万
long middle = (end - start) / 2; //去中间的值。比如 20000 到 1
//1,10000 创建一个 forkJoin
ForkJoin left = new ForkJoin(start, middle);
//拆分子任务,压入线程队列
left.fork();
//创建右边的。 10001 到 20000
ForkJoin right = new ForkJoin(middle + 1, end);
right.fork();
//合并并返回
return left.join() + right.join();
}
}
}
public class TestMain {
/**
* 实现数的累加
*/
@Test
public void test1() {
//开始时间
Instant start = Instant.now();
//这里需要一个线程池的支持
ForkJoinPool pool = new ForkJoinPool();
ForkJoinTask<Long> task = new ForkJoin(0L, 11000L);//000000
// 没有返回值 pool.execute();
// 有返回值
long sum = pool.invoke(task);
System.out.println("结果:"+sum);
//结束时间
Instant end = Instant.now();
//1 毫秒=1000000 纳秒
System.out.println(Duration.between(start, end).getNano()/1000000);//求出毫秒。getSeconds()
}
/**
* java8 并行流 parallel()
*/
@Test
public void test2() {
//开始时间
Instant start = Instant.now();
// 并行流计算 累加求和
long reduce = LongStream.rangeClosed(0, 10009L).parallel()
.reduce(0, Long::sum);
System.out.println(reduce);
//结束时间
Instant end = Instant.now();
System.out.println(Duration.between(start, end).getSeconds());
}
@Test
public void test3() {
List<Integer> list = Arrays.asList(1, 2, 3, 4, 5);
//list.stream().forEach(System.out::print);
list.parallelStream()
.forEach(System.out::print); //顺序不定
}
}
展示多线程的效果:
@Test
public void test(){
// 并行流 多个线程执行
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9);
numbers.parallelStream()
.forEach(System.out::print); //无需遍历
//
System.out.println("=========================");
numbers.stream()
.sequential() //顺序遍历
.forEach(System.out::print);
}
Optional容器
使用Optional容器可以快速的定位NPE,并且在一定程度上可以减少对参数 非空检验 的代码量。
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Employee {
private String name;
private Integer age;
private Double salary;
private Integer status;
}
public class TestMain {
@Test
public void test20() {
/**
* Optional.of(T t); // 创建一个Optional实例
*
* Optional.empty(); // 创建一个空的Optional实例
* Optional.ofNullable(T t); // 若T不为null,创建一个Optional实例,否则创建一个空实例
*
* isPresent(); // 判断是够包含值
* orElse(T t); //如果调用对象包含值,返回该值,否则返回T
* orElseGet(Supplier s); // 如果调用对象包含值,返回该值,否则返回s中获取的值
*
* map(Function f): // 如果有值对其处理,并返回处理后的Optional,否则返回Optional.empty();
* flatMap(Function mapper);// 与map类似。返回值是Optional
*
* 总结:Optional.of(null) 会直接报NPE
*/
//Optional.of(T t); // 创建一个Optional实例
Optional<Employee> op = Optional.of(new Employee("zhansan", 11, 12.32, 100));
System.out.println(op.get());
// NPE
Optional<Employee> op2 = Optional.of(null); //java.lang.NullPointerException
System.out.println(op2);
}
@Test
public void test2() {
//Optional.empty(); // 创建一个空的Optional实例
Optional<Object> op = Optional.empty();
System.out.println(op);
// No value present
System.out.println(op.get());
}
@Test
public void test3() {
//Optional.ofNullable(T t); // 若T不为null,创建一个Optional实例,否则创建一个空实例
Optional<Employee> op = Optional.ofNullable(new Employee("lisi", 33, 131.42, 100));
System.out.println(op.get());
Optional<Object> op2 = Optional.ofNullable(null);
System.out.println(op2); //打印:Optional.empty
// System.out.println(op2.get());
}
@Test
public void test5() {
Optional<Employee> op1 = Optional.ofNullable(new Employee("张三", 11, 11.33, 100));
/*
* isPresent(); // 判断是够包含值
* orElse(T t); //如果调用对象包含值,返回该值,否则返回T
* orElseGet(Supplier s); // 如果调用对象包含值,返回该值,否则返回s中获取的值
*/
System.out.println(op1.orElse(new Employee()));//op1.包含值,所以就打印:Employee(name=张三, age=11, salary=11.33, status=100)
System.out.println(op1.orElse(null));
}
@Test
public void test6() {
Optional<Employee> op1 = Optional.of(new Employee("田七", 11, 12.31, 100));
op1 = Optional.empty();//赋值为empty。
Employee employee = op1.orElseGet(() -> new Employee());//op1为空,所以返回后面new的对象 Employee(name=null, age=null, salary=null, status=null)
System.out.println(employee);
}
@Test
public void test7() {
/*
* map(Function f): // 如果有值对其处理,并返回处理后的Optional,否则返回Optional.empty();
* flatMap(Function mapper);// 与map类似。返回值是Optional
*/
Optional<Employee> op1 = Optional.of(new Employee("田七", 11, 12.31, 100));
System.out.println(op1.map((e) -> e.getSalary()).get());//12.3
}
}
接口:定义默认实现方法和静态方法
在接口中可以使用default和static关键字来修饰接口中定义的普通方法
public interface Interface {
default String getName(){
return "zhangsan";
}
static String getName2(){
return "zhangsan";
}
}
在JDK1.8中很多接口会新增方法,为了保证1.8向下兼容,
1.7版本中的接口实现类不用每个都重新实现新添加的接口方法,
- 引入了default默认实现,static的用法是直接用接口名去调方法即可。
- 当一个类继承父类又实现接口时,若后两者方法名相同,
则优先继承父类中的同名方法
,即“类优先”; - 如果
实现两个同名方法的接口
,则要求实现类必须手动声明默认实现哪个接口中的方法
。
一个接口有默认方法,考虑这样的情况,一个类实现了多个接口,且这些接口有相同的默认方法,以下实例说明了这种情况的解决方法:
以下实例转自:
https://blog.csdn.net/yitian_66/article/details/81010434#t17
public interface vehicle {
default void print() {
System.out.println("我是一辆车!");
}
}
public interface fourWheeler {
default void print() {
System.out.println("我是一辆四轮车!");
}
}
第一个解决方案是创建自己的默认方法,来覆盖重写接口的默认方法:
public class Car implements vehicle, fourWheeler {
@Override
public void print() {
System.out.println("我是一辆四轮汽车!");
}
}
第二种解决方案可以使用 super 来调用指定接口的默认方法:
public class Car implements vehicle, fourWheeler {
@Override
public void print() {
vehicle.super.print();
}
}
新的日期API:
- LocalDate | LocalTime | LocalDateTime