函数式编程思想
在数学中,函数就是有输入量、输入量的一套计算方案。相对而言,面向对象过分强调“必须通过对象的形式来做事情”,而函数式思想则尽量忽略面对对象的复杂语法,强调做什么,而不是以什么形式做。
面向对象的思想:
做一件事,找一个能解决这件事的对象,调用对象的方法,完成事情。
函数式编程思想:
只要能获取到结果,谁去做的,怎么做的都不重要,重视的是结果,不重视过程。
做什么,而不是怎么做
创建一个匿名内部类对象并不是我们的意愿,我们只是为了做这件事情而不得不创建一个对象。我们真正希望做的事情是:将接口内的方法体内的代码传递给实现类知晓。
传递一段代码才是我们真正的目的。而创建对象只是受限于面向对象的语法而不得不采取的一种手段方式。如果我们将关注点从“怎么做”回归到“做什么”的本质上,就会发现只要能够更好的达到目的,过程和形式并不重要。
lambda标准格式
lambda由三部分组成:
- 一些参数
- 一个箭头
- 一段代码
(参数类型 参数名称) -> { 代码语句 }
格式说明:
- 小括号内的语法与传统方法参数列表一致:无参数则留空;多个参数则用逗号隔开。
- -> 是新引入的语法格式,代表指向动作。
- 大括号内的语法与传统方法体要求一致。
常见写法
(i) -> i *2
i -> i*2
(int i) -> i*2
(int i) -> {
return i*2
}
举例:
@FunctionalInterface
interface Interface1 {
int dobuldNum(int i);
}
public class lambdaDemo1 {
public static void main(String[] args) {
//lambda写法
// Interface1 i1 = (i) -> i*2;
Interface1 i1 = (int i) -> {
return i*2;
};
System.out.println("lambda写法: " + i1.dobuldNum(2));
//传统写法
Interface1 i2 = new Interface1() {
@Override
public int dobuldNum(int i) {
return i*2;
}
};
System.out.println("传统写法: " + i2.dobuldNum(2));
}
}
运行结果:
lambda写法: 4
传统写法: 4
@FunctionalInterface注解是用来规范接口设置的,加了这个注解后,接口内只能有一个方法,不然编译就会报错,但java8新特性还可以有一个默认方法,下面讲到。

默认方法
java8新特性在接口中引入了一个默认方法;
虽然不能在接口中加多个方法,但默认方法可以,这样还是可以用lambda表达式。

Interface1 i1 = (i) -> i*2;
System.out.println("lambda写法: " + i1.dobuldNum(2));
System.out.println("默认方法调用: " + i1.dobuldNum1(2));
Function代替自定义的接口
不需要写接口,用Function代替
一般我们会去写一个接口,然后作为参数引用,在lambda中,现在可以用自带的函数接口。
interface Interface1 {
int dobuldNum(int i);
}
class MyMoney {
private final int money;
public MyMoney(int money) {
this.money = money;
}
public void printMoney(Interface1 interface1) {
System.out.println("存款:" + interface1.dobuldNum(this.money));
}
// 输入输出相同还可以用UnaryOperator<Integer>代替
public void printMoney2(Function<Integer, Integer> interface1) {
System.out.println("存款:" + interface1.apply(this.money));
}
}
public class lambdaDemo1 {
public static void main(String[] args) {
MyMoney me = new MyMoney(9999);
System.out.println("接口:");
me.printMoney(i -> 10*i);
System.out.println("用Function代替接口:");
me.printMoney2(i -> 20*i);
}
}
运行结果:
接口:
存款:99990
用Function代替接口:
存款:199980
函数接口支持链式操作
函数接口中,提供了一些默认的方法,比如调用andThen就可以实现链式操作。
Function<Integer, Integer> money = i -> i + 1;
me.printMoney2(money.andThen(i -> i + 20));
自带的函数接口

