深入浅出讲解Java8函数式编程


Posted in Java/Android onJanuary 18, 2022

什么是函数式编程

函数式编程就是一种抽象程度很高的编程范式,纯粹的函数式编程语言编写的函数没有变量,因此,任意一个函数,只要输入是确定的,输出就是确定的,这种纯函数我们称之为没有副作用。而允许使用变量的程序设计语言,由于函数内部的变量状态不确定,同样的输入,可能得到不同的输出,因此,这种函数是有副作用的。 函数式编程的一个特点就是,允许把函数本身作为参数传入另一个函数,还允许返回一个函数! 函数式编程最早是数学家阿隆佐·邱奇研究的一套函数变换逻辑,又称Lambda Calculus(λ-Calculus),所以也经常把函数式编程称为Lambda计算。

Java8内置了一些常用的方法接口FunctionalInterface

这种接口只定义了一个抽象方法,并且用@FunctionalInterface注解标记,如Predicate,Consumer,Function,Supplier,Comparator等等,这些都属于java.util.function包中

@FunctionalInterface
public interface Predicate<T> {
    boolean test(T t);
}

@FunctionalInterface
public interface Consumer<T> {
    void accept(T t);
}
// 省略不贴了

他们的特点是定义了函数的入参以及返回值,当使用时传入满足函数接口定义的表达式,即可通过编译器检查,下面会介绍函数接口和对应的4种使用方式

通过一个示例来看看使用函数式和不使用的区别,需求是要有一个函数,传入一个List<Integer>,筛选出单数的项,另一个则筛选出双数的项,先看看不使用函数式的写法

// 筛选出单数的方法
    public static List<Integer> filterSingular(List<Integer> list) {
        List<Integer> result = new ArrayList<>();
        for (Integer item : list) {
            if (item % 2 != 0) {
                result.add(item);
            }
        }
        return result;
    }


    // 筛选出双数的方法
    public static List<Integer> filterEven(List<Integer> list) {
        List<Integer> result = new ArrayList<>();
        for (Integer item : list) {
            if (item % 2 == 0) {
                result.add(item);
            }
        }
        return result;
    }

定义方法后调用,预期效果输出[1,3,5,7]和[2,4,5]

List<Integer> targetList = new ArrayList<Integer>() {
            {
                this.add(1);
                this.add(2);
                this.add(3);
                this.add(4);
                this.add(5);
                this.add(6);
                this.add(7);
            }
        };
        List<Integer> singularList = filterSingular(targetList);
        List<Integer> evenList = filterEven(targetList);
        System.out.println(singularList);
        System.out.println(evenList);

但其实这两个筛选函数,唯一区别只是判断条件的不同,这时候就可以将这个条件抽象成一个函数接口去编写,Predicate接口的test定义文章开头就有,传入一个泛型类型,返回一个boolean,改写下filter的代码

public static List<Integer> filter(List<Integer> list,Predicate<Integer> predicate) {
        List<Integer> result = new ArrayList<>();
        for (Integer item : list) {
            if (predicate.test(item)) {
                result.add(item);
            }
        }
        return result;
    }

将函数改造成了除了传入目前List外,还要传入一个实现了Predicate接口的实例对象,只需要传入满足函数定义入参和出参,就能通过编译,下面介绍4种这个函数的使用方式

  • 使用传统的匿名内部类,在java8之前只能这么操作
List<Integer> singularList = filter(targetList, new Predicate<Integer>() {
            @Override
            public boolean test(Integer integer) {
                return integer % 2 != 0;
            }
        });
        System.out.println(singularList);
  • 使用lambda表达式格式如下()->{},()的是方法列表,->{}是方法体,由于目前只有一个参数,并且参数类型是可以推断出来的,所以类型和()可以不写,方法体只有一句,{}也可以不写,不推荐在方法体中写过长的代码,应保证可读性
List<Integer> singularList2 = filter(targetList, integer -> integer % 2 != 0);
        // 下面是完整写法
        // List<Integer> singularList3 = filter(targetList, (Integer integer) -> {
        //    return integer % 2 != 0;
        // });

可以使用的原因,lambda表达式满足传入Integer返回一个boolean的抽象操作,可以自动转化为函数接口

  • 静态方法引用,这里定义了一个静态方法,也可以自动的转化为函数接口,使用时需要用双冒号语法
private static boolean integerWithSingular (Integer haha){
        return haha % 2 != 0;
    }

