程序员是一个苦逼的行业,其中重要的一点就是行业专业知识更新的速度非常快,超过其他绝大部分行业,尤其是前台开发,这些天各种框架思想如雨后春笋层出不穷,有时候买了一本书,书还没有学完,技术可能已经更新或者被淘汰。那么,作为程序员的我们,怎么样才能不被爆炸的知识击昏,切实掌握真正属于自己的技术呢?
从我个人的心得来说,学习技术应该重点学习那种新思想新角度的技术,并使用新的思维来写新学习的技术。大部分技术人员的问题不在于前者,而在于学习了新技术,却还是使用老的思维来写新技术,就好比用马拉火车一样,写者落泪看者伤心!
举例,在前台开发中,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的使用之外,最重要的是改变思维,使用新技术的思维来写新的代码,一开始可能有点痛苦,但多写几次之后就会适应,就会感觉到新的技术给我们带来的好处,这是成长的必经之路!
热门评论
老师,这个代码部分显示能不能更改下。。看的也太乱了
老师,这个代码部分显示能不能更改下。。看的也太乱了
Map<Integer, Map<Integer, Integer>> collect = totalStocks.stream().collect(Collectors.groupingBy(DataBean::getDeptId, Collectors.groupingBy(DataBean::getType, Collectors.summingInt(DataBean::getNum))));