您的位置:首页 > 编程语言 > Java开发

Java8-函数式编程

2020-02-04 21:53 253 查看

1 函数式编程特点

  1. 函数可作为返回值
  2. 限制副作用:限制修改函数外部的状态的途径
     显式函数:函数与外界交换数据的唯一渠道是参数返回值
     隐式函数:除参数返回值外,还会读取或改变外部信息
  3. 声明式:操作被封装到程序库中,不需要指定明确的执行语句,只需声明需求
    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);
    }
  4. 不变对象:传递的对象不会轻易修改
  5. 易于并行:对象处于不变状态,不用考虑一致性问题
  6. 代码简洁

2 函数式接口

2.1 FunctionalInterface注释

FunctionalInterface用于注释函数式接口(只有单一抽象方法的接口)

@FunctionalInterface
public static interface IntHandler{
void handle(int i);
}

FunctionalInterface的作用
强制检查一个接口是否符合函数式接口的规范,否则产生编译错误(l类似@override)

函数式接口的判定

  1. 函数式接口的两个关键:单一抽象(java8允许接口中存在实例方法)
  2. 接口中任何被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

  1. 比较方法
    @FunctionalInterface
    public interface Comparator<T> {
    int compare(T o1, T o2);
    ...
    }
  2. 再次比较方法
    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);
    };
    }
  3. 字符串长度排序,继而字符顺序排序的比较器
    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表达式结构

  1. 参数列表
  2. 箭头:用于分割参数列表和Lambda主体
  3. 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");
}
};

代码分析

  1. r1和r2的功能相同
  2. 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步—使用函数式接口传递行为

  1. 编写函数式接口
    @FunctionalInterface
    public interface BufferedReaderProcessor {
    String process(BufferedReader b) throws IOException;
    }
    process()为处理文件读取操作
  2. 改写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());
  1. 找到processFile方法声明

    public static String processFile(BufferedReaderProcessor p)
  2. Lambda表达式是BufferedReaderProcessor类型

  3. 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限制原因

  1. 实例变量保存在堆,局部变量保存在栈.Lambda在另一线程中使用,线程启动时,使用的变量可能已经不存在.final修饰变量,使变量存储在常量池中.
  2. Lambda常用于并发,不希望改变外部变量(引起不一致性)

4 方法引用

方法引用就是替代那些转发参数的Lambda表达式的语法糖

4.1 方法引用的结构与类型

方法引用结构
类名/实例名::方法名/new类名/实例名::方法名/new类名/实例名::方法名/new
方法引用类型

  1. 类的静态方法
  2. 类的实例方法
  3. 对象的实例方法
  4. 构造函数

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))
  • 点赞
  • 收藏
  • 分享
  • 文章举报
Athazement 发布了9 篇原创文章 · 获赞 0 · 访问量 530 私信 关注
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: