0%

Java 基础入门 Java8

Java 8是Java语言开发的一个主要版本

Java 8新特性

  • 速度更快
  • 代码更少,增加了新的语法,Lambda表达式
  • 强大的Stream API
  • 便于并行
  • 最大化减少空指针异常:Optional
  • Nashorn引擎,允许在JVM上运行JS应用

Lambda表达式

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

本质

作为接口的实例

基本语法

  • ->lambda操作符,或箭头操作符
  • ->左边:lambda形参列表,其实就是接口中的抽象方法的形参列表
  • ->右边:lambda体,其实就是重写的抽象方法的方法体

语法格式

  • 语法格式一:无参,无返回值

Runnable r2 = () -> { System.out.println("Hello world"); }

  • 语法格式二:lambda需要一个参数,但是没有返回值

Consumer<String> con1 = (String s) -> { System.out.println(s); }

  • 语法格式三:数据类型可以省略,因为可由编译器推断得出,称为类型推断

Consumer<String> con1 = (s) -> { System.out.println(s); }

  • 语法格式四:lambda若只需要一个参数时,参数的小括号可以省略

Consumer<String> con1 = s -> { System.out.println(s); }

  • 语法格式五:lambda需要两个或以上的参数,多条执行语句,并且可以有返回值

Comparator<Integer> com2 = (o1, o2) -> { return Integer.compare(o1, o2); };

  • 语法格六:当lambda体只有一条语句时, return 与大括号若有,都可以省略

Comparator<Integer> com2 = (o1, o2) -> Integer.compare(o1, o2);

Consumer<String> con1 = s -> System.out.println(s);

总结

  • -> 左边:lambda形参列表的参数类型可以省略(类型推断);如果lambda形参列表只有一个参数,其一对()也可以省略
  • -> 右边:lambda体应该使用一对{}包裹;如果lambda体只有一条执行语句(可能是 return 语句),则可以省略 {}return 关键字

使用场景

以前使用匿名实现类表示的现在都可以用 Lambda 表达式来写。

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
package com.xzt.lambda_;

import org.junit.jupiter.api.Test;

import java.util.Comparator;
import java.util.function.Consumer;

/**
* @author xzt
* @version 1.0
* Lambda表达式的使用举例
*/
public class Lambda_ {
public static void main(String[] args) {
Runnable r1 = new Runnable() {
@Override
public void run() { // 接口中只有一个方法,则可以写为lambda表达式
System.out.println("Hello world");
}
};

r1.run();

// 使用Lambda表达式进行优化
Runnable r2 = () -> System.out.println("Hello world");

r2.run();
}

// 语法格式三
@Test
public void Test() {
Comparator<Integer> com1 = new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
return Integer.compare(o1, o2);
}
};

int compare = com1.compare(12, 21);
System.out.println(compare);

// 使用Lambda进行优化
Comparator<Integer> com2 = (o1, o2) -> Integer.compare(o1, o2);

int compare1 = com2.compare(12, 21);
System.out.println(compare1);
// 方法引用
Comparator<Integer> com3 = Integer::compare;

int compare2 = com2.compare(12, 21);
System.out.println(compare2);
}

// 语法格式二
@Test
public void test2() {
Consumer<String> con = new Consumer<String>() {
@Override
public void accept(String s) {
System.out.println(s);
}
};
con.accept("abcdefd");

// 使用lambda表达式进行优化
Consumer<String> con1 = (String s) -> System.out.println(s);
con1.accept("abcdefd");

}
}

函数式(Function)接口

如果一个接口中,只声明了一个抽象方法,则此接口就称为函数式接口

可以使用@FunctionalInterface注解,不加也是函数式接口,加上可以帮助校验。

1
2
3
4
5
6
7
8
9
10
package com.xzt.functioninterface_;

/**
* @author xzt
* @version 1.0
*/
@FunctionalInterface
public interface MyInterface {
void method();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
package com.xzt.functioninterface_;

/**
* @author xzt
* @version 1.0
*/
public class FunctionalInterface_ {
public static void main(String[] args) {
MyInterface myInterface = () -> System.out.println("Hell world");
myInterface.method();
}
}

注意事项

  • 只包含一个抽象方法的接口,称为函数式接口
  • 你可以通过Lambda表达式来创建该接口的对象。(若Lambda表达式抛出一个受检异常(即:非运行时异常),那么该异常需要在目标接口的抽象方法上进行声明)
  • 我们可以在一个接口上使用@FunctionalInterface注解,这样做可以检查它是否在一个函数是接口。同时 javadoc 也会包含一条声明,说明这个接口是一个函数式接口
  • java.util.function包下定义了 Java 8 的丰富的函数式接口

内置核心函数式接口、

四大核心内置核心函数式接口

函数式接口 参数类型 返回类型 用途
Consumer<T> 消费型接口 T void 对类型为T的对象应用操作,包含方法:void accept(T t)
supplier<T> 供给型接口 T T 返回类型为T的对象,包含方法:T get()
Function<T, R> 函数型接口 T R 对类型为T的对象应用此操作,并返回结果。结果是R类型的对象。包含方法R apply(T t)
Predicate<T> 断定型接口 T Boolean 确定类型为T的对象是否满足某约束,并返回Boolean值,包含方法:boolean test(T t)
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
package com.xzt.functioninterface_;

import org.junit.jupiter.api.Test;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.function.Consumer;
import java.util.function.Predicate;
import java.util.function.Supplier;

/**
* @author xzt
* @version 1.0
*/
public class MainFunctionalInterface {
public static void main(String[] args) {
Consumer<Integer> consumer = new Consumer<Integer>() {
@Override
public void accept(Integer integer) {
System.out.println("数 = " + integer);
}
};

Consumer<Integer> consumer1 = (integer) -> System.out.println("数 = " + integer); // 转为Lambda表达式

Supplier<Integer> supplier = new Supplier<Integer>() {
@Override
public Integer get() {
return 20;
}
};
Supplier<Integer> supplier1 = () -> 20; // {} 和 return 都省略


}

@Test
public void test() {
List<String> list = Arrays.asList("北京", "南京", "普京", "天津", "东京");
List<String> filterStrs = filterString(list, new Predicate<String>() {
@Override
public boolean test(String s) {
return s.contains("京");
}
});
System.out.println(filterStrs);

// 转换为 Lambda 表达式
List<String> filterStrs1 = filterString(list, s -> s.contains("京"));
System.out.println(filterStrs1);
}

public List<String> filterString(List<String> list, Predicate<String> pre) {
ArrayList<String> strings = new ArrayList<>();

for (String s : list) {
if (pre.test(s)) {
strings.add(s);
}
}
return strings;
}
}

方法引用和构造器引用

方法引用

当要传递给Lambda 体的操作,已经有实现的方法了,可以使用方法引用

