初识函数式编程

iBit程序猿 2020年05月29日 955次浏览

某一天

老板:小艾,最近业绩很差啊,看来员工的工作积极性不是很好啊,帮我过滤一下当月考勤。那些每月迟到10次以上的人,我拿去祭天;那些每月迟到5-10次的,我要严重警告一下,下月再犯,就祭天吧;剩下小于5次的,也要稍微警告警告。

于是,小艾就刷刷刷地写下了下面的代码。

考勤对象

@Data
@AllArgsConstructor
public class Attendance {

    /**
     * 名称
     */
    private String name;

    /**
     * 迟到天数
     */
    private int beLateTimes;

}

代码实现1

过滤方法

/**
 * 过滤迟到次数超过10天的考勤
 *
 * @param attendances 考勤
 * @return 符合条件的考勤
 */
public List<Attendance> filterOver10(List<Attendance> attendances) {
    List<Attendance> result = new ArrayList<>();
    for (Attendance attendance : attendances) {
        if (attendance.beLateTimes > 10) {
            result.add(attendance);
        }
    }
    return result;
}

/**
 * 过滤迟到次数5-10天的考勤
 *
 * @param attendances 考勤
 * @return 符合条件的考勤
 */
public List<Attendance> filter5To10(List<Attendance> attendances) {
    List<Attendance> result = new ArrayList<>();
    for (Attendance attendance : attendances) {
        if (attendance.beLateTimes >= 5 && attendance.beLateTimes <= 10) {
            result.add(attendance);
        }
    }
    return result;
}


/**
 * 过滤迟到次数小于5天的考勤
 *
 * @param attendances 考勤
 * @return 符合条件的考勤
 */
public List<Attendance> filterLess5(List<Attendance> attendances) {
    List<Attendance> result = new ArrayList<>();
    for (Attendance attendance : attendances) {
        if (attendance.beLateTimes > 0 && attendance.beLateTimes < 5) {
            result.add(attendance);
        }
    }
    return result;
}

方法调用

// 拿去祭天的
List<Attendance> attendanceOver10 = filterOver10(attendances);

// 严重警告
List<Attendance> attendance5To10 = filter5To10(attendances);

// 警告
List<Attendance> attendanceLess5 = filterLess5(attendances);

聪明的小艾,一下子就发现问题了,上面三个方法,除了判断的条件(如下所示),其余的代码都是一样的,代码重复率很高。

于是乎,他在想,是不是可以将过滤方法写成如下的形式呢

filterByPredicate(List<Attendance> attendances, 条件) ?

代码实现2

过滤方法

/**
 * 过滤考勤
 *
 * @param attendances 考勤
 * @param predicate   谓词
 * @return 符合条件的考勤
 */
public List<Attendance> filterByPredicate(List<Attendance> attendances, AttendancePredicate predicate) {
    List<Attendance> result = new ArrayList<>();
    for (Attendance attendance : attendances) {
        if (predicate.test(attendance)) {
            result.add(attendance);
        }
    }
    return result;
}

定义策略

/**
 * 考勤判断器
 */
interface AttendancePredicate {

    /**
     * 是否满足条件
     *
     * @param attendance 考勤
     * @return 是否满足条件
     */
    boolean test(Attendance attendance);
}

/**
 * 迟到超过10次判断
 */
public class AttendanceOver10Predicate implements AttendancePredicate {
    @Override
    public boolean test(Attendance attendance) {
        return attendance.beLateTimes > 10;
    }
}

/**
 * 迟到5-10次判断
 */
public class Attendance5To10Predicate implements AttendancePredicate {
    @Override
    public boolean test(Attendance attendance) {
        return attendance.beLateTimes >= 5 && attendance.beLateTimes <= 10;
    }

}

/**
 * 迟到小于5次判断
 */
public class AttendanceLess5Predicate implements AttendancePredicate {
    @Override
    public boolean test(Attendance attendance) {
        return attendance.beLateTimes > 0 && attendance.beLateTimes < 5;
    }
}

方法调用

// 拿去祭天的
List<Attendance> attendanceOver10 = filterByPredicate(attendances, new AttendanceOver10Predicate());

// 严重警告
List<Attendance> attendance5To10 = filterByPredicate(attendances, new Attendance5To10Predicate());

// 警告
List<Attendance> attendanceLess5 = filterByPredicate(attendances, new AttendanceLess5Predicate());

小艾还是觉得代码有点啰嗦,每定义一个过滤条件,就要定义一个一个类。然后他再次做了试验。

代码试验3

过滤方法

/**
 * 过滤考勤
 *
 * @param attendances 考勤
 * @param predicate   谓词
 * @return 符合条件的考勤
 */
