函数式编程是Java1.8新增内容:
Java8提出了函数式接口的概念。简单来说就是只定义了单一抽象方法的接口。比如下面的定义:
package function;
@FunctionalInterface
public interface Functions {
void handleFunctions(int i);
}
注释FunctionalInterface用于表明Functions接口是一个函数式接口,该接口被定义为只包含一个抽象方法handleFunctions(),因此它符合函数式接口的定义。如果声明两个抽象方法则会出现编译错误:
需要注意的是函数式接口只能有一个抽象方法,但是不是只能有一个方法:在Java8中,接口运行存在实例方法,其次任何被java.lang.Object实现的方法都不能视为抽象方法所以下面的接口并不会报错:
它完全是一个符合规范的函数式接口。
在Java8之前接口只能包含抽象方法,但是在Java8之后接口也可以包含若干个实例方法。这一改进使Java8拥有了类似于多继承的能力。一个对象实例将拥有来自多个不同接口的实例方法。
在Java8中使用default关键字可以在接口内定义实例方法。(这个方法不是抽象方法,而是有特定逻辑的具体实例方法。)
下面贴上测试代码:
package function;
@FunctionalInterface
public interface Function2 {
void eat();
default void wait(int i) {
System.out.println("等待"+i);
}
}
package function;
@FunctionalInterface
public interface Functions {
void handleFunctions(int i);
String toString();
default void run() {
System.out.println("运行");
}
default void stop() {
System.out.println("停止");
}
}
package function;
public class FunctionTest implements Functions,Function2{
@Override
public void eat() {
// TODO Auto-generated method stub
}
@Override
public void handleFunctions(int i) {
// TODO Auto-generated method stub
}
public static void main(String[] args) {
FunctionTest test=new FunctionTest();
test.run();
test.wait(10);
}
}
从某种程度上说这种模式可以弥补Java单一继承的不便,但是需要注意的是他也会遇到和多继承相同的问题当我将Function2中添加上run方法:
它报编译错误了。这时候的解决方法是:
通过重新实现run()方法让编译器进行方法绑定。
lambda表达式是函数式编程的核心。lambda表达式就是匿名函数它是一段没有函数名的函数体,可以作为参数直接传递给相关的调用者。
下面演示一段lambda表达式的使用:
package function;
import java.util.Arrays;
import java.util.List;
public class LambdaTest {
public static void main(String[] args) {
List numbers=Arrays.asList(1,2,3,4,5);
numbers.forEach((Integer value)->System.out.println(value));
}
}
这段代码在forEach()函数中传入的是一个lambda表达式它完成了对元素的标准输出操作。
和匿名对象一样lambda表达式也可以访问外部的局部变量:
与匿名内部对象一样外部的i变量必须声明为final,这样才能保证在lambda表达式中合法的访问。
但是对于lambda来说即使去掉final定义也能正常运行。但是需要注意的是即使如此也不能修改i的值:
如果尝试修改i的值它会引起一个编译错误。
方法引用是Java8中提出来用于简化lambda表达式的一种手段,它通过类名和方法名来定位到一个静态方法或者实例方法。
1.静态方法引用:ClassName::methodName
2.实例上的实例方法引用:instanceReference::methodName
3.超类上的实例方法引用:super::methodName
4.类型上的实例方法引用:ClassName::methodName
5.构造方法引用:Class::new
6.数组构造方法引用:TypeName[]::new
首先方法引用使用“::”来定义,“::”的前半部分表示类名或者实例名,后半部分表示方法名称,如果是构造函数则使用new表示。
下面展示方法引用的基本使用:
首先定义个模型类User:
package function.dao;
public class User {
private int id;
private String name;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public User(int id, String name) {
super();
this.id = id;
this.name = name;
}
}
package function;
import java.util.ArrayList;
import java.util.List;
import function.dao.User;
public class MethodRef {
public static void main(String[] args) {
List user=new ArrayList<>();
for (int i = 0; i < 10; i++) {
user.add(new User(i,"name"+i));
}
user.stream().map(User::getName).forEach(System.out::println);
}
}
对于第一个方法引用“User::getName”表示是User类的实例方法。在执行时,Java会自动识别流中的元素(此处为User实例)是作为调用目标还是调用方法的参数。在“User::getName”中流内的元素应该作为调用目标,在这里调用了每一个User对象实例的getName()方法,并将这些User的name作为一个新的流。同时对于这里所有得到的name使用方法引用System.out::println进行处理。这里的System.out为PrintStream对象实例,因此,这里表示System.out实例的println方法,系统也会自动判断流内的元素此时应该作为方法的参数传入而不是调用目标。
一般来说如果使用的是静态方法或者调用目标明确那么流内的元素会自动作为参数使用。如果函数引用表示实例方法且不存在调动目标则流内元素自动作为调用目标。
因此如果一个类中存在同名的实例方法和静态函数,那么编译器就无法判断应该使用哪个方法进行调用:
此时在Double中同时存在两个以下函数:
public static String toString(double d)
public String toString()
此时对函数引用的处理出现了歧义因此会在编译器就出错。
方法引用还可以用于直接使用构造函数。
package function;
import java.util.ArrayList;
import java.util.List;
import function.dao.User;
public class ConsrtMethodRef {
@FunctionalInterface
interface UserFactory {
U create(int id, String name);
}
static UserFactory uf = User::new;
public static void main(String[] args) {
List user = new ArrayList<>();
for (int i = 0; i < 10; i++) {
user.add(uf.create(i, "name"+i));
}
user.stream().map(User::getName).forEach(System.out::println);
}
}
这里我申明了一个函数接口UserFactory作为User的工厂类。当使用User::new创建接口实例时,系统会根据UserFactory.create()的函数签名来选择合适的User构造函数。在这里很显然就是public User(int id, String name)。在创建UserFactory实例后,对UserFactory.create()的调用都会委托给User的实际构造函数进行,从而创建User对象实例。
如果将U create(int id,String name);改为U create(int id);
可以看到它会报错。
参考《实战Java高并发程序设计》