函数式编程是Java8的新特性,作为追求少而优雅的代码的程序员来说,必须学习一波。
函数式编程语言操纵代码片段就像操作数据一样容易。 虽然 Java 不是函数式语言,但 Java 8 Lambda 表达式和方法引用 (Method References) 允许你以函数式编程。
0、什么是函数式编程,函数式编程的核心思想又是什么呢?
函数式编程的第一个特点就是可以把函数作为参数传递给另一个函数,也就是所谓的高阶函数。例如,对数组进行排序,可以传入一个排序函数作为参数:
String[] array = { "orange", "Pear", "Apple" };
Arrays.sort(array, String::compareToIgnoreCase);
函数式编程的第二个特点就是可以返回一个函数,这样就可以实现闭包或者惰性计算:
以上两个特点还仅仅是简化了代码。从代码的可维护性上讲,函数式编程最大的好处是引用透明,即函数运行的结果只依赖于输入的参数,而不依赖于外部状态,因此,我们常常说函数式编程没有副作用。
没有副作用有个巨大的好处,就是函数内部无状态,即输入确定,输出就是确定的,容易测试和维护。
参考:https://www.liaoxuefeng.com/article/1260118907809920
1、Java通过Lambda表达式来进行函数式编程,而Lambda表达式是什么?又有什么作用呢?
在Java8里,所有的Lambda的类型都是一个接口,而Lambda表达式本身,也就是Runnable实现的“那段代码”。所以说Lambda表达式本身就是一个接口的实现。
而最直观的作用就是,Lambda表达式让代码变的异常简洁。
public class RunnableMethodReference {
public static void main(String[] args){
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("匿名函数");//编写匿名函数实现Runnable接口
}
}).start();
new Thread(() -> {
System.out.println("拉姆达表达式");//lambda表达式在这里等于匿名函数实现,但简洁了很多
}).start();
}
}
2、接下来,我们再通过代码来多多体会Lambda表达式的魅力。
1)、递归
2)、方法引用
Java 8 方法引用没有历史包袱。方法引用组成:类名或对象名,后面跟 ::,然后跟方法名称。
interface Callable {//我们从单一方法接口开始(同样,你很快就会了解到这一点的重要性)。
void call(String s);
}
class Describe {
void show(String msg){//show() 的签名(参数类型和返回类型)符合 Callable 的 call() 的签名。
System.out.println(msg);
}
}
public class MethodReferences {
static void hello(String name){//hello() 也符合 call() 的签名。
System.out.println("Hello, " + name);
}
static class Description{
String about;
Description(String about) {
this.about = about;
}
void help(String msg) { // 就像是 help(),静态内部类中的非静态方法。
System.out.println(about + " " + msg);
}
}
static class Helper {
static void assist(String msg) { // assist() 是静态内部类中的静态方法。
System.out.println(msg);
}
}
public static void main(String[] args){
Describe d = new Describe();
Callable c = d::show;//[6]我们将 Describe 对象的方法引用赋值给 Callable ,它没有 show() 方法,而是 call() 方法。 但是,Java 似乎接受用这个看似奇怪的赋值,因为方法引用符合 Callable 的 call() 方法的签名。
c.call("call()");//我们现在可以通过调用 call() 来调用 show(),因为 Java 将 call() 映射到 show()。
c = MethodReferences::hello; // [8]这是一个静态方法引用。
c.call("Bob");
c = new Description("valuable")::help; // 这是 [6] 的另一个版本:附加到存活对象的方法的方法参考,有时称为绑定方法引用。
c.call("information");
c = Helper::assist; // 最后,获取静态内部类的方法引用的操作与 [8] 中外部类方式一样。
c.call("Help!");
}
}
未绑定的方法引用,与构造方法的引用
interface MakeNoArgs {
Dog make();
}
interface Make1Arg {
Dog make(String nm);
}
interface Make2Args {
Dog make(String nm, int age);
}
interface TwoArgs {
void call2(Dog athis, int i, double d);
}
interface ThreeArgs {
void call3(Dog athis, int i, double d, String s);
}
interface FourArgs {
void call4(Dog athis, int i, double d, String s, char c);
}
public class Dog {
String name;
int age = -1;//For "unknow"
Dog() { name = "stray"; }
Dog(String nm) { name = nm; }
Dog(String nm, int yrs) { name = nm; age = yrs; }
void bark(String msg){
System.out.println(age + "岁的" + name + "狗在吠: " + msg);
}
void two(int i, double d) {
System.out.println(this.getClass() + "接收2个参数");
}
void three(int i, double d, String s) { System.out.println("接收3个参数");}
void four(int i, double d, String s, char c) { System.out.println("接收4个参数");}
}
/**
* @Author: dyf
* @Date: 2019/7/9 22:44
* @Description:
* Dog 有三个构造函数,函数接口内的 make() 方法反映了构造函数参数列表( make() 方法名称可以不同)。
* 注意我们如何对 [1],[2] 和 [3] 中的每一个使用 Dog :: new。 这 3 个构造函数只有一个相同名称::: new,但在每种情况下都赋值给不同的接口。
* 编译器可以检测并知道从哪个构造函数引用。编译器能识别并调用你的构造函数( 在本例中为 make())。
*/
public class MainTest {
public static void main(String[] args){
//构造函数引用
MakeNoArgs mna = Dog::new; // [1]
Make1Arg m1a = Dog::new; // [2]
Make2Args m2a = Dog::new; // [3]
Dog dn = mna.make();
Dog d1 = m1a.make("Comet");
Dog d2 = m2a.make("Ralph", 4);
System.out.println("狗名:" + dn.name + ", 狗龄:" + dn.age);
System.out.println("狗名:" + d1.name + ", 狗龄:" + d1.age);
System.out.println("狗名:" + d2.name + ", 狗龄:" + d2.age);
//未绑定的方法引用是指没有关联对象的普通(非静态)方法。 使用未绑定的引用之前,我们必须先提供对象:
//未绑定方法的引用,为了说明这一点,我将类命名为 Dog ,函数方法的第一个参数则是 athis。
TwoArgs twoargs = Dog::two;
ThreeArgs threeargs = Dog::three;
FourArgs fourargs = Dog::four;
Dog athis = new Dog();
twoargs.call2(athis, 11, 3.14);//必须是函数方法的第一个参数为方法对象
threeargs.call3(athis, 11, 3.14, "Three");
fourargs.call4(athis, 11, 3.14, "Four", 'Z');
//如果用Java8提供的接口函数来写:
Supplier<Dog> bark = Dog::new;
bark.get().bark("生产者函数");
Consumer<String> bark1 = new Dog()::bark;
bark1.accept("消费者函数");
Function<String, Dog> f = s -> new Dog(s);
f.apply("function").bark("function构造.....");
Consumer c1 = System.out::println;
Consumer c2 = s -> System.out.println(s + " from c2");
c1.andThen(c2).accept("入参");
}
}
3、函数式接口
例如:Runnable接口与Comparator接口就是函数式接口,有 @FunctionalInterface,表示只能有一个抽象方法的接口。比较重要的一个接口特性是接口的默认方法(用关键字default),用于提供默认实现。默认方法和普通实现类的方法一样,可以使用this等关键字:
@FunctionalInterface
interface Interface1 {
int doubleNum(int i);
default int add(int x, int y) {
return x + y;
}
}
java.util.function 包旨在创建一组完整的目标接口,使得我们一般情况下不需再定义自己的接口。
具体常见使用:
-
Function<T, R> - 函数:输入 T 输出 R
-
BiFunction<T, U, R> - 函数:输入 (T ,U) 输出 R 对象
-
Predicate<T> - 断言/判断:输入 T 输出 boolean
-
BiPredicate<T, U> - 断言/判断:输入 (T ,U) 输出 boolean
-
Supplier<T> - 生产者:无输入(),输出 T
-
Consumer<T> - 消费者:输入 T,无输出
-
BiConsumer<T, U> - 消费者:输入(T ,U) 无输出
-
UnaryOperator<T> - 单元运算:输入 T 输出 T
-
BinaryOperator<T> - 二元运算:输入 (T ,T) 输出 T
柯里化:
简单来说,柯里化就是把本来多个参数的函数转换为只有一个参数的函数
Function<Integer, Function<Integer, Function<Integer, Integer>>> function2 = x -> y -> z -> x + y + z;
System.out.println("计算结果为: " + function2.apply(1).apply(2).apply(3)); // 计算结果为: 6