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 ...
随机推荐
- Pytorch入门—Tensors张量的学习
Tensors张量的学习 张量是一种特殊的数据结构,与数组和矩阵非常相似.在PyTorch中,我们使用张量来编码模型的输入和输出,以及模型的参数. 张量类似于NumPy的ndarrays,只是张量可以 ...
- golang基础之结构体
匿名结构体 在定义一些临时数据结构等场景下还可以使用匿名结构体. 在函数体内 package main import ( "fmt" ) func main() { //方法一 v ...
- Javascript返回顶部和砸金蛋,跑马灯等游戏代码实现
1. 我们经常写页面的时候会遇到页面很长需要做返回顶部的操作:$("id /class").animate({scrollTop:$('.class').offset().top} ...
- HJ212-2017协议数据采集和接收
1.客户端TCP和UDP的数据发送工具 工具和软件下载页面如下:http://www.zlmcu.com/document/tcp_debug_tools.html 2.服务端数据接收监控软件 IPA ...
- uniapp有意思的api之openSetting
点击查看代码 <details> <summary>解决用户拒绝授权问题</summary> uni.openSetting({ success(res) { co ...
- nginx001
本文档版权归属:陈雷雷,仅限学习交流 QQ:370460470 blog:www.chenleilei.net Nginx服务实践 简述Nginx(nginx.org) Nginx (engine x ...
- MQ的相关概念
MQ的相关概念 什么是MQ MQ(message queue),从字面意思上看,本质是个队列,FIFO 先入先出,只不过队列中存放的内容是 message 而已,还是一种跨进程的通信机制,用于上下 ...
- NOIP模拟95(多校28)
T1 嗑瓜子 解题思路 \(f_{i,j}\) 表示操作 \(i\) 次,拿走了 \(j\) 个瓜子的概率,转移就比较直接了: \[f_{i+1,j+1}\leftarrow f_{i,j}\time ...
- MySQL学习笔记-存储引擎
存储引擎 一. MySQL体系结构 MySQL Server 连接层:连接的处理.认证授权.安全方案.检查是否超过最大连接数等. 服务层:SQL接口.解析器.查询优化器.缓存 引擎层:引擎是数据存储和 ...
- cookie cookie的获取
什么是 cookie cookie 是 浏览器 的 一种功能 是 浏览器 用来 存储 前端数据的一种 存储机制 ...