public List<Attendance> filterByPredicate(List<Attendance> attendances, AttendancePredicate predicate) {
    List<Attendance> result = new ArrayList<>();
    for (Attendance attendance : attendances) {
        if (predicate.test(attendance)) {
            result.add(attendance);
        }
    }
    return result;
}

定义策略

/**
 * 考勤判断器
 */
interface AttendancePredicate {

    /**
     * 是否满足条件
     *
     * @param attendance 考勤
     * @return 是否满足条件
     */
    boolean test(Attendance attendance);
}

方法调用

// 拿去祭天的
List<Attendance> attendanceOver10 = filterByPredicate(attendances, new AttendancePredicate() {
    @Override
    public boolean test(Attendance attendance) {
        return attendance.beLateTimes > 10;
    }
});

// 严重警告
List<Attendance> attendance5To10 = filterByPredicate(attendances, new AttendancePredicate() {
    @Override
    public boolean test(Attendance attendance) {
        return attendance.beLateTimes >= 5 && attendance.beLateTimes <= 10;
    }
});

// 警告
List<Attendance> attendanceLess5 = filterByPredicate(attendances, new AttendancePredicate() {
    @Override
    public boolean test(Attendance attendance) {
        return attendance.beLateTimes > 0 && attendance.beLateTimes < 5;
    }
});

在java8以前,变量的值类型,主要分为基本类型和引用类型。代码试验2和3,对考勤的判断条件,必须依附对象存在,这容易导致很多无效的类产生,代码臃肿,对象对内存消耗等问题。这时,我们再想,上面的过滤条件如果能实现成如下的形式就好了:

public List<Attendance> filterByPredicate(List<Attendance> attendances, 某个方法) {
    List<Attendance> result = new ArrayList<>();
    for (Attendance attendance : attendances) {
        if (某个方法.call(attendance)) {
            result.add(attendance);
        }
    }
    return result;
}

java8的出现,支持了函数式编程,解决了上述问题。

相关概念

  • 函数:通常指方法,尤其静态方法。
  • 函数接口:有且仅有一个抽象方法的接口都可作为函数接口。该抽象方法也作为该抽象标识(入参、出参)。函数接口通常使用注解"@FunctionalInterface",但这不强制。

代码改造1

过滤方法

/**
 * 过滤考勤
 *
 * @param attendances 考勤
 * @param predicate   谓词,可接受函数
 * @return 符合条件的考勤
 */
public List<Attendance> filterByPredicate(List<Attendance> attendances, AttendancePredicate predicate) {
    List<Attendance> result = new ArrayList<>();
    for (Attendance attendance : attendances) {
        if (predicate.test(attendance)) {
            result.add(attendance);
        }
    }
    return result;
}

定义函数接口

/**
 * 考勤判断器(可以作为一个函数接口)
 */
interface AttendancePredicate {

    /**
     * 是否满足条件
     *
     * @param attendance 考勤
     * @return 是否满足条件
     */
    boolean test(Attendance attendance);
}

判断条件方法

public boolean over10(Attendance attendance) {
    return attendance.beLateTimes > 10;
}

public boolean between5and10(Attendance attendance) {
    return attendance.beLateTimes >= 5 && attendance.beLateTimes <= 10;
}

public boolean less5(Attendance attendance) {
    return attendance.beLateTimes > 0 && attendance.beLateTimes < 5;
}

方法调用

// 拿去祭天的
List<Attendance> attendanceOver10 = filterByPredicate(attendances, FilterAttendance::over10);

// 严重警告
List<Attendance> attendance5To10 = filterByPredicate(attendances, FilterAttendance::between5and10);

// 警告
List<Attendance> attendanceLess5 = filterByPredicate(attendances, FilterAttendance::less5);

注意到了么?如果定义的方法和函数接口的抽象方法的入参和出参都相同的情况下,该方法可以作为函数参数传递。

传参可以使用匿名类,那是否也可以使用匿名函数进行传递呢?答案是可以的,使用Lambda表达式。

代码改造2

删除 代码改造1 中的 判断条件方法,方法调用改写为:

// 拿去祭天的
List<Attendance> attendanceOver10 = filterByPredicate(attendances
        , attendance -> attendance.beLateTimes > 10);

// 严重警告
List<Attendance> attendance5To10 = filterByPredicate(attendances
        , attendance -> attendance.beLateTimes >= 5 && attendance.beLateTimes <= 10);

// 警告
List<Attendance> attendanceLess5 = filterByPredicate(attendances
        , attendance -> attendance.beLateTimes > 0 && attendance.beLateTimes < 5);

是不是觉得改造完的代码简洁了许多。初识函数式编程先到这里,在稍后的内容,会详细讲解lambda表达式。