在之前的匿名内部类以及“使猪飞”的例子都是函数式编程,这篇文章将详细介绍什么是函数式编程、lambda表达式。
这是本文章代码使用的例子:
这是一个名为User的实体类,有构造器,重写了toString(),以及run()和say()方法用于举例。
public class User {
private String name;
private int age;
public User() {
}
public User(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
public void run(Runnable r){
r.run();
}
public void say(String message){
System.out.println(message);
}
public static String address(){
return "China";
}
}
定义两个接口
public interface MessageInterface {
public String sayMessage();
}
public interface Runnable {
public void run();
}
函数式编程
我们的面向对象编程太过于强调“必须通过对象的形式来做事情”,尤其Java,完全依赖于对象。而函数式思想可以忽略面向对象的复杂语法,函数式编程就是强调做什么,而不是强调哪个对象去怎么做,这也是面向函数和面向对象的区别。
以下就是函数替代对象的写法(也就是使用匿名内部类):
//Case1:
//The method {User.run()} that takes an object that
// implements the Runnable interface.
//When we use functional programming, we simply implement
// unimplemented method{Runnable.run()} in {new Runnable()},
//you don't have to create a object.
new User().run(new Runnable() {
@Override
public void run() {
System.out.println("The user is running!");
}
});
//Case2:
//You can also use the interface method in functional programming.
System.out.println(new MessageInterface() {
@Override
public String sayMessage() {
return "Hello, are you OK?";
}
}.sayMessage());
可见,我们使用匿名内部类的目的不是为了创建对象,而是去做这件事才去创建的。
Lambda表达式
匿名内部类实现起来虽然简单,但是其语法比较冗余。
JDK1.8引入的Lambda表达式,也就是说Lambda表达式是Java8的新特性之一。这种方式只针对有一个抽象方法的接口实现,以简洁的表达式形式实现接口功能来作为方法参数。
lambda的使用
(参数列表) -> {方法体}
上面的代码,改写为lambda表达式,只需要下面这一行:
new User().run(() -> {System.out.println("The user is running!");});
MessageInterface msg = () -> {return "Hello, are you OK?";};
System.out.println(msg.sayMessage());
是不是简洁很多?但这还不是最简洁的:
如果Lambda表达式的方法体只有一行代码,可以省略大括号不写,同时省略分号;
如果方法体中只有一行return语句,若省略大括号,必须省略return、分号;
参数类型可以省略不写,只有一个参数时()可以省略。
//省略{},省略;
new User().run(() -> System.out.println("The user is running!"));
//省略{},省略return,省略;
MessageInterface msg = () -> "Hello, are you OK?";
System.out.println(msg.sayMessage());
推荐使用这种方式,因为写法最简单,看起来最整洁。
当然,即使不会lambda表达式也不会有太大的影响,但是会这个你能看懂别人的的代码,也能让你的代码更优雅!
函数式接口
接口中有且只有一个抽象方法时才能使用Lambda表达式代替匿名内部类,这是因为Lambda表达式是基于函数式接口实现的。函数式接口指的是有且仅有一个抽象方法的接口。
在Java8中,接口上有@FunctionalInterface注解的即为函数式接口,在函数式接口内部只有一个抽象方法。@FunctionalInterface注解只是显示的标注了接口是一个函数式接口,并强制编辑器进行更严格的检查,确保该接口是函数式接口,这与Override注解性质是一样的。
这些是JDK1.8之前已有的一些函数式接口:
java.lang.Runnable
java.util.concurrent.Callable
java.security.PrivilegedAction
java.util.Comparator
java.io.FileFilter
java.nio.file.PathMatcher
java.lang.reflect.InvocationHandler
java.beans.PropertyChangeListener
java.awt.event.ActionListener
javax.swing.event.ChangeListener
JDK1.8新增的函数接口:
java.util.function
包下包含了很多类,用来支持Java的函数式编程。
Lambda应用举例
下面介绍一些可用lambda表达式改写的且特别实用的例子。
创建一个集合,同时添加若干对象:
ArrayList<User> users = new ArrayList<>();
users.add(new User("Forward Seen",20));
users.add(new User("Linda",14));
users.add(new User("Bob",35));
users.add(new User("Jack",34));
users.add(new User("Smith",20));
例1:实现集合遍历
遍历输出users集合中的全部User对象:
//通过匿名内部类的方式遍历
users.forEach(new Consumer<User>() {
@Override
public void accept(User user) {
System.out.println(user);
}
});
//使用lambda
users.forEach(user -> System.out.println(user));
因为只有一个参数user,所以( )可以省略
这一看是用lambda是不是特别简洁,一行代码搞定!
例2:实现集合元素排序
将users中的User对象按照年龄大小降序排序。
//通过匿名内部类的方式排序
users.sort(new Comparator<User>() {
@Override
public int compare(User o1, User o2) {
return o2.getAge() - o1.getAge();
}
});
//测试结果
users.forEach(user -> System.out.println(user));
//使用lambda
users.sort((o1, o2) -> o2.getAge() - o1.getAge());
通过这些例子的使用,我们可以了解,使用lambda可以不用考虑代码实现的过程,我们只需要结果!
换种方式跟你说,我们不需要记住诸如foreach、sort使用时所需要的是什么接口的什么方法,记不住没关系,我们只要知道这种格式,通过lambda表达式一样可以实现我们所要的结果!
方法引用
lambda表达式的主体只有一条语句时,可以通过“::”的格式来引用方法和构造方法。方法引用可以进一步简化lambda表达式的书写,其本质都是对lambda表达式的主体部分已存在的方法进行直接引用。
类名引用静态方法
通过类名对静态方法的引用。
//Lambda
MessageInterface msg = () -> User.address();
System.out.println(msg.sayMessage());
//静态方法引入
MessageInterface msg = User::address;
System.out.println(msg.sayMessage());
对象名引用方法
通过实例化对象的名称来对其方法进行引用。
User user = new User("Forward", 30);
//lambda表达式
MessageInterface msg = () -> user.getName();
System.out.println(msg.sayMessage());
//实例方法引入
//对象名::方法名,注意没有()
MessageInterface msg = user::getName;
System.out.println(msg.sayMessage());
构造方法引用
对类自带的构造器的引用。
//使用ambda表达式方式
printName("张三", name -> new User(name));
//使用构造器引用的方式
printName("张三", User::new);
类名引用普通方法
通过一个普通类的类名对其普通方法的引用。
//使用Lambda表达式方式
printUpper(new StringUtils(),"Hello",(object, t) -> object.printUpperCase());
//使用方法引用的方式
printUpper(new StringUtils(),"Hello" ,StringUtils::printUpperCase);