  • 方法引用可以看作是Lambda表达式深层次的表达,换句话说,方法引用就是Lambda表达式,也就是函数式接口的一个实例,通过方法的名字来指向一个方法,可以认为是Lambda表达式的一个语法堂。
  • 要求:实现接口的抽象方法的参数列表和返回值类型,必须与方法引用的方法的参数列表和返回值类型保持一致。
  • 格式:使用操作符 :: 将类或对象与方法名分割开来

使用情况

  • 对象::实例化方法名
  • 类::静态方法名
  • 类::实例化方法名
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
package com.xzt.methodreference;

import org.junit.jupiter.api.Test;

import java.util.function.Consumer;
import java.util.function.Supplier;

/**
* @author xzt
* @version 1.0
*/
public class MethodReference {

@Test
public void test(){
Consumer<String> consumer = str -> System.out.println(str);
consumer.accept("北京");

// 使用 方法引用 类::静态方法名
Consumer<String> consumer1 = System.out::println;

consumer1.accept("北京");
}

@Test
public void test1(){
String name = "abcd";
Supplier<String> supplier = () -> name;
System.out.println(supplier.get());

// 使用方法引用 对象名::实例化方法名
Supplier<String> supplier1 = name::toString;
System.out.println(supplier1.get());
}
}

构造器引用

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
package com.xzt.methodreference;

import java.util.function.Supplier;

/**
* @author xzt
* @version 1.0
*/
public class MethodReference01 {
public static void main(String[] args) {
Supplier<Employee> sup = new Supplier<Employee>() {
@Override
public Employee get() {
return new Employee();
}
};

// Lambda 表达式
Supplier<Employee> sup1 = () -> new Employee();

// 构造器引用
Supplier<Employee> sup2 = Employee::new;
}
}

class Employee{
public Employee get() {
return new Employee();
}
}

强大的Stream API

把真正的函数式编程风格引入到Java中,这是目前为止对Java类库最好的补充,因为 Stream API可以极大的提供Java程序员的生产力,让程序员写出高效率、干净、简洁的代码。

  • Stream API 对集合数据进行操作,就类似于使用SQL执行的数据库查询,也可以使用Stream API来并行执行操作。
  • Stream API 提供了一种高效且易于使用的处理数据的方式。

Stream 和 Collection集合的区别

  • Collection 是一种面向静态的内存数据结构,而 Stream 是有关计算的。前者主要是面向内存的,存储在内存中,后者主要是面向CPU,通过CPU实现计算。

注意

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

操作步骤

  1. 创建 Stream

一个数据源(如:集合、数组),获取一个流

  1. 中间操作

一个中间操作链,对数据员的数据进行处理

  1. 终止操作(终端操作)

一旦执行终止操作,就执行中间操作链,并产生结果。之后,不会再使用

创建对象的方式

通过集合

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

  • default Stream<E> stream() 返回一个顺序流
  • default Stream<E> parallelStream() 返回一个并行流
1
2
3
4
5
6
7
8
9
10
@Test
public void test(){
List<String> strings = new ArrayList<>();

// 返回一个顺序流
Stream<String> stream = strings.stream();

// 返回一个并行流
Stream<String> stringStream = strings.parallelStream();
}

通过数组

Java 8中的 Arrays的静态方法 stream() 可以获取数组流

1
2
3
4
5
@Test
public void test2() {
int[] arr = {1, 2, 3, 9, 6, 4};
Arrays.stream(arr);
}

通过 Stream 的 of

可以调用Stream 类的静态方法 of() ,通过显示值创建一个流。它可以接受任意数量的参数。

  • public static<T> Stream<T> of(T...values) 返回一个流
1
2
3
4
@Test
public void test3() {
Stream.of(1, 2, 3, 4);
}

创建无限流

1
2
3
4
5
6
7
8
@Test
public void test4() {
// 遍历前 10 个偶数
Stream.iterate(0, t -> t + 2).limit(10).forEach(System.out::println);

// 随机生成10 个随机数
Stream.generate(Math::random).limit(10).forEach(System.out::println);
}

Stream 的中间操作

多个中间操作可以连接起来形成一个流水线,除非流水线上触发终止操作,否则中间操作不会执行任何的处理,而在终止操作时一次性全部处理,称为”惰性求值“

正在加载今日诗词....