本文转载自微信公众号《程序新视野》,作者为二哥。转载本文请联系程序新视界公众号。前几天写了几篇面试题的文章,其中就包括?。可能有朋友会说,这些面试题都是“面试造火箭,工作变螺丝”。不可否认,一些面试问题确实如此。不过就在今天,因为了解了这篇文章的知识,居然在老板面前炫耀,帮老板解决了疑惑,还拿到了明天请客的“口头核对”,哈哈~~下面说说大佬遇到的奇葩问题及排查过程。老大的疑惑老大在项目中写了一段类似这样的代码:Listlist=newArrayList<>();//省略添加数据操作Listmodels=list.stream().map(ProjectId::getDeviceModel).distinct().collect(Collectors.toList());System.out.println(模型);因此,这段代码中的distinct()方法没有起作用,没有达到预期的去重效果。但是老大不死心,先查了下方法的文档:返回一个由这个流的不同元素组成的流(根据Object.equals(Object))。对于有序流,不同元素的选择是稳定的(对于重复的元素,保留在遇到顺序中首先出现的元素。)对于无序流,不提供稳定性保证。这是一个有状态的中间操作。根据API文档,没有问题,然后老大打开debug模式,发现奇怪的是实体类的equals方法没有进入。老板的解题思路值得学习。老板还没决定放弃,就给我发信息问有没有兴趣看一看。有这么奇怪的现象,怎能不研究呢?解决思路是根据老大发来的部分代码和实现思路,完成整个模拟测试程序,创建两个实体类ProjectId和DeviceModel,重写equals方法(和老大沟通,他重写了equals方法,单独使用时有效)。DeviceModel实体类只是简单的重写了equals方法,只比较字段no是否相等。@DatapublicclassDeviceModel{privateStringno;@OverridepublicStringtoString(){returnno;}@Overridepublicbooleanequals(Objectother){if(this==other){returntrue;}if(other==null||getClass()!=other.getClass()){returnfalse;}returnthis.toString().equals(other.toString());}}ProjectId实体类,重写了equals方法,@DatapublicclassProjectId{privateintid;privateDeviceModeldeviceModel;}然后,构建了测试类:publicclassTest{publicstaticvoidmain(String[]args){Listlist=newArrayList<>();DeviceModeldevice1=newDeviceModel();device1.setNo("1");ProjectIdprojectId1=newProjectId();projectId1.setDeviceModel(device1);projectId1.setId(1);list.add(projectId1);DeviceModeldevice2=newDeviceModel();device2.setNo("1");ProjectIdprojectId2=newProjectId();projectId2.setDeviceModel(device2);projectId2.setId(1);list.add(projectId2);DeviceModeldevice3=newDeviceModel();device3.setNo("2");ProjectIdprojectId3=newProjectId();projectId3.setDeviceModel(device3);projectId3.setId(2);list.add(projectId3);Listmodels=list.stream().map(ProjectId::getDeviceModel).distinct().collect(Collectors.toList());System.out.println(models);}}先建一组数据,然后让device1和device2有相同的no属性,重写equals方法。理论上,它们应该是相等的。device3对象作为执行上述程序的比较对象。控制台打印如下:[1,1,2]确实还原了boss的bug,不知为什么会这样。不过既然已经重现了bug,解决方法就比较简单了。这时候老大又发来一条线索,说用for循环的形式就可以了:Listresults=newArrayList<>();for(DeviceModeldeviceModel:list.stream().map(ProjectId::getDeviceModel).collect(Collectors.toList())){if(!results.contains(deviceModel)){results.add(deviceModel);}}System.out.println(results);这个实现形式可以用来做对比。排错排错的时候首先想到的是debug,但是equals方法也失败了。仔细看代码,发现在Stream处理的过程中使用了map操作。上一篇文章讲过,判断一个对象是否已经存在于Map中,就是先通过key的hash值定位到对应的数组下标。如果该位置的Entry没有值,则直接保存;如果已经存在值,则使用equals方法比较值是否相同。那么,是不是重写了equals方法,而没有重写hashcode方法呢?于是,在DeviceModel类中新增了一个hashcode方法:@OverridepublicinthashCode(){//JDK7newObjectstoolclassreturnObjects.hash(no);}再次执行,测试该方法,发现可以成功去重。很明显,大哥的错误是在重写equals方法的时候违反了一个原则:如果一个类的equals方法相等,那么他们的hashcode方法也一定相等。因为没有重写hashcode方法,所以违反了这个原则。所以隐式使用Map会出现莫名其妙的问题。后续几经周折,终于解决了问题。想必大家都明白为什么重写equals方法就一定要重写hashcode方法。后来老大又问我一个问题:为什么list.contains方法没有这个问题?因为List的底层结构是一个数组,不像Map,为了提高效率,先对key进行hash。简单看下ArrayList中contains方法的核心实现:publicintindexOf(Objecto){if(o==null){for(inti=0;i