JAVA和Nginx 教程大全

网站首页 > 精选教程 正文

代码简洁利器:java8学习之牛刀小试

wys521 2024-12-01 09:22:51 精选教程 21 ℃ 0 评论

读书要靠一点一滴的积累,切莫贪多求快,贪多往往嚼不烂。


引言

java版本更新的速度非常快,印象最深的版本还是1.8版本,引入了函数式的编程理念,本文结合实际使用案例,重学java8。重学的主要目的有两个:

1.简化业务代码,使代码更简洁优雅(行数越少,看着越清晰明了,也是开发者所追求的)

2.看懂源码(越来越多的框架源码到处都是函数式,方法引用,看源码绕不过去的大山)

先简单看下源码用到java8语法的地方


先看效果

正所谓,有目标才有动力嘛,先来对比一下java8与之前版本的效果图

需求:给集合按用户年龄大小排序,并打印

上代码:

public static void main(String[] args) {
        List<User> list = new ArrayList<>();
        list.add(new User("张三", 12, "红色"));
        list.add(new User("李四", 20, "蓝色"));
        list.add(new User("王五", 19, "红色"));
        list.add(new User("赵六", 17, "蓝色"));
        list.add(new User("钱八", 18, "绿色"));

        /** 需求: 根据年龄大小排序集合并打印 ***/
        // 1.老版实现方式,先排序
        list.sort(new Comparator<User>() {
            @Override
            public int compare(User o1, User o2) {
                return o1.getAge() - o2.getAge();
            }
        });
				// 再打印
        for (User user : list) {
            System.out.println(user);
        }

        // java8 实现方式
        list.sort(Comparator.comparing(User::getAge));
        list.forEach(System.out::println);
    }

问题来了,倒序呢,如何实现?

// 老版 对比的对象换个位置就行
list.sort(new Comparator<User>() {
            @Override
            public int compare(User o1, User o2) {
                return o2.getAge() - o1.getAge();
            }
        });

// java8 的方式也简洁,把正序翻转一下就是了
 list.sort(Comparator.comparing(User::getAge).reversed());

不论是从代码的简洁性上,还是逻辑性上,java8明显的更具优势。

概念学习

java8 引入了函数式编程概念,怎么理解呢,以往我们定义方法时,参数的定义,都是基础类型或者Object等类型,如:

public void print (int a, String b, Object c, User d) {}

现在呢,我想传递一组行为,或者动作,也可以理解为一组业务逻辑,如先打印,再计算

动作1 = { 1.打印 2.计算求和 } 动作2 = { 1.求平均值 2.调用其他函数 }

public void print(Function a, Predicate b) {....}

函数式的就是把 动作1动作2 也当做参数,也能传递,为此呢,java引入了函数式接口来定义上述的动作等。总体来说引入了三大类:

  • Predicate:接口定义了一个名叫test的抽象方法,它接受泛型 T对象,并返回一个boolean
  • Consumer:定义了一个名叫accept的抽象方法,它接受泛型T 的对象,没有返回(void)
  • Function:接口定义了一个叫作apply的方法,它接受一个 泛型T的对象,并返回一个泛型R的对象

java8 将动作分成了三类:

Predicate 分类就是这个动作会返回一个boolean类型的值(可以理解为条件判断动作)

当我们调用preicate类型的test方法时就会返回一个boolean类型值

Consumer 属于没有返回值的动作

当我们调用consumer类型的accept方法时,就会执行动作,并无返回值。

Function 属于有返回值的动作

当我们调用function类型的apply方法时,会返回一个类型的值。

实战演示

Predicate类型demo演示:

先定义一个接受predicate的方法,该方法接受动态表达式判断,若传入的参数校验成功,则打印 hello world,否则打印 code is beautiful

定义了三组函数式动作,并调用方法,看下效果图

上代码:

public static <T> void printWorldOption(Predicate<T> predicate, T param) {
        boolean test = predicate.test(param);
        // 如果条件为真,那么我们就打印一下 hello world 也可执行一组其他的动作
        if (test) {
            System.out.println("hello world");
        } else {
            System.out.println("code is beautiful");
        }
    }
