0%

Java1.8新特性

新特性简介

  1. 速度更快(HashMap的实现优化)
  2. 代码更少(Lambda表达式)
  3. Stream API
  4. 最大化减少空指针异常(Optional)
  5. 其它API优化(接口和时间)

HashMap的实现优化

加载因子:大于等于加载因子事,需要将当前map数组长度扩大两倍,默认0.75(不可能是百分百,因为有些位置经过hashcode求余后可能一直都没有值)

共有代码

  1. 索引

索引 = 哈希 % (lengh -1)

  1. 哈希

1.7和1.8实现原理不同,但原则是一样的:让hash的高位同样参与 & 运算,保证散列的均匀性

  1. 求余

% 比 & 运算慢很多,因此尽量使用 & 运算代替.若 $b = 2^n$,则a % b = a & (b - 1 ).原因是$b = 2^n$时, 则 b-1 的二进制低位均为1,与 a 做 & 运算能截取到对应低位值即余数.

因此当 length 为 2 的整数次幂时,索引 = 哈希 & (lengh -1)

  1. 大小为 2 的整数次幂
    1. 求余运算可使用 & 运算代替,速度快
    2. 快速得到低位均为1的二进制数(length -1 ),保证了 & 运算的意义,否则低位出现0,& 运算后该位必是0,散列的均匀性被破坏

1.7的实现

哈希
1
2
3
4
5
6
7
8
9
10
11
12
int h = hashSeed;
if (0 != h && k instanceof String) {
return sun.misc.Hashing.stringHash32((String) k);
}

h ^= k.hashCode();

// This function ensures that hashCodes that differ only by
// constant multiples at each bit position have a bounded
// number of collisions (approximately 8 at default load factor).
h ^= (h >>> 20) ^ (h >>> 12);
return h ^ (h >>> 7) ^ (h >>> 4);
添加
  1. 将要添加的元素hashcode求值并对map数组长度求余作为该元素要添加的位置/索引
  2. 如果有冲突且相等,则覆盖
  3. 如果有冲突且不相等,则在当前位置形成链表,后添加的元素放在第一个
查询
  1. 对要查询元素求得索引(通过上面添加操作描述的方法)
  2. 如果只有一个元素,直接返回
  3. 如果存在多个元素,遍历比较,如果某元素和查询元素相等则返回
删除
  1. 通过查询操作获取对应元素,然后将其删除
扩容
  1. 需要将当前数组长度扩大两倍,并需要对所有(数组+链表)元素重新求得索引并放入

1.8的实现

哈希
1
hash =  hash ^ (hash >>>16)
添加
  1. 将要添加的元素hashcode求值并对map数组长度求余作为该元素要添加的位置/索引
  2. 如果有冲突且相等,则覆盖
  3. 如果有冲突且不相等且冲突小于等于8,则在当前位置形成链表,后添加的元素放在第一个
  4. 如果有冲突且不相等且冲突大于8,如果此时table长度小于等于64,则进行resize(),扩大table的长度
  5. 如果有冲突且不相等且冲突大于8,如果此时table长度大于64,则形成红黑树
查询
  1. 对要查询元素求得索引(通过上面添加操作描述的方法)
  2. 如果只有一个元素,直接返回
  3. 如果存在多个元素,根据该处结构查询元素并返回
删除
  1. 通过查询操作获取对应元素,然后将其删除
  2. 如果此树节点数量过小,将当前红黑树结构转换为链表结构返回并替换
  3. 否则删除此节点,并按照红黑树原理平衡,且把根节点放入map数组对应索引下
扩容
  1. 同样需要将当前数组长度扩大两倍,遍历map数组,如果当前节点为链表同1.7实现
  2. 如果当前节点为红黑树,则将红黑树按照索引规则(hash & oldLength == 0)分为两个链表,如果等于0说明位置不变,如果不等于0说明需要将该链表放入新的索引下(oldIndex + oldLength);同时根据链表长度,如果小于6则转变为正常链表结构放入当前索引/新索引下,如果大于6则转变为正常红黑树结果放入当前索引/新索引下.

Lambda表达式

四大函数接口

函数式接口: 只定义了一个抽象方法的接口(Object类的public方法除外),就是函数式接口,并且还提供了注解:@FunctionalInterface.

  1. Consumer: 消费型接口,有参无返回值(void accept(T t))
  2. Supplier: 供给型接口,无参有返回值(T get())
  3. Function<T,R>: 函数式接口,有参有返回值(R apply(T t))
  4. Predicate: 断言型接口,有参有返回值(boolean test(T t))

