Java 8 新特性

Posted by 余腾 on 2019-04-22
Estimated Reading Time 16 Minutes
Words 3.5k In Total
Viewed Times

一、Java 8 新特性

1.7:HashMap 创建对象 运用 HashCode 方法 计算数组索引值。 如果数组内有对象,则发生Hash碰撞,此时增加链表链接(数组加链表)。
1.8:当数组容量 大于64,链表长度大于8时,链表转为 红黑树。


ConcurrentHashMap

  • 1.7 锁分段机制 , 16个隔离级别 隔离级别,段的长度不好评定,容易浪费空间。hash算法,数组加链表。红黑树
  • 1.8 CAS算法,无锁算法。

1.8堆内存。取消永久区,剥离出元空间(物理内存),当元空间达到一定的值,才开始垃圾回收机制,OOM发生概率低。

永久区 PremGenSize MaxPremGenSize
元空间 MetaSpaceSize MaxMetaSpaceSize



新特性前奏 (Lambda、Stream API)

1、EmployeeBean

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
public class Employee {
private String name;
private int age;
private double salary;

public Employee() {
super();
}

public Employee(String name, int age, double salary) {
super();
this.name = name;
this.age = age;
this.salary = salary;
}

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public int getAge() {
return age;
}

public void setAge(int age) {
this.age = age;
}

public double getSalary() {
return salary;
}

public void setSalary(double salary) {
this.salary = salary;
}

@Override
public String toString() {
return "Employee [name=" + name + ", age=" + age + ", salary=" + salary + "]";
}
}

2、策略设计模式-> 策略接口

1
2
3
public interface Strategy<T> {
public boolean test(T t);
}

3、策略设计模式-> 实现策略类

1
2
3
4
5
6
7
8
9
10
11
12
13
public class FilterEmployeeByAge implements Strategy<Employee> {
@Override
public boolean test(Employee e) {
return e.getAge() > 20;
}
}

public class FilterEmployeeBySalary implements Strategy<Employee> {
@Override
public boolean test(Employee e) {
return e.getSalary() > 3000;
}
}

4、Lambda、Stream 比较 匿名内部类

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
public class JavaLambda {
List<Employee> employees = Arrays.asList(
new Employee("张三",18,1111.11),
new Employee("张四",19,2222.22),
new Employee("张五",20,3333.33),
new Employee("张六",21,4444.44),
new Employee("张七",22,5555.55));

private List<Employee> filterEmployee(List<Employee> employees,Strategy<Employee> se) {
List<Employee> emps = new ArrayList<>();
for (Employee employee : employees) {
if(se.test(employee)) {
emps.add(employee);
}
}
return emps;
}


@Test
public void StrategyClassByAge() {
List<Employee> emps = filterEmployee(employees,new FilterEmployeeByAge());

for (Employee employee : emps) {
System.out.println(employee);
}
}

@Test
public void StrategyClassBySalary() {
List<Employee> emps = filterEmployee(employees,new FilterEmployeeBySalary());

for (Employee employee : emps) {
System.out.println(employee);
}
}

@Test
public void anonymousInnerClassByAge() {

List<Employee> emps = filterEmployee(employees,new Strategy<Employee>() {

@Override
public boolean test(Employee t) {
return t.getAge() > 20;
}
});

for (Employee employee : emps) {
System.out.println(employee);
}
}

@Test
public void anonymousInnerClassBySalary() {

List<Employee> emps = filterEmployee(employees,new Strategy<Employee>() {

@Override
public boolean test(Employee t) {
return t.getSalary() > 3000;
}
});

for (Employee employee : emps) {
System.out.println(employee);
}
}

@Test
public void testLambdaByAge() {
List<Employee> emps = filterEmployee(employees,(e) -> e.getAge() > 20);
emps.forEach(System.out::println);
}

@Test
public void testLambdaBySalary() {
List<Employee> emps = filterEmployee(employees,(e) -> e.getSalary() > 3000);
emps.forEach(System.out::println);
}

@Test
public void testStreamAPI() {
employees.stream()
.filter((e) -> e.getSalary() > 2000)
.limit(2)
.forEach(System.out::println);

System.out.println("--------------------");

employees.stream()
.map(Employee::getName)
.forEach(System.out::println);
}
}


二、Lambda 表达式

Lambda 是一个匿名函数,我们可以把Lambda表达式理解为是一段可以传递的代码(将代码像数据一样进行传递)可以写出更简洁、更灵活的代码。作为一种更紧凑的代码风格,使Java的语言表达能力得到了提升。


1、Lambda语法

