Java从1.8以后引入了函数式编程,这是很大的一个改进。函数式编程的优点在提高编码的效率,增强代码的可读性。
函数式编程本质上先把函数作为方法的输入参数,由于Java并未规定函数或方法是一种数据类型,因此需要借助接口来表达函数参数。
1. Lambda 表达式
通过在Arrays工具的sort方法中运用匿名内部类,不但能够简化代码数量,还能保持业务的连续性,但其结构仍然啰嗦,方法体里边的代码一字不落,仍然占据了好几行代码。
Integer[] array = {
83,86,95,2,4,99,55};
// 匿名内部类无需专门定义形态完整的类,只需指明新创建的实例从哪个接口扩展而来
Arrays.sort(array, new Comparator<Integer>() {
public int compare(Integer o1, Integer o2) {
return Integer.compare(o2, o1);
}
});
为了更进一步优化代码,Lambda表达式其实是一个匿名方法,它指的是:一个没有名字的方法,但方法体的内部代码是完整的。
为了保证调用到匿名方法的真身,Java对它的调用实际规定了以下限制条件:
- 调用匿名方法的地方,本身必须知晓该位置的参数类型;
- 参数类型必须是某个接口,并且该接口仅仅声明一个抽象方法。
满足以上两个限制条件才允许使用 Lambda 表达式。
Lambda表达式的特征标记:箭头标志“->”。
箭头左边为匿名方法的参数列表(匿名方法里面不存在输入参数的话,也要保留一对圆括号占位子。);
箭头右边为匿名方法的方法体。
Lambda体中的方法的参数列表和返回值要与所实现函数式接口的参数列表和返回值保持一致
根据上面两条规定知,对比排序方法sort满足第一条,排序比较器Comparator满足第二条。
// Lambda表达式第一招。去掉了new、接口名称、方法名称
Arrays.sort(intArray, (Integer o1, Integer o2) -> {
return Integer.compare(o2, o1); // 按照降序排列
});
// Lambda表达式第二招。去掉了输入参数的变量类型
Arrays.sort(intArray, (o1, o2) -> {
return Integer.compare(o2, o1); // 按照降序排列
});
// Lambda表达式第三招。去掉了方法体的花括号,以及方法返回的return和分号
Arrays.sort(intArray, (o1, o2) -> Integer.compare(o2, o1));
小结:使用Lambda表达式 可以省略new、接口名称、方法名称、输入参数的变量类型、方法体的花括号,以及方法返回的return和分号。
2. 函数式接口定义
既然匿名内部类与Lambda表达式都依附与某种接口,那就得好好研究这个接口的特别之处。
之前所说的Lambda表达式概念的时候,提到Lambda表达式指的是匿名方法,并且由于Java不支持把方法作为参数类型,因此只好再给方法加一层接口的包装类,于是之前所讲的sort方法里的参数类型变为Comparator接口而非compare方法了。
像上面所述,为了区分其他接口,Java给其命名为“函数式接口”,它拥有一般接口的形态,但其内部有且仅有一个抽象方法,除此之外,函数式接口还允许定义别的非抽象方法,包括默认方法与静态方法。
接下来举个动物类来理解概念。
//定义一个行为接口,给动物类调用
public interface Behavior {
public void act(); // 声明一个名叫行动的抽象方法
}
//演示动物类的定义,其中midnight方法的输入参数为Behavior类型
public class Animal {
private String name; // 动物名称
public Animal(String name) {
this.name = name;
}
public String getName() {
return this.name;
}
// 定义一个半夜行动的方法。具体的动作由输入行为的act方法执行
public void midnight(Behavior behavior) {
behavior.act();
}
}
// 测试公鸡在半夜干了啥
private static void testCock() {
Animal cock = new Animal("公鸡"); // 创建一个公鸡实例
// 调用midnight方法时,传入匿名内部类的实例
/*cock.midnight(new Behavior() {
@Override
public void act() {
System.out.println(cock.getName() + "在叫啦。");
}
});*/
// 调用midnight方法时,传入Lambda表达式的代码。
// 匿名方法不存在输入参数的话,也要保留一对圆括号占位子。
cock.midnight(() -> System.out.println(cock.getName() + "在叫啦。"));
}
函数式接口的小结: 函数式接口适用于外部把某个方法当作输入参数的场合。通过利用函数式接口,一群实例可在调用时单独传入具体动作,而无须像从前那样派生出许多子类,还要在各个子类中分别实现它们的动作方法。
3. 双冒号标记的方法引用
个人理解:方法引用承接自Lambda表达式,若Lambda表达式中的内容已经有方法实现了,那么就可以使用方法引用,Lambda表达式中只有一个方法的时候就可以使用方法引用。
方法引用通过一对 双冒号:: 来表示,方法引用是一种函数式接口的另一种书写方式。
方法引用的三种格式:
- 实例名称(对象) :: 方法名称
public void Test1() {
//举例1
Consumer<String> con = (x) -> System.out.println(x);//使用Lambda表达式
con.accept("Hello");
Consumer<String> con1 = System.out::println; //使用方法引用
con1.accept("World");
//举例2
Student student = new Student("Moti",10,"男");
Supplier<String> stuName = () -> student.getName(); //使用Lambda表达式
System.out.println(stuName.get());
Supplier<String> stuName1 = student::getName; //使用方法引用
System.out.println(stuName1.get());
}
运行结果
Hello
World
Moti
Moti
可见使用Lambda表达式的写法和使用方法引用的写法的效果是一样的!使用方法引用会更加简化代码
- 类名 :: 静态方法名
public void Test2() {
Comparator<Integer> com = (x,y) -> Integer.compare(x, y);//使用Lambda表达式
Comparator<Integer> com1 = Integer::compare; //使用方法引用
}
- 类名 :: 实例方法名
注意:
Lambda体中的方法的参数列表和返回值要与所实现函数式接口的参数列表和返回值保持一致
第一个参数是lambda体中方法的调用者,第二个参数是方法的参数时,可以使用 变量类型::实例方法名的方法引用
public void Test3() {
BiPredicate<String, String> bp = (x,y) -> x.equals(y); //使用Lambda表达式
BiPredicate<String, String> bp1 = String::equals; //使用方法引用
}