方法引用

  1. 静态方法引用

    1
    2
    3
    4
    5
    6
    //  * 被引用的方法参数列表和函数式接口中抽象方法的参数一致!!
    // * 接口的抽象方法没有返回值,引用的方法可以有返回值也可以没有
    // * 接口的抽象方法有返回值,引用的方法必须有相同类型的返回值!!
    // 类名::静态方法
    Function<Integer, Integer, Integer> biFun1 = (x, y) -> Integer.compare(x, y);
    BiFunction<Integer, Integer, Integer> biFun2 = Integer::compare;
  2. 对象方法引用

    1
    2
    3
    4
    5

    // * 同上
    //对象::实例方法 对象也可使用 this 或 super
    Consumer<Integer> con1 = (x) -> System.out.println(x);
    Consumer<Integer> con2 = System.out::println;
  3. 特定方法引用

    1
    2
    3
    4
    // * 同上
    //类名:实例方法 第一个参数为实际调用者,第二个参数为方法形参(无参也可以,与实例方法形参一致即可)
    BiFunction<String, String, Boolean> fun1 = (str1, str2) -> str1.equals(str2);
    BiFunction<String, String, Boolean> fun2 = String::equals;
  4. 构造方法引用

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    // * 被引用的类必须存在一个构造方法与函数式接口的抽象方法参数列表一致
    // 构造方法引用 类名::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));
  5. 数组引用

    1
    2
    3
    4
    // * 同上
    // 数组引用
    Function<Integer, String[]> fun = (x) -> new String[x];
    Function<Integer, String[]> fun2 = String[]::new;

Stream API

Stream 操作有三个步骤: 创建->操作->终止操作

创建

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 1.校验通过Collection 系列集合提供的stream()或者paralleStream()
List<String> list = new ArrayList<>();
Strean<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);

// 5.生成
Stream.generate(() ->Math.random());

操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
/**
* 筛选 过滤 去重
*/
emps.stream()
.filter(e -> e.getAge() > 10)
.limit(4)
.skip(4)
// 需要流中的元素重写hashCode和equals方法
.distinct()
.forEach(System.out::println);


/**
* 生成新的流 通过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);

终止操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
/**
* 查找和匹配
* allMatch-检查是否匹配所有元素
* anyMatch-检查是否至少匹配一个元素
* noneMatch-检查是否没有匹配所有元素
* findFirst-返回第一个元素
* findAny-返回当前流中的任意元素
* count-返回流中元素的总个数
* max-返回流中最大值
* min-返回流中最小值
*/

/**
* 检查是否匹配元素
*/
boolean b1 = emps.stream()
.allMatch((e) -> e.getStatus().equals(Employee.Status.BUSY));
System.out.println(b1);

boolean b2 = emps.stream()
.anyMatch((e) -> e.getStatus().equals(Employee.Status.BUSY));
System.out.println(b2);

boolean b3 = emps.stream()
.noneMatch((e) -> e.getStatus().equals(Employee.Status.BUSY));
System.out.println(b3);

Optional<Employee> opt = emps.stream()
.findFirst();
System.out.println(opt.get());

/**
* 并行流 , parallelStream() 得到一个可以并行操作的流 , 内核是ForkJoin Pool .
* 但要注意ForkJoin PoolJVM默认公共池 , 因此任何线程同时调用parallelStream()
* 都会彼此影响 , 线程数默认是CPU的核数 .
* 因此parallelStream()并行流也可看作1.8对1.7的Fork/Join写法优化 .
*/
Optional<Employee> opt2 = emps.parallelStream()
.findAny();
System.out.println(opt2.get());

long count = emps.stream()
.count();
System.out.println(count);

Optional<Employee> max = emps.stream()
.max((e1, e2) -> Double.compare(e1.getSalary(), e2.getSalary()));
System.out.println(max.get());

Optional<Employee> min = emps.stream()
.min((e1, e2) -> Double.compare(e1.getSalary(), e2.getSalary()));
System.out.println(min.get());

/**
* reduce 操作
*/
List<Integer> list = Arrays.asList(1,2,3,4,5,6,7,8,9,10);
/**
* reduce(identity,accumulator)->等价于以下代码:
*
* T result = identity;
* //实际reduce并不保证顺序 , 具体需要根据stream类型,如果不是并行流则保证顺序
* for (T element : this stream)
* result = accumulator.apply(result, element)
* return result;
*
* 因此identity必须保证stream中任何元素element ,
* accumulator.apply(identity, element)后是其本身.
*
*/
Integer count2 = list.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);

/**
* collect 操作 , 封装为集合或map,详见Collectors类
*/
List<Integer> ageList = emps.stream()
.map(Employee::getAge)
.collect(Collectors.toList());
ageList.stream().forEach(System.out::println);

Optional容器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99

/**
* Optional.of(T t); // 创建一个Optional实例 , Optional.of(null):直接抛出空指针异常
* 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直接抛出空指针异常
*/
@Test
public void test1(){
Optional<OptionalTestObj> op = Optional.of(new OptionalTestObj("zhangsan", true));
System.out.println(op.get());

// NPE 异常
Optional<OptionalTestObj> op2 = Optional.of(null);
System.out.println(op2);
}

/**
* Optional的EMPTY对象,可创建但不可获取,否则抛出不存在元素异常
*/
@Test
public void test2(){
Optional<Object> op = Optional.empty();
System.out.println(op);

// No value present 异常
System.out.println(op.get());
}

/**
* Optional.ofNullable()的使用,可传入Null创建Optional对象,
* 如果Null,返回EMPTY对象,但同样不能获取,否则同上
*/
@Test
public void test3(){
Optional<OptionalTestObj> op = Optional.ofNullable(new OptionalTestObj("lisi", true));
System.out.println(op.get());

Optional<Object> op2 = Optional.ofNullable(null);
System.out.println(op2);
// System.out.println(op2.get());
}

