方法引用与函数式编程

先引入一个问题, 在js中, func()func 有什么区别呢?

1
2
3
function func(){
console.log("这是一个函数");
}

区别就是func()是调用函数, 他的值是函数的返回值(这里返回值为undefined); 而func是函数引用, 他的值是函数的地址


那么在Java中, 如何实现?

在java的语法里, 以Arrayssort方法为例, Arrays.sort()是调用方法; Arrays::sort是引用方法;

有什么用呢?

看个例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/**
* 接受一个函数作参数,
* 这个函数只有一个String类型的参数, 并且没有返回值
* @param print
*/
public void func(Consumer<String> print) {
// 调用print方法
print.accept("Hello Java8");
}

@Test
public void test() {
// 使用方法引用, 引用系统的print方法
func(System.out::print);
// 使用lambda表达式, 现写一个方法传递过去
func(str -> System.out.println(str));
}

执行测试方法test() , 会打印出两个 “Hello Java8”

大概等同于js中的如下代码: (感觉js简单多了)

1
2
3
4
5
6
7
8
9
function func(print) {
print("Hello java8");
}

func(console.log);
// 箭头函数
func(str => {
console.log(str);
});

Consumer 对函数的参数和返回值都进行了限制, 还有其他类型的API, 见: Java 8 函数式接口 | 菜鸟教程 (runoob.com)


或者还可以自定义, 实际上Consumer以及其他API的底层也是使用的这种方式

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
@FunctionalInterface
interface Func {
// 定义一个方法的形式, 参数和返回类型
int call(int i, int j);
}
public class Test {
void lambda(Func func) {
int res = func.call(1, 2);
System.out.println(res);
}

public static void main(String[] args) {
Test test = new Test();
// 传参时, 给出具体实现
// 引用类方法
test.lambda(Integer::sum);
// lambda 表达式
test.lambda((a, b) -> a * b);
// 引用实例方法
test.lambda(test::doubleSum);
}

int doubleSum(int i, int j) {
return 2 * (i + j);
}
}

应用#

1
2
3
4
@FunctionalInterface
public interface SortMethod {
void call(int[] arr);
}
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
public class Test {
public static void printMethodRunTime(SortMethod sortMethod, int[] arr) {
// 留个问题: 如何获取传入参数fun的方法名称呢?比如当传入冒泡排序时, 应获取到 "bubbleSort" 方法名称
long start = System.currentTimeMillis();
sortMethod.call(arr);
long runTime = System.currentTimeMillis() - start;
System.out.println("总耗时" + runTime + "毫秒");
}

public static int[] generateRandomArray(int length, int min, int max) {
if (max - min < 1 || length < 1) {
throw new IllegalArgumentException("参数异常");
}
int[] array = new int[length];
double random;
for (int i = 0; i < length; i++) {
// 范围 [min, max+1)
random = Math.random() * (max + 1 - min) + min;
array[i] = (int) random;
}
return array;
}

public static void main(String[] args) {
SortMethods sortMethods = new SortMethodsImpl();
printMethodRunTime(sortMethods::bubbleSort, generateRandomArray(10000, -100, 100));
printMethodRunTime(sortMethods::insertSort, generateRandomArray(10000, -100, 100));
printMethodRunTime(sortMethods::selectSort, generateRandomArray(10000, -100, 100));
printMethodRunTime(sortMethods::quickSort, generateRandomArray(10000, -100, 100));
/* 运行结果:
总耗时97毫秒
总耗时107毫秒
总耗时72毫秒
总耗时4毫秒
*/
}
}

注: 排序方法接口SortMethods及其实现类SortMethodsImpl代码见: https://gist.github.com/tiam-bloom/ea2bc5112dfa67506a960a76bdf3c367

Lambda、方法引用、函数式编程都是java8的新特性, 还有stream流等等

更多请查看: JDK 8 中的新增功能 (oracle.com)

常见的使用, 比如遍历集合

1
2
List<String> list = new ArrayList<>();
list.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
/**
* 测试排序方法的正确性
*
* @param testTimes 测试次数
* @param sortMethod 待测试的排序方法
*/
public void testSortMethod(int testTimes, Consumer<int[]> sortMethod) {
for (int i = 0; i < testTimes; i++) {
// 生成一个长度为10的数组
int[] arr = generateRandomArray(10);
// 调用排序方法对arr排序
sortMethod.accept(arr);
// 如果排序不正确, 给出提示
if (!isRightSort(arr)) {
System.out.println("第" + (i + 1) + "次排序出问题啦!!!");
System.out.println("sortMethod 排序结果: " + Arrays.toString(arr));
return;
}
}
System.out.println("通过" + testTimes + "次测试, 哎哟不错哦");
}
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
/**
* 生成一个范围在[min, max]的随机数组
*
* @param length 数组长度
* @param min 数组中最小值
* @param max 最大值
* @return 随机数组
*/
public int[] generateRandomArray(int length, int min, int max) {
if (max - min < 1 || length < 1) {
throw new IllegalArgumentException("参数异常");
}
int[] array = new int[length];
double random;
for (int i = 0; i < length; i++) {
// 范围 [min, max+1)
random = Math.random() * (max + 1 - min) + min;
array[i] = (int) random;
}
return array;
}

/**
* 方法重载(默认参数)
*
* @param length 长度
* @return 随机数组范围[-100,100]
*/
public int[] generateRandomArray(int length) {
return this.generateRandomArray(length, -100, 100);
}
1
2
3
4
5
6
7
8
9
10
11
/**
* 判断arr数组是否有序
*
* @param arr
* @return
*/
public boolean isRightSort(int[] arr) {
int[] arrCopy = Arrays.copyOf(arr, arr.length);
Arrays.sort(arrCopy);
return Arrays.equals(arrCopy, arr);
}