LAMBDA与环绕执行及简单应用

环绕执行代码简化

1 概述

         在业务开发中,经常会碰到一些类似的业务逻辑,它们都要走相似的步骤,具体的入参出参和执行过程不同。因而在代码落地时,会有很多重复,譬如发送HTTP请求,读取EXCEL文件等等。

         在java8推出来后,针对环绕执行模式(即资源处理时,常见的模式是打开一个资源,做一些处理,然后关闭资源。这些准备和清理资源阶段总是很类似,并且会围绕执行处理的那些重要代码。)重复代码的问题,可以通过泛型解决入参出参差异的问题,通过lambda表达式(函数式编程)传递方法解决执行过程不同的问题。这样环绕代码只需写一次。

2 简单示例1,筛选list集合中的优惠券

         筛选金额大于100的优惠券,一般会有如下代码

public static List<Coupon> doCheck(List<Coupon> coupons){
	if(CollectionUtils.isEmpty(coupons)){
		return null;
	}
	
	List<Coupon> selected = new ArrayList<>();
	for(Coupon coupon: coupons){
		if(coupon.getMoney() > 100){
			selected.add(coupon);
		}
	}
	
	return selected;
}

         如果另有需求,要调整发券金额筛选条件,或者以发券时间为筛选条件,或者是金额和时间共同作为筛选条件等等,该怎么办呢?

         A为每个不同逻辑都去重复写for循环,此办法势必会带来大量的重复代码,而且可读性不强

         B将各种筛选条件作为方法的参数传入,此时则需要区分不同的筛选模式和筛选参数,会使得整个筛选逻辑特别复杂,可读性更差,后续新增或修改筛选条件很难维护。

         能不能将整个筛选逻辑抽象出来呢?这里引入java.util.function中的Predicate,它接收一个泛型对象参数,返回boolean,用它接收具体的筛选逻辑。

Predicate<Coupon> predicate = new Predicate<Coupon>(){
			@Override
			public boolean test(Coupon coupon) {
				if(coupon.getMoney() > 100){
					return true;
				}
				return false;
			}
		};

         因为Predicate是函数式接口(一般指只定义了一个抽象方法的接口),在java8中,可以简写为

Predicate<Coupon> predicate = (Coupon coupon)->coupon.getMoney() > 100;

         然后将环绕代码调整为

public static List<Coupon> doCheck(List<Coupon> coupons, Predicate<coupon> checkIn){
	if(CollectionUtils.isEmpty(coupons)){
		return null;
	}
	
	List<Coupon> selected = new ArrayList<>();
	for(Coupon coupon: coupons){
		if(checkIn.test(coupon)){
			selected.add(coupon);
		}
	}
	
	return selected;
}

         将整个筛选框架定下来后,后续每次调用筛选逻辑,都只需要调整具体的筛选条件比较部分代码了,譬如

doCheck(coupons, (coupon)->coupon.getMoney>100);
doCheck(coupons, (coupon)->coupon.getMoney>100 && coupon.getDeadline<’2019-09-09’);

         当然对list等数据的筛选,可以使用stream处理。这里举例只是为了说明问题。

3 简单示例2,写数据到excel

先定义函数接口

package com.excel;

import java.util.List;

import org.apache.poi.ss.usermodel.Sheet;

@FunctionalInterface
public interface WriteSheetContent<T> {
	
	void wirte(Sheet sheet, List<T> data);

}

再定义执行写excel的调用方法

package com.excel;

import java.io.File;
import java.io.FileOutputStream;
import java.io.OutputStream;
import java.util.List;

import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.usermodel.Workbook;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.springframework.util.CollectionUtils;

public class WriteExcel {
	
	public static <T> File write(List<T> datas, String filePath, String sheetName, WriteSheetContent<T> consume){
		
		if(CollectionUtils.isEmpty(datas)){
			return null;	
		}
		
		File file = null;
		OutputStream os= null;
        Workbook workbook = null;
        try {
        	file = new File(filePath);
        	if(! file.getParentFile().exists()){
        		boolean mkdir = file.getParentFile().mkdirs();
                if (!mkdir) {
                    throw new Exception("mkdir root path error");
                }
        	}
        	workbook = new XSSFWorkbook();
        	
        	Sheet sheet = workbook.createSheet(sheetName);

        	consume.wirte(sheet, datas);
    		
            os = new FileOutputStream(file);
            workbook.write(os);
            
		} catch (Exception e) {
            e.printStackTrace();
		} finally{
			try {
				if(os != null){
					os.close();
				}
				if(workbook != null){
					workbook.close();
				}
			} catch (Exception e) {
				e.printStackTrace();
			}
		}
		
		return file;
	}

}

最后就是具体的生成sheet的过程

package com.excel;

import java.util.ArrayList;
import java.util.List;

import org.apache.poi.ss.usermodel.Row;

import com.lambda.Coupon;

public class WriteExcelTest {
	
