Java8-函数式编程
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步—使用函数式接口传递行为
- 编写函数式接口
@FunctionalInterface public interface BufferedReaderProcessor { String process(BufferedReader b) throws IOException; }
process()为处理文件读取操作 - 改写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 方法引用的结构与类型
方法引用结构
类名/实例名::方法名/new类名/实例名::方法名/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))
- 点赞
- 收藏
- 分享
- 文章举报
- Java 8 函数式编程
- 【利用脚本将java回归到面向函数式编程】
- 跟上 Java 8 : 函数式编程
- 函数式编程之 Lambda 表达式的引出_Java8 实践
- Java8 Lambda表达式 函数式编程 方法引用
- java基础[10]-函数式编程及Lambda
- Java经典类库-Guava中的函数式编程讲解
- Java8学习笔记(一)—— 函数式编程的四个基本接口
- Java8:函数式编程与Lambda表达式
- Java FP(Java8): Java中函数式编程的Map和Fold(Reduce)
- 面向Java开发者的函数式编程
- paip.提升效率---filter map reduce 的java 函数式编程实现
- java8 函数式编程
- Java中函数式编程的谓词函数(Predicates)
- Java经典类库-Guava中的函数式编程讲解
- 简要介绍JAVA8中的函数式编程;Brief Introduction to Functional Programming in Java 8
- java8入门必备——函数式编程思维——过滤函数的同义异名问题
- paip.提升效率---filter map reduce 的java 函数式编程实现
- java 1.8 函数式编程
- Java编程的逻辑 (93) - 函数式数据处理 (下)