⭐⭐⭐ Spring Boot 项目实战 ⭐⭐⭐ Spring Cloud 项目实战
《Dubbo 实现原理与源码解析 —— 精品合集》 《Netty 实现原理与源码解析 —— 精品合集》
《Spring 实现原理与源码解析 —— 精品合集》 《MyBatis 实现原理与源码解析 —— 精品合集》
《Spring MVC 实现原理与源码解析 —— 精品合集》 《数据库实体设计合集》
《Spring Boot 实现原理与源码解析 —— 精品合集》 《Java 面试题 + Java 学习指南》

摘要: 原创出处 https://zhuanlan.zhihu.com/p/38755472 「晓风轻」欢迎转载,保留摘要,谢谢!


🙂🙂🙂关注**微信公众号:【芋道源码】**有福利:

  1. RocketMQ / MyCAT / Sharding-JDBC 所有源码分析文章列表
  2. RocketMQ / MyCAT / Sharding-JDBC 中文注释源码 GitHub 地址
  3. 您对于源码的疑问每条留言将得到认真回复。甚至不知道如何读源码也可以请教噢
  4. 新的源码解析文章实时收到通知。每周更新一篇左右
  5. 认真的源码交流微信群。

程序员是一个苦逼的行业,其中重要的一点就是行业专业知识更新的速度非常快,超过其他绝大部分行业,尤其是前台开发,这些天各种框架思想如雨后春笋层出不穷,有时候买了一本书,书还没有学完,技术可能已经更新或者被淘汰。那么,作为程序员的我们,怎么样才能不被爆炸的知识击昏,切实掌握真正属于自己的技术呢?

从我个人的心得来说,学习技术应该重点学习那种新思想新角度的技术,并使用新的思维来写新学习的技术。大部分技术人员的问题不在于前者,而在于学习了新技术,却还是使用老的思维来写新技术,就好比用马拉火车一样,写者落泪看者伤心!

举例,在前台开发中,MVVM框架的出现,相对于jquery天下的前台是一个非常大的改变,我们学习mvvm框架的时候,首先第一步要从思维上认识到这2种技术的不同,要理解数据驱动概念,理解 “jquery是死的,mvvm是活的” 这点,而不是只是把mvvm框架当作一个模板框架来使用。而在实际工作中,出现过有些水平较差的开发人员,还在使用jquery的思维来写vue的框架,结果是vue的好处没有享受到,整个开发部署过程还变得复杂。

后台开发里面,jdk8由于接口默认实现特性,绝对算得上是一个里程碑,其中最重要的函数式编程的思想。lambda表达式和stream流的学习不算复杂,但你学会了api的使用,你就真的掌握了函数式编程吗?我看未必!

我们来看一道题目,计算 集合中 DeptId 和 Type 相同的数据的num总和。需求很常见,看着很简单,各位写写看?

import lombok.Data;

import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;


/**
* 计算 集合中 DeptId 和 Type 相同的数据的num总和
*/
public class Test {

public static void main(String[] args) {
List<DataBean> totalStocks = new ArrayList<>();

DataBean stock1 = new DataBean();
stock1.setDeptId(2);
stock1.setType(2);
stock1.setNum(2);
totalStocks.add(stock1);

DataBean stock2 = new DataBean();
stock2.setDeptId(2);
stock2.setType(2);
stock2.setNum(3);
totalStocks.add(stock2);

DataBean stock3 = new DataBean();
stock3.setDeptId(3);
stock3.setType(3);
stock3.setNum(5);
totalStocks.add(stock3);

DataBean stock4 = new DataBean();
stock4.setDeptId(3);
stock4.setType(3);
stock4.setNum(4);
totalStocks.add(stock4);

DataBean stock5 = new DataBean();
stock5.setDeptId(4);
stock5.setType(4);
stock5.setNum(10);
totalStocks.add(stock5);

}

}

@Data
class DataBean {

private int type;

private int deptId;

private int num;
}

这条题目是我群里的人问的,他自己用stream写了一个,觉得写的不好,问我应该怎么样写,我当时把这道题目发在群里面,结果发现大部分写得都不太好(建议大家先不要往下看,自己打开ide写写试试)。其中一个常见错误是stream操作中修改了数据,如

//group
Map<String, List<DataBean>> gourps = totalStocks.stream()
.collect(Collectors.groupingBy(e -> e.getDeptId() + ":" + e.getType()));

// reduce(错误的写法)
List<DataBean> result = gourps.values().stream()
.reduce(list -> list.stream().reduce((e1, e2) -> {
e2.setNum(e1.getNum() + e2.getNum());
return e2;
}).get())
// toList
.collect(Collectors.toList());

System.out.println(result);

还有一种是,自己new了一个ArrayList,然后再流操作之中往list里面加数据的。至于group2次的就不评论了。

我们学习函数式编程里面,函数式强调函数必须是纯函数,不能修改数据,而且是幂等,在stream里面,任何修改数据的行为都是不应该的,修改数据的时候应该返回新对象。所以在里面set数据的,或者往list里面增加数据的,都是不好的写法!我们学习了函数式编程,就应该遵守他的规则和思想。上面的题目,我的写法如下:

List<DataBean> result = totalStocks.stream()
//group
.collect(Collectors.groupingBy(e -> e.getDeptId() + ":" + e.getType()))
// 分组后的list做reduce
.values().stream().map(list -> list.stream().reduce(Test::combine).get())
// 收集到list
.collect(Collectors.toList());

System.out.println(result);


private static DataBean combine(DataBean e1, DataBean e2){
DataBean e = new DataBean();

e.setDeptId(e1.getDeptId());
e.setType(e1.getType());
e.setNum(e1.getNum() + e2.getNum());

return e;
}

我们在看另外一种场景,由于不按照函数式思维编写代码,在不同的jdk版本中,结果不一样,可能导致bug

import java.lang.management.ManagementFactory;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

class Demo{
public String msg = "初始值";
}

public class LambdaDemo2 {
public static void main(String[] args) {
System.out.println("jdk版本:"+ManagementFactory.getRuntimeMXBean().getVmVersion());
List<Demo> demos = new ArrayList<Demo>();

demos.add(new Demo());
demos.add(new Demo());

// 这里peek中修改了对象,产生了副作用(side-effects)
long count = demos.stream().peek(demo -> demo.msg = "peek中修改了").count();
System.out.println(count);

// jdk8下下面的demo已经改变
// jdk10下没有
demos.stream().forEach(demo -> System.out.println(demo.msg));

}
}

peek函数,按照函数式编程的规范,是调试用的,是个中间操作,如果你在里面修改了对象,看上去没有问题,但是在JDK8和JDK10中,2种运行结果不一致,JDK10种peek方法直接跳过了。

所以,**我们学习一个新技术,可以从下面几个层次了解推进,产生的背景?和之前的技术有什么区别?他的思想(风格)是什么?主要的API和套路。**如函数式编程,要了解和命令式编程的区别,一开始可能不太理解,多看几个贴,先有个模糊的概念。然后再看看函数式是怎么样的风格?而不是当作一个新的类库/工作类来使用(很多人是当作工具类使用的想法)。最后系统学习api。当然最重要的还是多写,写完之后看看是不是符合函数式编程的风格,如果看着别扭那就是那个环节出现了问题。

总结,我们学习新技术的同时,除了API的使用之外,最重要的是改变思维,使用新技术的思维来写新的代码,一开始可能有点痛苦,但多写几次之后就会适应,就会感觉到新的技术给我们带来的好处,这是成长的必经之路!

文章目录