使用静态方法引用,Cn是所在类名,这种方式对比lambda表达式可以让可读性进一步提高,因为方法有名字,可以通过名字去判断在执行什么操作,并且更适合编写更多的逻辑

List<Integer> singularList3 = filter(targetList, Cn::integerWithSingular);
  • 实例方法,因为任何实例方法,第一个参数永远都是一个隐藏的指针this指向当前实例,由于上面例子泛型传入的是Integer类型,需要改写下预期才能演示,先声明一个类,并且有一个实例方法是完成传入Test类型返回boolean的映射
public class Test {
    private long id;
    
    public Test(long id) {
        this.id = id;
    }
    
    private boolean integerWithSingular(){
        return this.id % 2 != 0;
    }
}

将filter函数的Integer类型全换成Test类型

public static List<Test> filter(List<Test> list, Predicate<Test> predicate) {
        List<Test> result = new ArrayList<>();
        for (Test item : list) {
            if (predicate.test(item)) {
                result.add(item);
            }
        }
        return result;
    }

下面的调用中,传入类名::实例方法名实现的效果是等价的

ArrayList<Test> targetList = new ArrayList<Test>() {
        {
            this.add(new Test(1));
            this.add(new Test(2));
        }
    };
    filter(targetList,Test::integerWithSingular);

任何只包含一个抽象方法的接口都可以被自动转换成函数接口,自己定义的接口没有标注@FunctionalInterface标注也可以

用的比较多的函数接口

  • Consumer 输入一个对象,输出是空的,相当于消费掉传入的对象,ArrayList的forEach方法使用了Consumer
// ArrayList的forEach方法源码
    @Override
    public void forEach(Consumer<? super E> action) {
        Objects.requireNonNull(action);
        final int expectedModCount = modCount;
        @SuppressWarnings("unchecked")
        final E[] elementData = (E[]) this.elementData;
        final int size = this.size;
        for (int i=0; modCount == expectedModCount && i < size; i++) {
            action.accept(elementData[i]);
        }
        if (modCount != expectedModCount) {
            throw new ConcurrentModificationException();
        }
    }
  • Function更加接近于函数的定义,用于将一个类型变换成另一个类型,如数学中的函数把X变成Y,函数接口的定义如下,还是以刚才编写的Test类为理解,再编写一个map方法
public static String map(Test test, Function<Test, String> function) {
        return function.apply(test);
    }

只要满足传入一个Test类型,返回一个String类型的东西都可以被自动转换

map(new Test(1),test -> "name");
        
        // 如果Test类型还有一个属性为String的name和对应的getter方法,可以写成下面这种实例方法引用
        // map(new Test(2), Test::getName);
  • Supplier和Consumer是对立者,Consumer消费,Supplier提供,从虚空中提供一个东西
public static Object create(Supplier<Object> supplier){
        return supplier.get();
    }

只要满足凭空冒出一个东西的条件即可

create(Object::new);
    // new的作用也是从虚无创造出一个对象,所以可以这么写
    create(() -> "supplier");
    create(() -> new Test(1));

最后再介绍函数式编程在排序中的使用

// Collections.sort的静态方法定义
    public static <T> void sort(List<T> list, Comparator<? super T> c) {
        list.sort(c);
    }

    // Comparator.comparing的静态方法定义
    // 理解成需要传入一个T类型映射到U类型的形式即可
    // 对应着示例就是传入一个Test,返回一个实现了Comparable接口的对象(如Integer,String...)
    public static <T, U extends Comparable<? super U>> Comparator<T> comparing(
            Function<? super T, ? extends U> keyExtractor)
    {
        Objects.requireNonNull(keyExtractor);
        return (Comparator<T> & Serializable)
            (c1, c2) -> keyExtractor.apply(c1).compareTo(keyExtractor.apply(c2));
    }

下面是爽快时间

// 使用简短的代码就能实现按对象中某个字段去排序
    public static void main(String[] args) {
        ArrayList<Test> tests = new ArrayList<Test>() {
            {
                this.add(new Test(2, "abc"));
                this.add(new Test(1, "efg"));
            }
        };
        // 现在Test实例的id字段排序,再将数组反转,然后再按照name字段排序
        Collections.sort(tests, Comparator.comparing(Test::getId)
                .reversed()
                .thenComparing(Test::getName));
        System.out.println(tests);
    }

其他的函数接口就不再赘述,只要搞懂原理,就能轻松上手使用

总结