public static void main(String[] args) {
        // 条件: 字符串比较abc
        Predicate<String> condition1 = x -> x.equals("abc");
        printWorldOption(condition1, "abcd");

        // 条件:字符传长度是否大于5
        Predicate<String> condition2 = x -> x.length() > 5;
        printWorldOption(condition2, "12312321d");

        // 条件:字符串中是否包含a
        Predicate<String> condition3 = x -> x.contains("a");
        printWorldOption(condition3, "abcd");
    }


Consumer类型demo演示:

先定义一下执行动作的方法,里边也可以加一些其他动作,演示demo简洁为主

虽然调用的都是一个方法,可以看到,传入的动作与参数都不一致,打印的也是依据动作来的。

上代码:

 public static <T> void printWorld(Consumer<T> consumer, T param1) {
        consumer.accept(param1);
    }

public static void main(String[] args) {
        // 打印 “你好”
        Consumer<String> consumer1 = System.out::println;
        printWorld(consumer1, "你好");

        // 获取字符串长度,并想加打印
        Consumer<String> consumer2 = str -> {
            int a = str.length();
            int b = a + a;
            System.out.println(b);};
        printWorld(consumer2, "helloworld");

        // 数子先加1,再乘以9, 最后打印
        Consumer<Integer> consumer3 = a -> {
            a += 1;
            a *= 9;
            System.out.println(a);};
        printWorld(consumer3, 2);
    }

Function类型的也是一个道理,只是有返回值。暂时先不做演示了,直接给大家看看真正实际应用的框架源码,和作者日常开发用到的情况。

mybatis-plus的源码应用


方法太长了截图看不全,上代码吧

public static <E> boolean saveOrUpdateBatch(Class<?> entityClass, Class<?> mapper, Log log, Collection<E> list, int batchSize, BiPredicate<SqlSession, E> predicate, BiConsumer<SqlSession, E> consumer) {
        String sqlStatement = getSqlStatement(mapper, SqlMethod.INSERT_ONE);
        return executeBatch(entityClass, log, list, batchSize, (sqlSession, entity) -> {
            if (predicate.test(sqlSession, entity)) {
                sqlSession.insert(sqlStatement, entity);
            } else {
                consumer.accept(sqlSession, entity);
            }
        });
    }

这是一个批量的保存或更新的一个方法, 该方法定义了Predicate的条件型函数,和一个Consumer消耗性函数,BiPredicate、BiConsumer, 小伙伴不要被吓到,这两个看着像,但是和我们讲的不太一样啊,前边多了Bi啊,其实这是同族的演化,就是可以多传入参数,比Predicate,Consumer多传入一个入参,用法一样的。

大体上可以看到,这个predicate是为了判断是否插入还是更新的一个条件。追溯一下调用的地方

可以看到,划线的地方,就是predicate的具体动作判断, 方框地方,就是consumer的动作。上代码:

return SqlHelper.saveOrUpdateBatch(this.entityClass, this.mapperClass, this.log, entityList, batchSize, (sqlSession, entity) -> {
            Object idVal = tableInfo.getPropertyValue(entity, keyProperty);
            return StringUtils.checkValNull(idVal)
                || CollectionUtils.isEmpty(sqlSession.selectList(getSqlStatement(SqlMethod.SELECT_BY_ID), entity));
        }, (sqlSession, entity) -> {
            MapperMethod.ParamMap<T> param = new MapperMethod.ParamMap<>();
            param.put(Constants.ENTITY, entity);
            sqlSession.update(getSqlStatement(SqlMethod.UPDATE_BY_ID), param);
        });

作者在项目中用到如下图所示,因为项目代码的保密性,做了抽离。

重点总结

理解函数式非常重要,这是我们用好java8的前提基础,简便的理解函数就是一组动作,动作也有分类,大体分了三类,理解了这三类函数式接口,剩下的都是小菜了。最后再补充一个知识点,就是我们也可以自定义函数式接口,加上一个注解就可以了@FunctionalInterface ,需特别注意,函数式只能有一个抽象方法奥。

本文暂时没有评论,来添加一个吧(●'◡'●)

欢迎 发表评论:

最近发表
标签列表