99%的人都踩过坑,全面整理60例Java避坑指南!
线程安全
Java 语言基础 易错知识点
Integer
包装器类型若为 null 值转换为 int 会产生空指针异常。应先判断包装器类型是否为非 null,再转换为基本类型。- 在使用
Double 和 Float
时会丢失精度,例如 1.0 - 0.9 期望是0.1,结果是 0.09999999999999998。因此,在涉及金额计算时,最好将金额转换为整型,例如将 1.1 元表示为 110 分。
double result = 1.0 - 0.9;
System.out.println(result);//0.09999999999999998
@Test
public void test2() {
System.out.println(String.valueOf(getSimpleString()));
}
public static <T> T getSimpleString() {
return (T) "121";
}
break
默认情况下可以跳出当前层的循环。然而,若需要跳出最外层的循环,我们需要为for循环取一个名字,并且在break
语句中指定要跳出哪一层的循环。举个例子,下面的例子中使用了break loop1
,用来指定跳出最外层的循环。
int m = 10;
int n = 4;
loop1:
for (int i = 0; i < m; i++) {
loop2:
for (int j = 0; j < n; j++) {
break loop1;
}
System.out.println("i:" + i);
}
- 内部类中无法直接修改外部引用的值,此时我们可以采用数组作为容器来传递数值。若在内部类中需要修改suc的值,我们可以将其声明为一个数组类型,并通过修改该数组元素的值来实现目的。
int[] suc = new int[1];
new Thread(new Runnable() {
@Override
public void run() {
int i = 0;
i++;
suc[0] = i;
}
}).start();
-
为了判断字符串是否相等,我们应该使用
String.equals或者Objects.equals
方法,而不能简单地使用 == 运算符。 -
在处理包装器类型
Integer、Long
时,不建议使用"=="来判断它们是否相等,最好采用Objects.equals方法来判断相等。因为"=="判断相等的方式是根据对象的内存地址来判断的。(尽管在-127到128的范围内,由于对象池缓存的存在,可以使用"=="进行判等,但为了统一起见,最好还是使用Objects.equals来判断相等) -
为了避免出现乱码,调用
String.getBytes(Charset.forName("utf-8"))
时应明确指定字符集,因为不同的操作系统环境有不同的默认字符集。 -
使用
StringUtils.isEmpty
判断字符串是否为null,使用StringUtils.isBlank
判断字符串是否为空白或者为null。 -
String是不可变对象,调用
substring、replace
等方法不会修改原始的String对象。如果需要修改字符串,应该使用线程安全的StringBuilder。在单线程环境下,也可以使用StringBuffer。 -
枚举类型的相等判断可以使用"=="。例如,
statusEnum == StatusEnum.Success
可以判断枚举类型statusEnum
是否等于StatusEnum.Success
。 -
Java中的基本类型和引用类型都是通过值传递的。因此以下代码尝试交换a、b两个基本类型的值的方法是错误的。
public void testSwap() {
int a = 1;
int b = 2;
swap(a, b); // a、b值不会变化。
}
public void swap(int a, int b) {
int c = a;
a = b;
b = c;
}
-
不建议在
boolean
类型的命名中使用 is 前缀,因为 getter 方法默认会自动带上 is 前缀。如果使用 is 前缀,可能会导致 JSON 等解析异常。 -
在使用
switch
语句时,只能使用常量,不能使用变量。 -
使用 switch case 语句时,记得和 break 关键字搭配使用。执行完一个 case 后的语句后,流程控制会转移到下一个 case 继续执行。如果你只想执行这一个 case 语句,不想执行其他 case,那么就需要在这个 case 语句后面加上 break,以跳出 switch 语句。
-
如果要使用
Object.clone
方法,需要确保类型实现了Cloneable
接口,否则调用clone
方法会抛出CloneNotSupportedException
异常。 -
Object.clone
方法属于浅层拷贝,即基础数据类型会被复制,而引用类型则是共享的。如果需要实现深层拷贝,可以考虑使用 BeanUtils.copyProperties 方法。 -
在使用 List 进行 for 循环遍历时,不能在遍历期间直接删除元素。如果需要删除元素,可以使用 stream 表达式进行过滤,或者使用
iterator
删除元素。
Iterator<Integer> it = numbers.iterator();
while(it.hasNext()) {
Integer i = it.next();
if(i < 10) {
it.remove(); // 删除小于 10 的元素
}
}
-
在服务正式运行时,为了记录日志,我们不能使用
e.printStackTrace()
和System.out.println
打印日志,而是应该使用log4j Logger
。因为标准输入输出会被重定向,使用日志框架将请求记录到日志文件中更为合适。 -
应该谨慎处理异常捕获,避免直接忽略异常而频繁发生错误。
-
对于
RuntimeException
等异常,我们也应该记录异常的根因。我们应该使用new RuntimeException("构建xxx出现异常", e)
的方式记录根异常,而不是仅仅使用new RuntimeException("构建xxx出现异常")
。这样的做法可以保留异常堆栈信息,有助于问题的排查。
try{
//业务逻辑
}catch(Exception e){
throw new RuntimeException("构建xxx出现异常", e);
}
-
Error
和Exception
都属于throwable
类型。如果在捕获异常时使用catch Exception
,那么遇到Error是无法被捕获的。常见的Error有OutOfMemoryError
、NoSuchMethodError
等。如果想要捕获Error类型的异常,需要使用catch Throwable
,这样就可以捕获到Exception
和Error
。 -
当我们使用finally代码块中的return语句时,try代码块中的return语句也会同时执行。最终,返回的值将会是finally代码块中return的值。
@Test
public void testTry() {
System.out.println("getValue:" + getValue());
}
public int getValue() {
int i = 0;
try {
System.out.println("try");
i = 1;
return i;
} finally {
i = 2;
return i;
}
}
执行结果getValue
返回 2
24. 当try代码块中使用return语句返回i时,无论finally块中如何修改i,都不会影响返回值。
@Test
public void testTry() {
System.out.println("getValue:" + getValue());
}
public int getValue() {
int i = 0;
try {
System.out.println("try");
i = 1;
return i;
} finally {
i = 2;
}
}
getValue()函数的执行结果为1。尽管 finllay
修改了 i
的值,但并不会对 try
模块的返回值产生影响。这是因为返回值在 return
语句执行后会被暂存在栈中,并且后续对其再次的修改也不会对该返回值产生影响,除非在 finllay
中再次使用 return
语句来覆盖返回值。
Jdk和其他类库易错知识点
-
在使用
HashMap
时,我们可能会遇到哈希冲突的问题。因为两个元素可以拥有相同的hashCode
,但是它们的equals方法却不能相等。为了解决这个问题,我们需要重写键值的hashCode
和equals
方法,确保当两个元素相等时,它们的hashCode
和equals
都应该相等。 -
HashMap是非线程安全的。如果我们需要在多线程环境下使用
HashMap
,可以考虑使用ConcurrentHashMap
这个线程安全类。 -
HashMap
的values和keySet方法返回的是无序集合。如果我们希望按照插入顺序排序,可以使用LinkedHashMap
。 -
对于Set集合,其中的对象也是无序的,遍历Set集合的结果与插入Set集合的顺序并不相同。如果我们希望有序,可以使用
LinkedHashSet
。 -
HashMap可以插入key、value都是null的元素,并且
containsKey(null)
会返回true。这是需要我们在使用HashMap时要注意的一点。
Map<String, String> map = new HashMap();
map.put(null, null);
System.out.println(map.containsKey(null));
containsKey 返回 true
-
ConcurrentHashMap
的 key 和 value 都不能为 null,否则会出现 NPE 错误。 -
当我们使用
Arrays.asList
方法创建一个List对象时,这个List对象是不允许添加add、清理clear元素的。 -
使用ArrayList的
subList
方法创建子List,这个子List会与父List共享相同的底层存储空间。因此,在子List中添加元素会对父级List产生影响,即父级List也会被修改。 -
在使用Java中的
List.toArray
方法将List转为数组时,我们需要指定转换后的数组类型。这个方法可以将一个List对象转换为其对应的数组形式。
List<String> list = Lists.newArrayList("1", "2", "3");
String[] array = (String[]) list.toArray();
System.out.println(array);
此时强转为 String[] 会出现异常。
需要指定类型,将其转换为具体的数组,因为List在运行时会进行类型擦除,所以需要重新指定入参的类型。可以使用
list.toArray(new String[0])
进行转换。在这种情况下,不需要给String数组指定长度,指定0即可,toArray
方法会自动处理。
List<String> list = Lists.newArrayList("1", "2", "3");
String[] array = list.toArray(new String[0]);
System.out.println(array);
-
ArrayList
是一个非线程安全的集合类,可以使用Collections.synchronizedList
方法将其转换为线程安全的List。但是,由于get方法也使用synchronize
关键字修饰,这会严重影响并发读取。可以考虑使用CopyOnWriteArrayList
来代替。 -
使用
ArrayList.subList
方法并不会修改原始对象,需要通过返回值获取subList。 -
在使用
Optional.of
方法时,如果参数为null,会抛出异常。如果允许参数为null,请使用Optional.ofNullable
方法。 -
SimpleDateFormat
是一个非线程安全的类,可以使用线程局部变量避免多线程访问的问题。或者可以使用JDK 8中引入的线程安全的Formatter类DateTimeFormatter
来代替,例如DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("mm:ss")
。 -
慎用
Files.readAllBytes
方法一次性读取磁盘文件,这可能导致内存溢出的问题。建议使用 File 类和BufferedReader
类的缓冲区循环读取文件,以避免一次性加载过多数据。 -
在比较大小时,可以使用
Comparable
接口。该接口定义了compareTo
方法,当当前对象大于目标对象时,返回正数,即按升序排序;当当前对象小于目标对象时,返回负数,即按降序排序。 -
在使用
Class.isAssignableFrom
方法时,如果子类在后面,父类在前面,则返回 true;否则返回 false。
if (Object.class.isAssignableFrom(String.class)) {
System.out.println("true");
}
-
Redis 客户端
Jedis
不具备线程安全性,因此需要通过使用JedisPool
来进行管理。 -
对于使用
Spring Async、Transactional
等注解的方法,需要注意它们应该被调用于不同的类中。如果在本类中调用这些注解,其效果将无法生效。 -
涉及到IO请求的连接一定能够要及时关闭。
-
Java 泛型的实际类型在编译时会被擦除,因此无法在运行时获取其类型参数的具体类型。在运行时,泛型类型会统一被擦除为Object类型。(如果类型参数被定义为 T extends Fruit,则默认为Fruit类型)。泛型的好处是在编译的时候检查类型安全
List<String> list = new ArrayList<String>();
System.out.println(list instanceof List<String>)
以上代码无法编译通过,JVM中并不存在List<String>.class
或是List<Integer>.class
,而只有List.class。
转载自:https://juejin.cn/post/7289736431173845033