函数式接口的妙用

1.什么是函数式接口

函数式接口(Functional Interface)就是一个有且仅有一个抽象方法,但是可以有多个非抽象方法的接口。

函数式接口可以被隐式转换为 lambda 表达式。

Lambda 表达式和方法引用(实际上也可认为是Lambda表达式)上。

一般函数式接口可以在类上加一个@FunctionalInterface注解以标识其为函数式接口

2.优缺点

优点:

  1. 代码简介,开发迅速
  2. 方便函数式编程
  3. 非常容易进行并行计算
  4. Java引入Lambda,改善了集合操作(引入Stream API),并可以利用到Lambda表达式的延后性

缺点:

  1. 代码可读性差
  2. 在非并行计算中,很多未必有传统的for性能高
  3. 不容易进行调试

3.用法

下面举一个比较典型的例子,码云上的GVP开源权限认证框架Sa-token中有一段代码:

/**
 * 封装 注销、踢人、顶人 三个动作的相同代码(无API含义方法)
 * @param loginId 账号id 
 * @param device 设备标识 
 * @param appendFun 追加操作 
 * @param isLogoutSession 是否注销 User-Session 
 */
protected void clearTokenCommonMethod(Object loginId, String device, Consumer<String> appendFun, boolean isLogoutSession) {
   // 1. 如果此账号尚未登录,则不执行任何操作 
   SaSession session = getSessionByLoginId(loginId, false);
   if(session == null) {
      return;
   }
   // 2. 循环token签名列表,开始删除相关信息 
   for (TokenSign tokenSign : session.getTokenSignList()) {
      if(device == null || tokenSign.getDevice().equals(device)) {
         // -------- 共有操作 
         // s1. 获取token 
         String tokenValue = tokenSign.getValue();
         // s2. 清理掉[token-last-activity] 
         clearLastActivity(tokenValue);     
         // s3. 从token签名列表移除 
         session.removeTokenSign(tokenValue); 
         // -------- 追加操作 
         appendFun.accept(tokenValue);
      }
   }
      // 3. 尝试注销session 
   if(isLogoutSession) {
      session.logoutByTokenSignCountToZero();
   }
}

clearTokenCommonMethod这个方法是为了在执行某些操作后,从系统中将token删除掉,并清空用户信息,注销session,因为在注销踢人顶人这几个地方都有类似的逻辑,但是相似的逻辑中有又一部分不太一样,这种情况下,直接抽成一个方法是不能直接通用的,那么这个时候一般有两个方案

  1. 策略模式,写doAfterClearToken方法。在ShiroSpring Security的代码中,经常可以看到有这种方式,即在本类中写一个抽象方法doAfterClearToken,在删除token后调用这个方法,这个方法在这个类中不做任何事情,交由子类来实现。当然,此时这个类必须是抽象类,然后再写注销类踢人类顶人类来继承这个类,并重写doAfterClearToken方法来实现各自的逻辑,大概就是下面这样