到此这篇关于Java8函数式编程的文章就介绍到这了,更多相关Java8函数式编程内容请搜索三水点靠木以前的文章或继续浏览下面的相关文章希望大家以后多多支持三水点靠木!

Java/Android 相关文章推荐
浅谈Python魔法方法
Jun 28 Java/Android
Java 泛型详解(超详细的java泛型方法解析)
Jul 02 Java/Android
JavaGUI模仿QQ聊天功能完整版
Jul 04 Java/Android
看完这篇文章获得一些java if优化技巧
Jul 15 Java/Android
logback如何自定义日志存储
Aug 30 Java/Android
JAVA API 实用类 String详解
Oct 05 Java/Android
Java 在线考试云平台的实现
Nov 23 Java/Android
正则表达式拆分url实例代码
Feb 24 Java/Android
alibaba seata服务端具体实现
Feb 24 Java/Android
Java Spring Lifecycle的使用
May 06 Java/Android
Java线程的6种状态与生命周期
May 11 Java/Android
Spring IOC容器Bean的作用域及生命周期实例
May 30 Java/Android
关于maven依赖 ${xxx.version}报错问题
Jan 18 #Java/Android
Eclipse+Java+Swing+Mysql实现电影购票系统(详细代码)
关于Spring配置文件加载方式变化引发的异常详解
Jan 18 #Java/Android
springboot中的pom文件 project报错问题
Jan 18 #Java/Android
java代码实现空间切割
springboot新建项目pom.xml文件第一行报错的解决
Jan 18 #Java/Android
关于@OnetoMany关系映射的排序问题,使用注解@OrderBy
Dec 06 #Java/Android
You might like
剧场版动画《PSYCHO-PASS 3 FIRST INSPECTOR》3月27日日本上映!
2020/03/06 日漫
支持中文的PHP按字符串长度分割成数组代码
2015/05/17 PHP
PHP实现简单计算器小程序
2020/08/28 PHP
不安全的常用的js写法
2009/09/15 Javascript
原生js实现改变随意改变div属性style的名称和值的结果
2013/09/26 Javascript
jquery select 设置默认选中的示例代码
2014/02/07 Javascript
jQuery实现鼠标经过图片变亮其他变暗效果
2015/05/08 Javascript
JS获取下拉框显示值和判断单选按钮的方法
2015/07/09 Javascript
JS实现漂亮的淡蓝色滑动门效果代码
2015/09/23 Javascript
第二篇Bootstrap起步
2016/06/21 Javascript
百度地图JavascriptApi Marker平滑移动及车头指向行径方向
2017/03/13 Javascript
基于bootstrap实现多个下拉框同时搜索功能
2017/07/19 Javascript
一个简易时钟效果js实现代码
2020/03/25 Javascript
JavaScript实现表单注册、表单验证、运算符功能
2018/10/15 Javascript
JavaScript实现放大镜效果代码示例
2020/04/29 Javascript
vue实现购物车加减
2020/05/30 Javascript
[01:23]一分钟告诉你 DOTA2为什么叫信仰2
2014/06/20 DOTA
[03:22]DSPL第一期精彩集锦:酷炫到底!
2014/11/07 DOTA
Python实现根据IP地址和子网掩码算出网段的方法
2015/07/30 Python
python读取xlsx的方法
2018/12/25 Python
python 函数中的内置函数及用法详解
2019/07/02 Python
python list数据等间隔抽取并新建list存储的例子
2019/11/27 Python
python实现感知机模型的示例
2020/09/30 Python
美国独家设计师眼镜在线光学商店:Glasses Gallery
2017/12/28 全球购物
WoolOvers澳洲官方网站:英国针织服装公司
2018/05/13 全球购物
英国现代家具和装饰网站:PN Home
2018/08/16 全球购物
KIKO MILANO俄罗斯官网:意大利领先的化妆品和护肤品品牌
2021/01/09 全球购物
经典c++面试题三
2015/07/08 面试题
2013年学期结束动员演讲稿
2014/01/07 职场文书
视光学毕业生自荐书范文
2014/02/13 职场文书
2014村务公开实施方案
2014/02/25 职场文书
应聘文员自荐信范文
2014/03/11 职场文书
2015大一新生军训感言
2015/08/01 职场文书
详解如何使用Node.js实现热重载页面
2021/05/06 Javascript
Python爬虫网络请求之代理服务器和动态Cookies
2022/04/12 Python
Python 文字识别
2022/05/11 Python