/**
* optional对象的orElse(T other)的使用,如果optional对象的属性value为
* Null,返回else对象,否则返回属性value
*/
@Test
public void test4(){
Optional<OptionalTestObj> op1 = Optional.ofNullable(new OptionalTestObj("zhangsan", true));
Optional<OptionalTestObj> op2 = Optional.ofNullable(null);
// 输出 zhangsan
System.out.println(op1.orElse(new OptionalTestObj()).getName());
// 输出 null
System.out.println(op2.orElse(new OptionalTestObj()).getName());
}
/**
* optional对象的orElseGet(Supplier<T> other)的使用,原理同上.
*/
@Test
public void test5(){
Optional<OptionalTestObj> op1 = Optional.of(new OptionalTestObj("wangwu", true));
Optional<OptionalTestObj> op2 = Optional.empty();
OptionalTestObj obj1 = op1.orElseGet(() -> new OptionalTestObj());
OptionalTestObj obj2 = op2.orElseGet(() -> new OptionalTestObj());
// wangwu
System.out.println(obj1.getName());
// null
System.out.println(obj2.getName());
}

/**
* optional对象的map(Function<T,R> mapper)的使用
*/
@Test
public void test6(){
Optional<OptionalTestObj> op1 = Optional.of(new OptionalTestObj("mazi", true));
// mazi
System.out.println(op1.map( (e) -> e.getName()).get());
}
/**
* optional对象的flatMap(Function<T,Optional<R>> mapper)的使用
*/
@Test
public void test7(){
Optional<OptionalTestObj> op1 = Optional.of(new OptionalTestObj("mazi", true));
// mazi
System.out.println(op1.flatMap( (e) -> Optional.ofNullable(e.getName())).get());
}

其它

接口增加默认实现或静态方法

1
2
3
4
5
6
7
8
9
public interface Person {
default String getName(){
return "zhangsan";
}

static String getName2(){
return "zhangsan";
}
}

在JDK1.8中很多接口会新增方法,为了保证1.8向下兼容,1.7版本中的接口实现类不用每个都重新实现新添加的接口方法,引入了default默认实现,static的用法是直接用接口名去调方法即可。当一个类继承父类又实现接口时,若后两者方法名相同,则优先继承父类中的同名方法,即“类优先”,如果实现两个同名方法的接口,则要求实现类必须手动声明默认实现哪个接口中的方法。

新的日期API LocalDate | LocalTime | LocalDateTime

  1. LocalDate

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    //获取当前日期,只含年月日 固定格式 yyyy-MM-dd    2018-05-04
    LocalDate today = LocalDate.now();

    // 根据年月日取日期,5月就是5,
    LocalDate oldDate = LocalDate.of(2018, 5, 1);

    // 根据字符串取:默认格式yyyy-MM-dd,02不能写成2
    LocalDate yesteday = LocalDate.parse("2018-05-03");

    // 如果不是闰年 传入29号也会报错
    LocalDate.parse("2018-02-29");

    //2018-05-04
    LocalDate today = LocalDate.now();
    // 取本月第1天: 2018-05-01
    LocalDate firstDayOfThisMonth = today.with(TemporalAdjusters.firstDayOfMonth());
    // 取本月第2天:2018-05-02
    LocalDate secondDayOfThisMonth = today.withDayOfMonth(2);
    // 取本月最后一天,再也不用计算是28,29,30还是31: 2018-05-31
    LocalDate lastDayOfThisMonth = today.with(TemporalAdjusters.lastDayOfMonth());
    // 取下一天:2018-06-01
    LocalDate firstDayOf2015 = lastDayOfThisMonth.plusDays(1);
    // 取2018年10月第一个周三 so easy?: 2018-10-03
    LocalDate thirdMondayOf2018 = LocalDate.parse("2018-10-01").with(TemporalAdjusters.firstInMonth(DayOfWeek.WEDNESDAY));
  2. LocalTime

    1
    2
    3
    4
    5
    //16:25:46.448(纳秒值)
    LocalTime todayTimeWithMillisTime = LocalTime.now();
    //16:28:48 不带纳秒值
    LocalTime todayTimeWithNoMillisTime = LocalTime.now().withNano(0);
    LocalTime time1 = LocalTime.parse("23:59:59");
  3. LocalDateTime

    1
    2
    3
    4
    5
    6
    7
    8
    //转化为时间戳  毫秒值
    long time1 = LocalDateTime.now().toInstant(ZoneOffset.of("+8")).toEpochMilli();
    long time2 = System.currentTimeMillis();

    //时间戳转化为localdatetime
    DateTimeFormatter df= DateTimeFormatter.ofPattern("YYYY-MM-dd HH:mm:ss.SSS");

    System.out.println(df.format(LocalDateTime.ofInstant(Instant.ofEpochMilli(time1),ZoneId.of("Asia/Shanghai"))));

参考 年芳单八 - JDK1.8 新特性(全)

您的支持是对我最大的动力 (●'◡'●)