public abstract class StpLogic{
protected void clearTokenCommonMethod(Object loginId, String device, boolean isLogoutSession) {
   // 1. 如果此账号尚未登录,则不执行任何操作 
   SaSession session = getSessionByLoginId(loginId, false);
   if(session == null) {
      return;
   }
   // 2. 循环token签名列表,开始删除相关信息 
   for (TokenSign tokenSign : session.getTokenSignList()) {
      if(device == null || tokenSign.getDevice().equals(device)) {
         // -------- 共有操作 
         // s1. 获取token 
         String tokenValue = tokenSign.getValue();
         // s2. 清理掉[token-last-activity] 
         clearLastActivity(tokenValue);     
         // s3. 从token签名列表移除 
         session.removeTokenSign(tokenValue); 
         // -------- 追加操作 
         doAfterClearToken(tokenValue);
      }
   }
      // 3. 尝试注销session 
   if(isLogoutSession) {
      session.logoutByTokenSignCountToZero();
   }
}

public abstract void doAfterClearToken(String tokenValue);
}
public class kickout extends StpLogic{
    @Override
    public void doAfterClearToken(String tokenValue){
        //*******这里写具体的踢人的逻辑
    }
}
public class LogOut extends StpLogic{
    @Override
    public void doAfterClearToken(String tokenValue){
        //*******这里写具体的注销的逻辑
    }
}
public class replaced extends StpLogic{
    @Override
    public void doAfterClearToken(String tokenValue){
        //*******这里写具体的顶人的逻辑
    }
}
  1. 利用函数式接口。第一种方法是比较传统的方法,而函数式接口即作者使用的这种,传入一个Consumer接口,然后在清除token后执行accept方法,这样就完成了不同的后续操作,然后在调用这个方法时编写具体的执行逻辑代码,这样调用的好处是你不用单独为了一小段代码再撰写一个类(当然函数式接口使用过的是匿名内部类,本质上也是创建了类的,只不过不用撰写类放在项目目录下),这样可以提高开发效率,当然可读性比直接撰写了差一些,代码如下
	/**
	 * 顶人下线,根据账号id 和 设备标识 
	 * <p> 当对方再次访问系统时,会抛出NotLoginException异常,场景值=-4 </p>
	 * 
	 * @param loginId 账号id 
	 * @param device 设备标识 (填null代表顶替所有设备) 
	 */
	public void replaced(Object loginId, String device) {
		clearTokenCommonMethod(loginId, device, tokenValue -> {
			// 将此 token 标记为已被顶替 
			updateTokenToIdMapping(tokenValue, NotLoginException.BE_REPLACED);
	 		SaManager.getSaTokenListener().doReplaced(loginType, loginId, tokenValue);
		}, false);
	}
/**
 * 踢人下线,根据账号id 和 设备标识 
 * <p> 当对方再次访问系统时,会抛出NotLoginException异常,场景值=-5 </p>
 * 
 * @param loginId 账号id 
 * @param device 设备标识 (填null代表踢出所有设备) 
 */
public void kickout(Object loginId, String device) {
   clearTokenCommonMethod(loginId, device, tokenValue -> {
      // 将此 token 标记为已被踢下线  
      updateTokenToIdMapping(tokenValue, NotLoginException.KICK_OUT);
      SaManager.getSaTokenListener().doKickout(loginType, loginId, tokenValue);
   }, true);
}
/**
 * 会话注销,根据账号id 和 设备标识 
 * 
 * @param loginId 账号id 
 * @param device 设备标识 (填null代表所有注销设备) 
 */
public void logout(Object loginId, String device) {
   clearTokenCommonMethod(loginId, device, tokenValue -> {
      // 删除Token-Id映射 & 清除Token-Session 
      deleteTokenToIdMapping(tokenValue);
      deleteTokenSession(tokenValue);
      SaManager.getSaTokenListener().doLogout(loginType, loginId, tokenValue);
   }, true);
}

函数式接口Consumer介绍,这个函数式接口是为了消费数据而定义的,其代码如下

package java.util.function;

import java.util.Objects;

/**
 * Represents an operation that accepts a single input argument and returns no
 * result. Unlike most other functional interfaces, {@code Consumer} is expected
 * to operate via side-effects.
 *
 * <p>This is a <a href="package-summary.html">functional interface</a>
 * whose functional method is {@link #accept(Object)}.
 *
 * @param <T> the type of the input to the operation
 *
 * @since 1.8
 */
@FunctionalInterface
public interface Consumer<T> {

    /**
     * Performs this operation on the given argument.
     *
     * @param t the input argument
     */
    void accept(T t);

    /**
     * Returns a composed {@code Consumer} that performs, in sequence, this
     * operation followed by the {@code after} operation. If performing either
     * operation throws an exception, it is relayed to the caller of the
     * composed operation.  If performing this operation throws an exception,
     * the {@code after} operation will not be performed.
     *
     * @param after the operation to perform after this operation
     * @return a composed {@code Consumer} that performs in sequence this
     * operation followed by the {@code after} operation
     * @throws NullPointerException if {@code after} is null
     */
    default Consumer<T> andThen(Consumer<? super T> after) {
        Objects.requireNonNull(after);
        return (T t) -> { accept(t); after.accept(t); };
    }
}
  • 4
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值