Java中代码Bug记录--泛型失效、数组删除、HashMap死循环
最近在工作的过程中,遇到了不少奇怪自己或者同事的Bug,都是一些出乎意料的,不太容易发现的,记录一下来帮助可能也遇到了这些Bug的人
1. 编译时泛型校验失效
Map<String, String> nameToType = new HashMap<>();
nameToType.put( "testName", 123 ); // java: 不兼容的类型: int无法转换为java.lang.String
上面的代码,我们很容易看出来,无法通过编译,因为Map的value需要的是一个String,但我们传的是一个int。但我只要稍微改一下:
package generic;
import java.util.HashMap;
import java.util.Map;
public class RecContext<T>{
private Map<String, String> nameToType = new HashMap<>();
public Map<String, String> getNameToType(){
return nameToType;
}
public void setNameToType( Map<String, String> nameToType ){
this.nameToType = nameToType;
}
public static void main( String[] args ){
RecContext recContextRaw = new RecContext();
recContextRaw.getNameToType().put( "testName", 123 );
}
}
同样是一个value要求为String的Map, 放到一个对象里面就可以通过编译了
不过这不是一个普通的对象,这个Class本身带泛型,但我们在使用的时候,没有指定这个泛型,也就是IDEA中常常报的错,说你在Raw Use这个类型
也正是因为Raw Use了这个类,所以导致它的泛型属性也被类型擦除了,具体可以看StackOverflow-What is a raw type and why shouldn't we use it?
这篇文章里面是这么说的
In simpler terms, when a raw type is used, the constructors, instance methods and non-static fields are also erased
简单地讲,当使用了原始类型,构造器、实例方法和非静态的字段都会被擦除
我们有一个老的项目里面有不少这样的Raw use,也正好有另外一个同事把一个其他的类型插入了这个Map,于是就报了一个类型转换错误,同事们都很震惊,认为这个Map不是有泛型吗,怎么可能插入别的类型,一番排查,才发现是这个问题
解决方法
- 不要Raw use类,会使编译校验失效,也有很多其他的理由,可以参考上面那篇文章,我不使用的原因是因为IDEA每次都会发出告警。还有如果发现这个类一直在Raw use也没啥问题,说明这个类本身就不需要泛型,可以考虑是一下是不是需要重构一下
- 我的告警配置的是黄色的背景,看起来很惹眼
- IDEA的告警会是Raw use,不可能的条件判断,可以更换写法的代码(完全不影响效果),甚至可能是bug(之前碰到过,修复的时候发现IDEA已经提示了,但是可能别人的告警不是很明显,没看到)
- 如果是真的Raw use了类,那检查类型是否错误的责任就落到我们自己的头上,确保不要传进错误的类型,确保取出来的类型不要转换错误
2. 数组删除中的“刻舟求剑”
线上代码中有这样一个逻辑,想从帖子列表中筛选出一部分帖子然后从原列表中删除,其代码逻辑如下:
import java.util.ArrayList;
import java.util.List;
public class ListDelTest{
public static void main( String[] args ){
List<Integer> jobIds = new ArrayList<>();
for( int i = 0; i < 10; i++ ){
jobIds.add( i );
}
List<Integer> delIndex = new ArrayList<>();
for( int i = 0; i < jobIds.size(); i++ ){
// 线上是其他的筛选逻辑,在这我们用偶数代替
if( jobIds.get( i ) % 2 == 0 ){
delIndex.add( i );
}
}
for( int i = 0; i < jobIds.size(); i++ ){
if( delIndex.contains( i ) ){
jobIds.remove( jobIds.get( i ) );
}
}
System.out.println( jobIds );
// [1, 2, 4, 5, 7, 8]
}
}
可以看到输出结果中,并不符合我们的预期,我们希望的是把所有的偶数删除,结果中不但有偶数,而且一些奇数也不见了
这个其实很容易理解,因为我们记得位置是0,2,4,6,8
原始数据是0,1,2,3,4,5,6,7,8,9
当我们删除了0时,数据变成了1,2,3,4,5,6,7,8,9
这时候我们再去删除index是2的值,结果就把3这个值给删除了
解决方法
- 使用stream filter collect,这种其实不是原地删除,而是新建了一个List, 不过我们把这个List覆盖原来的引用,效果一样
- 常见的边遍历边删除的方法,使用Iterator,可以避免
ConcurrentModificationException
异常
int i = 0;
Iterator<Integer> iterator = jobIds.iterator();
while( iterator.hasNext() ){
iterator.next();
if( delIndex.contains( i ) ){
iterator.remove();
}
i++;
}
System.out.println( jobIds );
// [1, 3, 5, 7, 9]
- 倒着删除,这样不会影响我们已经记录过的index,我记得当时我在华为OD面试的时候一个面试官问的我的一个问题,我没答出来,他告诉我的答案
for( int i = jobIds.size() - 1; i > -1; i-- ){
if( delIndex.contains( i ) ){
jobIds.remove( jobIds.get( i ) );
}
}
3. Java8 HashMap死循环
线上同事上线了一个新的过滤器,我们的过滤器是并发执行的,比如,帖子敏感词过滤,会将帖子分成10份,用10个线程分别执行,执行完了就把结果放到一个公共的map中
很明显,这个map是有线程安全问题的,因为会有多个线程同时去put,然而,因为没考虑到,同事使用了普通的HashMap
线上的现象就是,每过一段时间,某个机器的CPU使用率就到了90%以上,需要重启
按照我们的理解,就算是有并发问题,怎么会使CPU使用变高呢
我们都背过,在Java1.7中的HashMap会因为并发插入产生死循,1.8使用尾插法代替头插法解决了死循环
可我们用的是Java1.8,看起来好像还是有死循环的问题
具体原因我就不仔细分析了,是在链表转换树或者对树进行操作的时候会出现线程安全的问题
解决方法
- 多线程还是需要使用ConcurrentHashMap
参考
[1] StackOverflow-What is a raw type and why shouldn't we use it?
Java中代码Bug记录--泛型失效、数组删除、HashMap死循环的更多相关文章
- JAVA 中LinkedHashMap要点记录
JAVA 中LinkedHashMap要点记录 构造函数中可能出现的几个参数说明如下: 1.initialCapacity 初始容量大小,使用无参构造方法时,此值默认是16 2.loadFactor ...
- Java 中为什么不能创建泛型数组?
之前只是知道在 Java 中不能创建泛型数组,今天翻看 <Effective Java>其中对这个部分有讲解,记录一下. 现在我们假设在 Java 中可以创建泛型数组,看看可能会发生什么情 ...
- slf4j+log4j在Java中实现日志记录
小Alan今天来跟大家聊聊开发中既简单又常用但必不可少的一样东西,那是什么呢?那就是日志记录,日志输出,日志保存. 后面就统一用日志记录四个字来形容啦. 日志记录是项目的开发中必不可少的一个环节,特别 ...
- JAVA中一些需要记录的知识点(进阶部分)···持续更新
1.JAVA中的相对路径 file = new file("")与file = new file("./")方式相同,该路径为整个project的根目录(实际上 ...
- Java中代码点与代码单元(转)
摘要 本文介绍 Java 平台支持增补字符的方式.增补字符是 Unicode 标准中代码点超出 U+FFFF 的字符,因此它们无法在 Java 编程语言中描述为单个的 16 位实体(例如char数据类 ...
- java中Arrays.sort()对二位数组进行排序
int [][]a = new int [5][2]; //定义一个二维数组,其中所包含的一维数组具有两个元素 对于一个已定义的二位数组a经行如下规则排序,首先按照每一个对应的一维数组第一个元素进行升 ...
- JAVA 中日志的记录于使用
java中常用的日志框架 日志接口 Commons Logging Apache Commons Logging是一个基于Java的日志记录实用程序,是用于日志记录和其他工具包的编程模型.它通过其他一 ...
- Java中定义不了可变长数组怎么办---集合 泛型
一.集合(Collections) Java使用集合来组织和管理对象. 1.Java的集合类 集合类主要负责保存.盛装和管理对象,因此集合类也被称为容器类. 集合类分为Set.List.Map和Que ...
- CDH:5.14.0 中 Hive BUG记录
CDH5.14.0使用的HIVE版本: 自建表log: +----------------------------------------------------+--+ | createtab_st ...
- 【开发技术】java中代码检查checkStyle结果分析
编写Javadoc代码在Java代码的类.函数.数据成员前中输入/**回车,Eclipse能够自动生成相应的Javadoc代码.可以在后面添加相关的文字说明. Type is missing a ja ...
随机推荐
- LVS负载均衡(2)-- NAT模型搭建实例
目录 1. LVS NAT模型搭建 1.1 NAT模型网络规划 1.2 NAT模型访问流程 1.3 NAT模型配置步骤 1.3.1 ROUTER设备配置 1.3.2 后端nginx服务器配置 1.3. ...
- DB2 关联更新
update GIS_TER_ADDRESS_MSG set (POS_X,POS_Y)=(select LAT,LON from TEMP_ATM where GIS_TER_ADDRESS_MSG ...
- windows安装nginx可视化工具nginxWebUI
一.官网下载ngixn * 官网地址<http://nginx.org/> 选取稳定版(Stable Version)二.解压压缩包三.启动Nginx1.进入安装目录下,找到nginx.e ...
- Laravel 实现自定义资源路由
Laravel 如何实现自定义资源路由 最近在开发过程中,发现总有一些路由需要重复定义,比如切换状态,导出,回收站啊之类的.如果使用 Laravel 自带的资源路由方法,还不足以满足重复劳动得过程.所 ...
- 智能便捷_AIRIOT智慧充电桩管理解决方案
现如今随着对可持续交通的需求不断增加,电动车市场正在迅速扩大,建设更多更智能的充电桩,并通过管理平台提高充电设施的可用性和效率成为一项重要任务.传统的充电桩管理平台在对充电设施进行管理过程中,存在如下 ...
- Linux下tail -f,tail -F,tailf的区别
在Linux中,tail -f ,tail -F,tailf都是用来查看滚动日志的好方法,但是三者之间却有着细微的不同: tail -f 等同于--follow=descriptor,动态显示数据 ...
- 使用interface化解一场因操作系统不同导致的编译问题
场景描述 起因: 因项目需求,需要编写一个agent, 需支持Linux和Windows操作系统. Agent里面有一个功能需要获取到服务器上所有已经被占用的端口. 实现方式:针对不同的操作系统,实现 ...
- uni-app写微信小程序,data字段循环引用
在写程序过程中,需要使用到 globalData里的内容,而这个全局变量,在uni-app上需要通过: var app=getApp(); app.globalData.xxx=xxx来使用. 我觉得 ...
- Sqlserver存储过程中使用try-catch和事务
BEGIN TRY BEGIN TRANSACTION --逻辑代码 COMMIT TRANSACTION --提交事务 END TRY BEGIN CATCH SELECT @Msg = ERROR ...
- 必应每日一图url(可直接使用)
必应每日一图url 首先放出地址,后面是一堆心路历程(一堆废话),只为链接的可以不用看 https://baotangguo.cn:8081/ 最初 博客园装饰的时候(虽然是抄的),想着上面背景图 ...