Java8-函数式编程

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 方法引用的结构与类型

方法引用结构
类 名 / 实 例 名 : : 方 法 名 / n e w 类名/实例名::方法名/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))
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值