	public static void main(String[] args){
		
		String filePath = "E:\\tmp\\haha.xlsx";
		List<Coupon> coupons = new ArrayList<>();
		Coupon a = new Coupon();
		a.setId(1);
		a.setMoney(100);
		a.setDesc("发券拉");
		coupons.add(a);
		
		WriteExcel.write(coupons, filePath, "优惠券", (sheet, datas)->{
			int column = 0;
    		Row headRow = sheet.createRow(0);
    		headRow.createCell(column++).setCellValue("优惠券ID");
    		headRow.createCell(column++).setCellValue("金额");
    		headRow.createCell(column++).setCellValue("描述");
    		
    		for(Coupon data: datas){
    			column = 0;
    			Row row = sheet.createRow(1);
    			row.createCell(column++).setCellValue(data.getId());
    			row.createCell(column++).setCellValue(data.getMoney());
    			row.createCell(column++).setCellValue(data.getDesc());
    		}
		});
		
	}

}

其中类WriteExcel中的方法是完全复用的,不会因为数据或excel样式的不同需要重新定义。

类似的有发送Http请求时,解析http返回数据可以通过lambda表达式做。

简单应用

java方法传递,是以类为载体的。lambda函数作为参数传递,通常会拿来和匿名类做比较,它们有如下几点不同:

A lambda表达式没有成员变量,只能处理无状态的逻辑

B 匿名类块中的变量有单独的作用域,可以和外部类重名;lambda能直接引用外部类中的变量(注意this的指向)

1 延迟计算

函数式接口可以将方法作为参数传递,使得能在具体使用时,进行结果计算,而不是提前计算好,传递结果变量。

譬如,在记录日志时,在开启debug模式时打印日志。

if(log.isDebugEnabled()){
	if(resultInfo.getCode() == 0){
		log.debug("请求XX接口成功:"+ GsonUtils.toString(resultInfo.getData()));
	}
}

if(log.isDebugEnabled()){
	if(CollectionUtils.isNotEmpty(coupons)){
		StringBuilder sb = new StringBuilder();
		for(Coupon coupon: coupons){

		}
		log.debug("使用优惠券:"+ sb.toString());
	}
}

在抽象上述逻辑时(减少业务代码中大量的log.isDebugEnabled()判断),会有

public class LogHelper {

    public static void debug(Logger log, String message){
        if(!log.isDebugEnabled()){
            return;
        }
        log.debug(message);
    }

}

此时,在调用LogHelper.debug时,就需要提前将message计算好。那么进一步有如下

@FunctionalInterface
public interface MessageGenerate {

    String generate();

}
-----------------------------

import org.slf4j.Logger;

public class LogHelper {

    public static void debug(Logger log, MessageGenerate cxt){
        if(!log.isDebugEnabled()){
            return;
        }

        String message =  cxt.generate();
        log.debug(message);
    }

}
-----------------------------

List<Coupon> coupons = new ArrayList();
        
LogHelper.debug(log, ()->{
	if(CollectionUtils.isEmpty(coupons)){
		return "";
	}
	StringBuilder sb = new StringBuilder();
	for(Coupon coupon: coupons){

	}
	return sb.toString();
});

虽然在调用MessageGenerate.generate(无参数,引用外部变量特殊做法)时,组织了生成message的代码,但是它只在记录日志时才会执行。

2 使用外部变量简化代码

         在使用数据连接时,譬如redis连接时,jedis中有很多方法,但参数类型和个数,返回结果等都不同。很难通过lambda统一参数和返回结果。一般会有如下代码:

public String set(String key, String value) {
	ShardedJedis jRedis = null;
	try {
		jRedis = shardedJedisPool.getResource();
		return jRedis.set(key, value);
	} catch (Exception e) {
		log.error("Redis set err , key = {}", key, e);
	} finally {
		if (null != jRedis) {
			jRedis.close();
		}
	}
	return null;
}

public String get(String key) {
	ShardedJedis jedis = null;
	try {
		jedis = shardedJedisPool.getResource();
		return jedis.get(key);
	} catch (Exception e) {
		log.error("Redis get err , key = {}", key, e);
	} finally {
		if (null != jedis) {
			jedis.close();
		}
	}
	return null;
}

这里可以不考虑封装jedis的方法,把方法的执行交给jedis对象。

public <R> R handle(Function<ShardedJedis, R> function){
	ShardedJedis jedis = null;
	try {
		jedis = shardedJedisPool.getResource();
		return function.apply(jedis);
	} catch (Exception e) {
		log.error("Redis handle err", e);
	} finally {
		if (null != jedis) {
			jedis.close();
		}
	}
	return null;
}

String redisKey = "key", redisValue="value";
String s = redisUtils.handle((jedis)->jedis.set(redisKey, redisValue));

3 设计模式

在遵循“开放闭合”原则下,采用各种模式组织代码,譬如工厂模式,策略模式,模板模式等,都会将差异化部分封装在新类(其中很多都是接口或抽象类的子类)或新方法中。此时和容易想到将这些类或方法用lambda函数替代。

譬如在工厂模式中就不需要定义很多接口的子类了。当然比较复杂逻辑,为了保证代码的可读性和可维护性,还是需要针对具体情况做具体拆分。

//定义接口
public interface RefundStrategy{
    boolean refund(Order order);
}

//调用
RefundStrategy alipayRefund = order->{ //do refund };
alipayRefund.refund(order);

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值