Lambda 表达式在Java 语言中引入了一个新的语法元素和操作符。这个操作符为 “->” ,该操作符被称为 Lambda 操作符或箭头操作符。它将 Lambda 分为两个部分:

左侧:指定了 Lambda 表达式需要的所有参数。(对应接口抽象方法的参数列表)
右侧:指定了 Lambda 体,即 Lambda 表达式要执行的功能。(对应抽象方法的实现)


语法格式一:无参,无返回值,Lambda 体只需一条语句。

1
2
3
4
5
@Test
public void test1() {
Runnable runnable = () -> System.out.println("Hello Lambda");
runnable.run();
}

语法格式二:有一个参数,并且无返回值。Lambda 只需要一个参数时,参数的小括号可以省略。

1
2
3
4
5
@Test
public void test2() {
Consumer<String> consumer = (x) -> System.out.println(x);
consumer.accept("Lambda");
}

语法格式三:Lambda 需要两个及两个以上参数,有返回值,并且Lambda体中有多条语句。

1
2
3
4
5
6
7
8
9
@Test
public void test3() {
Comparator<Integer> comparator = (x, y) -> {
System.out.println("函数式接口" + x + ":" + y);
return Integer.compare(x, y);
};
int compare = comparator.compare(2, 1);
System.out.println(compare);
}

语法格式四:当Lambda 体只有一条语句时,return 与大括号可以省略。

1
2
3
4
5
6
@Test
public void test4() {
Comparator<Integer> comparator = (x, y) -> Integer.compare(x, y);
int compare = comparator.compare(2, 1);
System.out.println(compare);
}

Lambda 表达式中的参数类型都是由编译器推断 得出的。Lambda 表达式中无需指定类型,程序依然可 以编译,这是因为 javac 根据程序的上下文,在后台 推断出了参数的类型。Lambda 表达式的类型依赖于上 下文环境,是由编译器推断出来的。这就是所谓的 “类型推断”



2、函数式接口

只包含一个抽象方法的接口,称为函数式接口。在任意函数式接口上使用 @FunctionalInterface 注解,这样做可以检查它是否是一个函数式接口。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@FunctionalInterface
public interface MyFunction {
public String getValue(String str);
}

//需求:用于处理字符串
public String strHandler(String str, MyFunction mf){
return mf.getValue(str);
}

@Test
public void test(){
String upper = strHandler("abcd", (str) -> str.toUpperCase());
System.out.println(upper);
}


3、Java 内置四大核心函数式接口

函数式接口 参数类型 返回类型 方法
Consumer<T> 消费型接口 T void void accept(T t)
Supplier<T> 供给型接口 T T get();
Function<T, R> 函数型接口 T R R apply(T t);
Predicate<T> 断定型接口 T boolean boolean test(T t);

Consumer<T> 消费型接口

