OO(object oriented,面向对象)是抽象数据,FP(functional programming,函数式编程)是抽象行为。
纯粹的函数式编程在安全性上面更进一步,它增加了额外的约束, 即所有数据必须是不可变的: 设置一次,永不改变.
将值传递给函数, 该函数生成新值,但却从不修改自身以外的任何东西, 因为传入的数据是不可变的,这也就间接的解决了并发变成对数据操作的问题, 也就是不会被其他线程改变数据所影响,因为数据是不能被改变的
Lambda表达式:
是使用最小可能语法去编写函数的定义
java8中出现的, 参数与函数体用->进行分割 ,左边是参数 右边是方法体, 与单独的定义类和匿名内部类是等价的
1.lambda表达式产生函数,而不是类, 在jvm(java虚拟机)中,一切都是一个类,因此在幕后操作使得lambda表达式看起来像是一个函数- 你可以假装它是一个函数
2.lambda的语法尽量的少,这样可以让它易于编写和使用
注意:lambda表达式代表的接口只能有一个抽象方法, 可以有default方法跟静态方法
// functional/LambdaExpressions.java
interface Description {
String brief();
}
interface Body {
String detailed(String head);
}
interface Multi {
String twoArg(String head, Double d);
}
public class LambdaExpressions {
static Body bod = h -> h + " No Parens!"; // [1]
static Body bod2 = (h) -> h + " More details"; // [2]
static Description desc = () -> "Short info"; // [3]
static Multi mult = (h, n) -> h + n; // [4]
static Description moreLines = () -> { // [5]
System.out.println("moreLines()");
return "from moreLines()";
};
public static void main(String[] args) {
System.out.println(bod.detailed("Oh!"));
System.out.println(bod2.detailed("Hi!"));
System.out.println(desc.brief());
System.out.println(mult.twoArg("Pi! ", 3.14159));
System.out.println(moreLines.brief());
}
}
输出结果
Oh! No Parens!
Hi! More details
Short info
Pi! 3.14159
moreLines()
from moreLines()
[1]当只有一个参数, ()是可以省略的
[2]当然不省也没问题
[3]没有参数是, ()就不能省略了
[4]多参数就(arg, arg)
正常的lambda表达式的方法体都是单行的,如果要方法体是多行的 就要在->后面加上{}
单行的方法是不带retuen的 , 由lambda直接返回, 多行就需要return了
被 Lambda 表达式引用的局部变量必须是 final
或者是等同 final
效果的。
lambda的递归
递归就是自己调自己, 在lambda中, 递归的入参必须是实例变量或者静态变量
IntCall fib;这种就是实例变量
static IntCall fact;这种就是静态变量
我的理解是,实例变量跟静态变量他们的指向的堆中的地址是固定的
方法引用
引用就是把被引用的方法赋给具有相同方法签名的对象上
也是java8中出现的 用::分割 ::左边是类或者对象的名称 ,右边是方法的名称,但是没有参数列表
// functional/MethodReferences.java
import java.util.*;
interface Callable { // [1]
void call(String s);
}
class Describe {
void show(String msg) { // [2]
System.out.println(msg);
}
}
public class MethodReferences {
static void hello(String name) { // [3]
System.out.println("Hello, " + name);
}
static class Description {
String about;
Description(String desc) { about = desc; }
void help(String msg) { // [4]
System.out.println(about + " " + msg);
}
}
static class Helper {
static void assist(String msg) { // [5]
System.out.println(msg);
}
}
public static void main(String[] args) {
Describe d = new Describe();
Callable c = d::show; // [6]
c.call("call()"); // [7]
c = MethodReferences::hello; // [8]
c.call("Bob");
c = new Description("valuable")::help; // [9]
c.call("information");
c = Helper::assist; // [10]
c.call("Help!");
}
}
输出为
call()
Hello, Bob
valuable information
Help!
这里可以看出, 只有方法签名相同,就能把对方法的引用赋给接口Callable
[1] 我们从单一方法接口开始(同样,你很快就会了解到这一点的重要性)。
[2] show()
的签名(参数类型和返回类型)符合 Callable 的 call()
的签名。
[3] hello()
也符合 call()
的签名。
[4] help()
也符合,它是静态内部类中的非静态方法。
[5] assist()
是静态内部类中的静态方法。
[6] 我们将 Describe 对象的方法引用赋值给 Callable ,它没有 show()
方法,而是 call()
方法。 但是,Java 似乎接受用这个看似奇怪的赋值,因为方法引用符合 Callable 的 call()
方法的签名。
[7] 我们现在可以通过调用 call()
来调用 show()
,因为 Java 将 call()
映射到 show()
。
[8] 这是一个静态方法引用。
[9] 这是 [6] 的另一个版本:对已实例化对象的方法的引用,有时称为绑定方法引用。
[10] 最后,获取静态内部类中静态方法的引用与 [8] 中通过外部类引用相似。
::左边的,也就是被赋予方法的接口(且只能是接口才能被赋予方法),只能是只有一个普通抽象方法的函数,::右边的函数无所谓
这里你会发现, lambda表达式是直接给接口赋予一个现写的方法, 而方法引用是把别人的方法给那个接口引用过来,变相的实现了接口
注意 正常的情况是 需要用对象来绑定这个方法的(静态方法除外), 但是也会有没有对象的情况,称为未绑定的方法引用
eg
// functional/UnboundMethodReference.java
// 没有方法引用的对象
class X {
String f() { return "X::f()"; }
}
interface MakeString {
String make();
}
interface TransformX {
String transform(X x);
}
public class UnboundMethodReference {
public static void main(String[] args) {
// MakeString ms = X::f; // [1]
TransformX sp = X::f;
X x = new X();
System.out.println(sp.transform(x)); // [2]
System.out.println(x.f()); // 同等效果
}
}
未绑定的方法引用
[1] 的指令你可以看出这样是不允许的,因为在MakeString()方法里面并没有任何产生对象的机会
这里 makeString不行是因为它的方法是无参数的,也就没有那种能把对象传进取的情况, \
而接口TransformX是有参的, 可以接受外部传入对象的可能,尽管没引用的方法跟要被赋予的方法的方法签名不一样
[2] 的结果有点像脑筋急转弯。我拿到未绑定的方法引用,并且调用它的transform()
方法,将一个X类的对象传递给它,最后使得 x.f()
以某种方式被调用。Java知道它必须拿到第一个参数,该参数实际就是this
,然后调用方法作用在它之上。
// functional/MultiUnbound.java
// 未绑定的方法与多参数的结合运用
class This {
void two(int i, double d) {}
void three(int i, double d, String s) {}
void four(int i, double d, String s, char c) {}
}
interface TwoArgs {
void call2(This athis, int i, double d);
}
interface ThreeArgs {
void call3(This athis, int i, double d, String s);
}
interface FourArgs {
void call4(
This athis, int i, double d, String s, char c);
}
public class MultiUnbound {
public static void main(String[] args) {
TwoArgs twoargs = This::two;
ThreeArgs threeargs = This::three;
FourArgs fourargs = This::four;
This athis = new This();
twoargs.call2(athis, 11, 3.14);
threeargs.call3(athis, 11, 3.14, "Three");
fourargs.call4(athis, 11, 3.14, "Four", 'Z');
}
}
构造函数的引用
构造函数也可以进行引用, 通过捕获构造函数的引用,然后通过该引用调用构造函数, 格式为 :: new
// functional/CtorReference.java
class Dog {
String name;
int age = -1; // For "unknown"
Dog() { name = "stray"; }
Dog(String nm) { name = nm; }
Dog(String nm, int yrs) { name = nm; age = yrs; }
}
interface MakeNoArgs {
Dog make();
}
interface Make1Arg {
Dog make(String nm);
}
interface Make2Args {
Dog make(String nm, int age);
}
public class CtorReference {
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);
}
}
可以看出, 虽然有三个构造方法, 但是在引用的形式上是一样的 均为::new
编译器会根据它被赋予的对象的方法的参数列表来定位具体的构造函数
Runnable接口
Runnable 接口自 1.0 版以来一直在 Java 中,因此不需要导入。它也符合特殊的单方法接口格式:它的方法 run()
不带参数,也没有返回值。因此,我们可以使用 Lambda 表达式和方法引用作为 Runnable:
// 方法引用与 Runnable 接口的结合使用
class Go {
static void go() {
System.out.println("Go::go()");
}
}
public class RunnableMethodReference {
public static void main(String[] args) {
new Thread(new Runnable() {
public void run() {
System.out.println("Anonymous");
}
}).start();
new Thread(
() -> System.out.println("lambda")
).start();
new Thread(Go::go).start();
}
}
可以看出 正常用Runnable()是一个使用匿名内部类的方法, 来调用Thread的构造函数入参,然后调用start()方法
因为参数的签名相同, 就可以用lambda表达式或者方法引用来实现
函数式接口
lambda表达式跟方法引用都需要被赋值, 同时赋值需要类型信息,才能使编译器保证类型的正确性,为了解决这个问题,java8以内了java.util.function
包,它包含了一些接口,这些接口是lambda表达式跟方法引用的目标类型,每一个接口都只有一个抽象方法,叫做函数式方法,这些接口的出现是为了让你不必特意的去创建一个单抽象方法
注解:@FunctionalInterface
作用是强制接口”函数式方法”模式, 也就是一个接口只有一个抽象方法
被@FunctionalInterface注解的接口一定可以用lambda表达式跟方法引用
// functional/FunctionalAnnotation.java
@FunctionalInterface
interface Functional {
String goodbye(String arg);
}
interface FunctionalNoAnn {
String goodbye(String arg);
}
/*
@FunctionalInterface
interface NotFunctional {
String goodbye(String arg);
String hello(String arg);
}
产生错误信息:
NotFunctional is not a functional interface
multiple non-overriding abstract methods
found in interface NotFunctional
*/
public class FunctionalAnnotation {
public String goodbye(String arg) {
return "Goodbye, " + arg;
}
public static void main(String[] args) {
FunctionalAnnotation fa =
new FunctionalAnnotation();
Functional f = fa::goodbye;
FunctionalNoAnn fna = fa::goodbye;
// Functional fac = fa; // Incompatible
Functional fl = a -> "Goodbye, " + a;
FunctionalNoAnn fnal = a -> "Goodbye, " + a;
}
}
这里就体现了java8 的特别之处, 你会发现 接口Functional跟FunctionalNoAnn 不需要被实现类重写方法, 而是通过lambda表达式或方法引用来对其抽象方法赋方法体
而函数式方法这个包的目的就是 它给你提供一个接口->你不需要自己去创建接口,就能实现方法的共享与复用
高阶函数
高阶函数就是 入参 和返回值是函数的方法
import java.util.function.*;
class I {
@Override
public String toString() { return "I"; }
}
class O {
@Override
public String toString() { return "O"; }
}
public class TransformFunction {
static Function<I,O> transform(Function<I,O> in) {
return in.andThen(o -> {
System.out.println(o);[2]
return o;
});
}
public static void main(String[] args) {
Function<I,O> f2 = transform(i -> {
System.out.println(i);[1]
return new O();
});
O o = f2.apply(new I());
}
}
输出为
I
O
这个是一个即传入函数,又返回函数的高阶函数, 在main中的先传入new I()执行[1], 接着执行[2], andThen就是一个函数式接口, 就是执行完in,接着执行andThen
闭包
闭包就是一个可以调用的对象,这个对象包含一些信息,来源于创建它的作用域
函数表达式这块涉及到的闭包就是lambda表达式, java8提供了有限的闭包支持,
情况1
public class Closure1 {
int i;
IntSupplier makeFun(int x) {
return () -> x + i++;
}
}
import java.util.function.*;
public class SharedStorage {
public static void main(String[] args) {
Closure1 c1 = new Closure1();
IntSupplier f1 = c1.makeFun(0);
IntSupplier f2 = c1.makeFun(0);
IntSupplier f3 = c1.makeFun(0);
System.out.println(f1.getAsInt());
System.out.println(f2.getAsInt());
System.out.println(f3.getAsInt());
}
}
输出
0
1
2
想这个 i 是闭包外的量, 也就是作用域外的变量, 所以可以传入lambda表达式中, 注意 lambda表达式的局部变量必须被final修饰或者等同final效果的变量 否则会报错的, 这里的final并不是指值不可修改, 而是变量的引用不可修改
等同final效果 -> 它是java8出现的 , 定义为局部变量并不会改变 ,那么就是等同final效果的
为什么lambda的传入的局部变量要是final或是等同final效果呢
因为局部变量是存放在栈帧上的, 用完就没了.而传到lambda表达式里的局部变量实际上是一个复制品而不是它本身, 所以你对这个复制品进行操作就会造成混淆,因为原始的局部变量可能已经消失了或者怎样,因此java为了解决这一点,干脆就lambda只能用final或是等同final效果的局部变量
第二点是违反了线程安全的设置, 在java中 ,局部变量要求是只对调用它的那个线程是私有的,这样来保证线程安全,而lambda表达式的实际是另开线程的匿名内部类,这样的话就变相的导致私有的线程数据被共享,这是java安全所不允许的
像这种局部变量是变化的 就会报错
import java.util.function.*;
public class Closure3 {
IntSupplier makeFun(int x) {
int i = 0;
// x++ 和 i++ 都会报错:
return () -> x++ + i++;
}
}