1 函数式编程特点
- 函数可作为返回值
- 限制副作用:限制修改函数外部的状态的途径
显式函数:函数与外界交换数据的唯一渠道是参数和返回值
隐式函数:除参数和返回值外,还会读取或改变外部信息 - 声明式:操作被封装到程序库中,不需要指定明确的执行语句,只需声明需求
public void imperative() { int[] iArr= {1,3,4,5,6,2}; for(int i=0;i<iArr.length;i++) { System.out.println(iArr[i]); } } public void declarative() { int[] iArr= {1,3,4,5,6,2}; Arrays.stream(iArr).forEach(System.out::println); }
- 不变对象:传递的对象不会轻易修改
- 易于并行:对象处于不变状态,不用考虑一致性问题
- 代码简洁
2 函数式接口
2.1 FunctionalInterface注释
FunctionalInterface用于注释函数式接口(只有单一抽象方法的接口)
@FunctionalInterface
public static interface IntHandler{
void handle(int i);
}
FunctionalInterface的作用
强制检查一个接口是否符合函数式接口的规范,否则产生编译错误(l类似@override)
函数式接口的判定
- 函数式接口的两个关键:单一和抽象(java8允许接口中存在实例方法)
- 接口中任何被Object实现的方法不能视为抽象方法
//不是函数式接口 interface NonFunc { boolean equals(Object obj); }
2.2 接口默认方法
- Java8之前,接口只能包含抽象方法
- Java8开始,接口可以包含实例方法
接口默认方法实现
使用default关键字在接口中定义实例方法
public interface IHorse{
void eat();
default void run(){
System.out.printIn("hourse run");
}
}
默认接口方法实现类多继承功能
类似多继承,Mule同时具有IHorse和IAnimal的实例方法
默认接口方法解决多继承的问题
当类实现的接口中有多个相同的default方法,实现类必须重写该方法.
public class Mule implements IHorsefIDonkey,lAnimal{
@Override
public void run(){
IHorse.super.run();//沿用IHorse的run()
}
@Override
public void eat() {
System.out.println('Mule eat');
}
}
2.3 常见函数式接口
Comparator
- 比较方法
@FunctionalInterface public interface Comparator<T> { int compare(T o1, T o2); ... }
- 再次比较方法
default Comparator<T> thenComparing(Comparator<? super T> other) { Objects.requireNonNull(other); return (Comparator<T> & Serializable) (c1, c2) -> { int res = compare(c1, c2); return (res != 0) ? res : other.compare(c1, c2); }; }
- 字符串长度排序,继而字符顺序排序的比较器
Comparator<String> cmp = Comparator.comparinglnt(String;:length).thenComparing(String.CASE_INSENSITIVE_ORDER);
Runnable
创建任务
@FunctionalInterface
public interface Runnable {
public abstract void run();
}
Predicate
判断实例是否符合条件
@FunctionalInterface
public interface Predicate<T> {
boolean test(T t);
...
}
Function
经由一个对象产生另一个对象
@FunctionalInterface
public interface Function<T, R> {
R apply(T t);
..
}
3 Lambda表达式
3.1 Lambda表达式基础
Lambda表达式结构
- 参数列表
- 箭头:用于分割参数列表和Lambda主体
- Lambda主体:执行语句/返回值
两种Lambda主体 (1) expression:Lambda表达式的返回值 (2) {statements;}:执行语句(return也可返回值)
3.2 Lambda表达式使用场景
函数式接口
Runnable r1 = () -> System.out.println("Hello World");
Runnable r2 = new Runnable(){
public void run(){
System.out.println("Hello World");
}
};
代码分析
- r1和r2的功能相同
- Lambda表达式以内联的形式为函数式接口的抽象方法(函数描述符)提供实现, 并把整个表达式作为函数式接口的实例
环绕执行模式
- 常见的执行流程分为三段:初始化+执行任务+回收资源
- 初始化和回收资源对不同的操作是相同的,但执行的任务是不同的
- 行为参数化可简化执行流程
3.3 Lambda实现行为化参数的过程
第0步—分析原代码需要行为参数化的部分
public static String processFile() throws IOException {
try (BufferedReader br =new BufferedReader(new FileReader("data.txt"))) {
return br.readLine();
}
}
processFile()只能读取文件的一行,需要扩展多种文件读取方式
第1步—使用函数式接口传递行为
- 编写函数式接口
process()为处理文件读取操作@FunctionalInterface public interface BufferedReaderProcessor { String process(BufferedReader b) throws IOException; }
- 改写processFile方法
public static String processFile(BufferedReaderProcessor p) throws IOException { try (BufferedReader br = new BufferedReader(new FileReader("data.txt"))) { return p.process(br); } }
第2步:传递Lambda
处理一行:
String oneLine = processFile((BufferedReader br) -> br.readLine());
处理两行:
String twoLines = processFile((BufferedReader br) -> br.readLine() + br.readLine());
3.4 类型检查、推断以及限制
3.4.1 类型检查
- Lambda的类型是从上下文推断出来的.
- Lambda表达式只提供参数列表和Lambda主体,若目标类型符合上下文,则使用正确
类型推断过程
String oneLine = processFile((BufferedReader br) -> br.readLine());
-
找到processFile方法声明
public static String processFile(BufferedReaderProcessor p)
-
Lambda表达式是BufferedReaderProcessor类型
-
BufferedReaderProcessor是函数式接口,process抽象方法接受BufferedReader,并返回String,符合processFile
一对多关系
同一个Lambda表达式可以与不同的函数式接口联系
Callable<Integer> c = () -> 42;
Procucer<Integer> p = () -> 42;
3.4.2 类型推断
目标类型确定函数描述符,而函数描述符可以确定Lambda参数的类型
无类型推断
Comparator<Apple> c =(Apple a1, Apple a2) -> a1.getWeight().compareTo(a2.getWeight());
有类型推断
Comparator<Apple> c =(a1, a2) -> a1.getWeight().compareTo(a2.getWeight());
当Lambda仅有一个类型需要推断的参数时,参数名称两边的括号也可以省略
3.4.3 使用局部变量
类似匿名类,Lambda可以使用自由变量(外层作用域变量),但变量必须由final修饰
int portNumber = 1337;
Runnable r = () -> System.out.println(portNumber);
portNumber = 31337;//报错,jvm会自动使用final修饰portNumber
final限制原因
- 实例变量保存在堆,局部变量保存在栈.Lambda在另一线程中使用,线程启动时,使用的变量可能已经不存在.final修饰变量,使变量存储在常量池中.
- Lambda常用于并发,不希望改变外部变量(引起不一致性)
4 方法引用
方法引用就是替代那些转发参数的Lambda表达式的语法糖
4.1 方法引用的结构与类型
方法引用结构
类
名
/
实
例
名
:
:
方
法
名
/
n
e
w
类名/实例名::方法名/new
类名/实例名::方法名/new
方法引用类型
- 类的静态方法
- 类的实例方法
- 对象的实例方法
- 构造函数
4.2 构造函数的方法引用
无参构造
Supplier<Apple> c1 = Apple::new;
//Supplier<Apple> c1 = () -> new Apple();
Apple a1 = c1.get();//构造对象结束
有参构造
//有参构造
Function<Integer, Apple> c2 = Apple::new;
//Function<Integer, Apple> c2 = (weight) -> new Apple(weight);
Apple a2 = c2.apply(110);//构造对象结束
自定义有参构造
-
确定构造函数的参数列表(无现成的函数式接口)
Color(int, int, int)
-
构造函数式接口
public interface TriFunction<T, U, V, R>{ R apply(T t, U u, V v); }
-
构造对象
TriFunction<Integer, Integer, Integer, Color> colorFactory = Color::new; Color c=colorFactory.apply(255,255,255);
4.3 Lambda和方法引用实战
行为参数化
sort()根据Comparator来实施具体的排序
void sort(Comparator<? super E> c)
使用匿名类
inventory类型类List<Apple>
inventory.sort(new Comparator<Apple>() {
public int compare(Apple a1, Apple a2){
return a1.getWeight().compareTo(a2.getWeight());
}
});
使用Lambda表达式
inventory.sort((Apple a1, Apple a2)-> a1.getWeight().compareTo(a2.getWeight()));
Comparator具有一个叫作comparing的静态辅助方法,它可以接受一个Function来提取Comparable键值,并生成一个Comparator对象
inventory.sort(Comparator.comparing((a) -> a.getWeight()));
使用方法引用
inventory.sort(Comparator.comparing(Apple::getWeight));
5 复合Lambda表达式
多个简单的Lambda复合成复杂的表达式
- 两个谓词之间做一个or操作,组合成一个更大的谓词
- 让一个函数的结果成为另一个函数的输入
函数式接口中只有一个抽象方法,要实现复合,需要借助默认方法
5.1 比较器复合
逆序
inventory.sort(comparing(Apple::getWeight).reversed());
比较器链
inventory.sort(comparing(Apple::getWeight).reversed().thenComparing(Apple::getCountry));
5.2 谓词复合
public boolean redApple(Apple a){
if(a.getColor=="red")
return true;
return false;
}
negate
非红苹果
Predicate<Apple> notRedApple = redApple.negate();
and
重量>150的红苹果
Predicate<Apple> redAndHeavyApple =redApple.and(a -> a.getWeight() > 150);
or
红苹果&&重量>150||绿苹果
negate,or,and按照从左到右调用顺序确定优先级
Predicate<Apple> redAndHeavyAppleOrGreen =redApple.and(a -> a.getWeight() > 150).or(a -> "green".equals(a.getColor()));
5.3 函数复合
Function<Integer, Integer> f = x -> x + 1;
Function<Integer, Integer> g = x -> x * 2;
Function<Integer, Integer> h1 = f.andThen(g);//g(f(x))
Function<Integer, Integer> h2 = f.compose(g);//f(g(x))