1
2
3
4
5
6
7
8
@Test
public void test(){
consumer(10000, (m) -> System.out.println(m);
}

public void consumer(int number, Consumer<Integer> con){
con.accept(number);
}

Supplier<T> 供给型接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@Test
public void test(){
List<Integer> numList = getNumList(10, () -> (int)(Math.random() * 100));

for (Integer num : numList) {
System.out.println(num);
}
}

//需求:产生指定个数的整数,并放入集合中
public List<Integer> getNumList(int num, Supplier<Integer> sup){
List<Integer> list = new ArrayList<>();

for (int i = 0; i < num; i++) {
Integer n = sup.get();
list.add(n);
}
return list;
}

Function<T, R> 函数型接口

1
2
3
4
5
6
7
8
9
10
11
@Test
public void test(){
String subStr = strHandler("abcdefg", (str) -> str.substring(2, 5));
System.out.println(subStr);
}

//需求:用于处理字符串
//Function<String, String> 第一个String接收类型,第二个String返回类型
public String strHandler(String str, Function<String, String> fun){
return fun.apply(str);
}

Predicate<T> 断言型接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@Test
public void test(){
List<String> list = Arrays.asList("Hello", "ABC", "Lambda", "a", "bb");
List<String> strList = filterStr(list, (s) -> s.length() >= 3);

for (String str : strList) {
System.out.println(str);
}
}

//需求:将满足条件的字符串,放入集合中
public List<String> filterStr(List<String> list, Predicate<String> pre){
List<String> strList = new ArrayList<>();

for (String str : list) {
if(pre.test(str)){
strList.add(str);
}
}
return strList;
}

4、方法引用

若 Lambda 体中的功能,已经有方法提供了实现,可以使用方法引用(可以将方法引用理解为 Lambda 表达式的另外一种表现形式)

注意:
1、方法引用所引用的方法的参数列表与返回值类型,需要与函数式接口中抽象方法的参数列表和返回值类型保持一致!
2、若Lambda 的参数列表的第一个参数,是实例方法的调用者,第二个参数(或无参)是实例方法的参数时,格式: ClassName::MethodName

对象的引用 :: 实例方法名

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
@Test
public void test1() {
PrintStream ps = System.out;
Consumer<String> con = (str) -> ps.println(str);
con.accept("Hello Java8!");

System.out.println("--------------------------------");

Consumer<String> con2 = ps::println;
con2.accept("Hello Java8!");
Consumer<String> con3 = System.out::println;

}

@Test
public void test2() {
Employee emp = new Employee(101, "张三", 18, 9999.99);

Supplier<String> sup = () -> emp.getName();
System.out.println(sup.get());

System.out.println("----------------------------------");

Supplier<String> sup2 = emp::getName;
System.out.println(sup2.get());
}

类名 :: 静态方法名

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@Test
public void test1() {
Comparator<Integer> com = (x, y) -> Integer.compare(x, y);
System.out.println(com.compare(1, 2));

System.out.println("-------------------------------------");

Comparator<Integer> com2 = Integer::compare;
System.out.println(com2.compare(1, 2));
}

@Test
public void test2() {
BiFunction<Double, Double, Double> fun = (x, y) -> Math.max(x, y);
System.out.println(fun.apply(1.6, 22.2));

System.out.println("--------------------------------------------------");

BiFunction<Double, Double, Double> fun2 = Math::max;
System.out.println(fun2.apply(1.6, 1.5));
}

类名 :: 实例方法名

1
2
3
4
5
6
7
8
9
10
@Test
public void test() {
BiPredicate<String, String> bp = (x, y) -> x.equals(y);
System.out.println(bp.test("abc", "abc"));

System.out.println("-----------------------------------------");

BiPredicate<String, String> bp2 = String::equals;
System.out.println(bp2.test("abc", "abc"));
}

构造器引用

构造器的参数列表,需要与函数式接口中参数列表保持一致!

1
2
3
4
5
6
7
8
9
10
@Test
public void test() {
Supplier<Employee> sup = () -> new Employee();
System.out.println(sup.get());

System.out.println("------------------------------------");

Supplier<Employee> sup2 = Employee::new;
System.out.println(sup2.get());
}

数组引用

1
2
3
4
5
6
@Test
public void test() {
Function<Integer, String[]> fun = (x) -> new String[x];
String[] strs = fun.apply(10);
System.out.println(strs.length);
}


三、Stream API

集合讲的是数据,流讲的是计算!

注意:
①Stream 自己不会存储元素。
②Stream 不会改变源对象。相反,他们会返回一个持有结果的新Stream。
③Stream 操作是延迟执行的。这意味着他们会等到需要结果的时候才执行。

1、创建 Stream:一个数据源(如:集合、数组),获取一个流。
2、中间操作:一个中间操作链,对数据源的数据进行处理。
3、终止操作(终端操作):一个终止操作,执行中间操作链,并产生结果。


创建 Stream

Java8 中的 Collection 接口被扩展,提供了 两个获取流的方法:

  • default Stream stream():返回一个顺序流。
  • default Stream parallelStream():返回一个并行流

中间操作

筛选与切片

  • filter() —— 接收 Lambda,从流中排除某些元素。
  • limit() —— 截断流,使其元素不超过给定数量。
  • skip(n) —— 跳过元素,返回一个扔掉了前 n 个元素的流。若流中元素不足n个,则返回一个空流,与 limit(n) 互补。
  • distinct() —— 筛选,通过流所生成元素的 hashCode() 和 equals() 去除重复元素

映射

  • map() —— 接收Lambda,将元素转换成其他形式或提取信息。接收一个函数作为参数,该函数会被应用到每个元素上,并将其映射成一个新的元素。
  • flatMap() —— 接收一个函数作为参数,将流中的每个值都换成另一个流,然后把所有流连接成一个流。

排序

  • sorted() —— 自然排序
  • sorted(Comparator com) —— 定制排序

终止操作

匹配

  • allMatch() —— 检查是否匹配所有元素。
  • anyMatch() —— 检查是否至少匹配一个元素。
  • noneMatch() —— 检查是否没有匹配的元素。
  • findFirst() —— 返回第一个元素。
  • findAny() —— 返回当前流中的任意元素。
  • count() —— 返回流中元素的总个数。
  • max() —— 返回流中最大值。
  • min() —— 返回流中最小值。

归约

  • reduce(T identity, BinaryOperator) / reduce(BinaryOperator) —— 可以将流中元素反复结合起来,得到一个值。

收集

  • collect() —— 将流转换为其他形式。接收一个 Collector接口的实现,用于给Stream中元素做汇总的方法。

并行流与串行流

Fork/Join 框架 “工作窃取”模式(work-stealing)

  • 就是在必要的情况下,将一个大任务,进行拆分(fork)成若干个小任务(拆到不可再拆时),再将一个个的小任务运算的结果进行 join 汇总。

Java8

1
2
3
4
5
6
7
8
9
10
11
12
13
@Test
public void test(){
long start = System.currentTimeMillis();
Long sum = LongStream
.rangeClosed(0L, 100000000000L)
.parallel()
.sum();

System.out.println(sum);

long end = System.currentTimeMillis();
System.out.println("耗费的时间为: " + (end - start));
}


四、Optional 容器类

Optional 类(java.util.Optional) 是一个容器类,代表一个值存在或不存在,原来用 null 表示一个值不存在,现在 Optional 可以更好的表达这个概念。并且可以避免空指针异常。

常用方法:

  • 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。


五、新时间日期 API

LocalDate、LocalTime、LocalDateTime不可变对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@Test
public void test1(){
LocalDateTime ldt = LocalDateTime.now();
System.out.println(ldt);
System.out.println(ldt.getYear());
System.out.println(ldt.getMonthValue());
System.out.println(ldt.getDayOfMonth());
System.out.println(ldt.getHour());
System.out.println(ldt.getMinute());
System.out.println(ldt.getSecond());

LocalDateTime ld2 = LocalDateTime.of(2019, 4, 20, 10, 10, 10);
System.out.println(ld2);

LocalDateTime ldt3 = ld2.plusDays(2);
System.out.println(ldt3);

LocalDateTime ldt4 = ld2.minusMonths(2);
System.out.println(ldt4);
}

Instant 时间戳

(使用 Unix 元年 1970年1月1日 00:00:00 所经历的毫秒值)

1
2
3
4
5
6
7
8
9
10
11
12
13
@Test
public void test2(){
Instant ins = Instant.now(); //默认使用 UTC 时区
System.out.println(ins);

OffsetDateTime odt = ins.atOffset(ZoneOffset.ofHours(8));
System.out.println(odt);

System.out.println(ins.getNano());

Instant ins2 = Instant.ofEpochSecond(1);
System.out.println(ins2);
}

Duration:用于计算两个“时间”间隔

Period:用于计算两个“日期”间隔

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@Test
public void test3() {
Instant ins1 = Instant.now();
System.out.println("--------------------");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
}
Instant ins2 = Instant.now();
System.out.println("所耗费时间为:" + Duration.between(ins1, ins2));

System.out.println("----------------------------------");

LocalDate ld1 = LocalDate.now();
LocalDate ld2 = LocalDate.of(2008, 1, 1);

Period pe = Period.between(ld2, ld1);
System.out.println(pe.getYears());
System.out.println(pe.getMonths());
System.out.println(pe.getDays());
}