// 断言函数接口
Predicate<Integer> predicate = i -> i > 0;
// 带类型的函数接口,可以省略掉范型
IntPredicate predicate1 = i -> i > 0;
// 打印false
System.out.println(predicate.test(-9));
// 消费的函数接口
Consumer<String> consumer = s -> System.out.println(s);
consumer.accept("有入参没有出参");
// 输入T输出R的函数接口
Function<Integer, String> function = f -> "入参出参不一样" + f;
String apply = function.apply(2333);
System.out.println(apply);
// 提供的函数接口
Supplier<String> supplier = () -> "没有入参, 只有出参";
String su = supplier.get();
System.out.println(su);
方法引用
Consumer<String> consumer = s -> System.out.println(s);
// 方法引用,等价于上面的操作
Consumer<String> consumer1 = System.out::println;
- 静态方法的方法引用
- 非静态方法的方法引用
- 非静态方法用类名来方法引用
- 不带参数的方法引用
- 带参数的方法引用
class Dog {
private String name = "小黄";
private int food;
public Dog() {
}
public Dog(String name) {
this.name = name;
}
public static void bark(Dog dog) {
System.out.println(dog.name + "叫了");
}
public int eat(int num) {
System.out.println("吃了" + num + "斤狗粮");
this.food -= num;
return this.food;
}
}
public class FunctionDemo1 {
public static void main(String[] args) {
// 静态方法的方法引用
Consumer<Dog> consumer = Dog::bark;
Dog dog = new Dog();
consumer.accept(dog);
// 非静态方法引用
// 非静态方法,使用对象实例的方法引用
// Function<Integer, Integer> function = dog::eat;
// UnaryOperator<Integer> function = dog::eat;
IntUnaryOperator function = dog::eat;
System.out.println("还剩下" + function.applyAsInt(2) + "斤");
// 非静态方法也可以用类名来方法引用
// 其实每个非静态方法的第一个参数都有默认的this,写不写都会有,是可以省略的,
// 所以在方法中才可以用类的属性this.food,
BiFunction<Dog, Integer, Integer> eatFunction = Dog::eat;
System.out.println("还剩下" + eatFunction.apply(dog, 2) + "斤");
// 不带参数的构造函数的方法引用
Supplier<Dog> supplier = Dog::new;
System.out.println("你没有对象?现在有了,我帮你new了一个: " + supplier.get());
// 带参数的构造函数的方法引用
Function<String, Dog> function1 = Dog::new;
System.out.println("现在的对象不喜欢?你要什么要求写出来,重新帮你new一个: " + function1.apply("大长腿"));
}
}
变量引用
lambda的执行体里面引用外面的变量,外面的变量必须是final的,平时我们没写final也可以,这是java8的新特性,隐藏了final,其实还是不能对变量做改变的。就如下面的str,如果在调用前加上str = “”,就会报错。
public class LambdaDemo3 {
public static void main(String[] args) {
String str = "abcd";
Consumer<String> consumer = s -> System.out.println(s + str);
consumer.accept("10");
}
}
级联表达式和柯里化
柯里化就是把多个参数的函数变成统一的只有一个参数的函数。2个参数变成一个参数,那么他的返回值必定还是一个函数。
如下:
// 实现了x+y的级联表达式
Function<Integer, Function<Integer, Integer>> fun = x -> y -> x + y;
System.out.println(fun.apply(2).apply(3));
实际上就是一个2个参数的相加表达式。
IntBinaryOperator addExp = (x, y) -> x + y;
System.out.println(addExp.applyAsInt(2, 3));
// 或者
BinaryOperator<Integer> operator = (x, y) -> x + y;
System.out.println(operator.apply(2, 3));
统一之后,就可以使用循环等处理了
Function<Integer, Function<Integer, Function<Integer, Integer>>> fun2 = x -> y -> z -> x + y + z;
System.out.println(fun2.apply(2).apply(3).apply(4));
// 统一了之后 使用循环调用
int[] nums = {2,3,4};
Function f = fun2;
for (int i = 0; i < nums.length; i++) {
if (f instanceof Function) {
Object obj = f.apply(nums[i]);
if (obj instanceof Function) {
f = (Function) obj;
} else {
System.out.println("调用结束:结果为" + obj);
}
}
}
lambda的省略格式
lambda强调的是“做什么”而不是“怎么做“,所以凡是可以根据上下文推断的信息,都可以省略。
省略规则如下:
- 小括号内参数的类型可以省略。
- 如果小括号内有且只有一个参数,则小括号可以省略。
- 如果大括号内有且仅有一个语句,则无论是否有返回值,都可以省略大括号、return关键字及语句分号。
lambda的使用前提
lambda的语法非常简洁,完全没有面向对象复杂的束缚,但是使用时有几个问题需要特别注意:
- 使用lambda必须具有接口,而且要求接口有且只有一个抽象方法(函数式接口)。
无论是JDK内置的Runnable、Comparator接口还是自定义的接口,只有当接口中的抽象方法存在且唯一时,才可以使用。 - 使用lambda必须具有上下文推断。
也就是方法的参数或者局部变量类型必须为lambda对应的接口类型,才能使用lambda作为该接口的实例。
本文介绍了函数式编程的核心思想,对比面向对象,强调通过代码传递而非对象操作。重点讲解了lambda表达式的标准格式、示例和Java 8中的使用,以及lambda的省略规则和使用注意事项。
663

被折叠的 条评论
为什么被折叠?