DateTimeFormatter 解析和格式化日期或时间

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@Test
public void test4() {

DateTimeFormatter dtf = DateTimeFormatter.ISO_DATE_TIME;
LocalDateTime ldt = LocalDateTime.now();
String strDate = ldt.format(dtf);
System.out.println(strDate);

System.out.println("--------------------");

DateTimeFormatter dtf1 = DateTimeFormatter.ofPattern("yyyy年MM月dd日 HH:mm:ss");
String strDate2 = dtf1.format(ldt);
System.out.println(strDate2);

LocalDateTime newLdt = ldt.parse(strDate2, dtf1);
System.out.println(newLdt);
}

ZonedDate、ZonedTime、ZonedDateTime:时区

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Test
public void test5() {
Set<String> set = ZoneId.getAvailableZoneIds();
set.forEach(System.out::println);
}

@Test
public void test6() {
LocalDateTime ldt = LocalDateTime.now(ZoneId.of("Asia/Shanghai"));
System.out.println(ldt);

ZonedDateTime zdt = ZonedDateTime.now(ZoneId.of("US/Pacific"));
System.out.println(zdt);
}

重复注解与类型注解

Java 8对注解处理提供了两点改进:可重复的注解及可用于类型的注解。

感谢阅读


If you like this blog or find it useful for you, you are welcome to comment on it. You are also welcome to share this blog, so that more people can participate in it. If the images used in the blog infringe your copyright, please contact the author to delete them. Thank you !