Java编程中忽略这些细节,Bug肯定找上你
摘要:在Java语言的日常编程中,也存在着容易被忽略的细节,这些细节可能会导致程序出现各种Bug。
本文分享自华为云社区《Java编程中容易忽略的细节总结丨【奔跑吧!JAVA】》,作者:jackwangcumt 。
Java语言构建的各类应用程序,在人类的日常生活中占用非常重要的地位,各大IT厂商几乎都会使用它来构建自己的产品,为客户提供服务。作为一个企业级应用开发语言,稳定和高效的运行,至关重要。在Java语言的日常编程中,也存在着容易被忽略的细节,这些细节可能会导致程序出现各种Bug,下面就对这些细节进行一些总结:
1 相等判断中的==和equals
在很多场景中,我们都需要判断两个对象是否相等,一般来说,判定两个对象的是否相等,都是依据其值是否相等,如两个字符串a和b的值都为"java",则我们认为二者相等。在Java中,有两个操作可以判断是否相当,即==和equals,但二者是有区别的,不可混用。下面给出示例:
String a = "java";
String b = new String("java");
System.out.println(a == b);//false
System.out.println(a.equals(b));//true
字符串a和b的字面值都为"java",用a == b判断则输出false,即不相等,而a.equals(b)则输出true,即相等。这是为什么呢?在Java中,String是一个不可变的类型,一般来说,如果两个String的值相等,默认情况下,会指定同一个内存地址,但这里字符串String b用new String方法强制生成一个新的String对象,因此,二者内存地址不一致。由于 == 需要判断对象的内存地址是否一致,因此返回false,而equals默认(override后可能不一定)是根据字面值来判断,即相等。
下面再给出一个示例:
//integer -128 to 127
Integer i1 = 100;
Integer i2 = 100;
System.out.println(i1 == i2);//true
i1 = 300;
i2 = 300;
System.out.println(i1 == i2);//false
System.out.println(i1.equals(i2));//true
这是由于Java中的Integer数值的范围为-128到127,因此在这范围内的对象的内存地址是一致的,而超过这个范围的数值对象的内存地址是不一致的,因此300这个数值在 == 比较下,返回false,但在equals比较下返回true。
2 switch语句中丢失了break
在很多场景中,我们需要根据输入参数的范围来分别进行处理,这里除了可以使用if ... else ...语句外,还可以使用switch语句。在switch语句中,会罗列出多个分支条件,并进行分别处理,但如果稍有不注意,就可能丢失关键字break语句,从而出现预期外的值。下面给出示例:
//缺少break关键字
public static void switchBugs(int v ) {
switch (v) {
case 0:
System.out.println("0");
//break
case 1:
System.out.println("1");
break;
case 2:
System.out.println("2");
break;
default:
System.out.println("other");
}
}
如果我们使用如下语句进行调用:
switchBugs(0);
则我们预期返回"0",但是却返回"0" "1"。这是由于case 0 分支下缺少break关键字,则虽然程序匹配了此分支,但是却能穿透到下一个分支,即case 1分支,然后遇到break后返回值。
3 大量的垃圾回收,效率低下
字符串的拼接操作,是非常高频的操作,但是如果涉及的拼接量很大,则如果直接用 + 符号进行字符串拼接,则效率非常低下,程序运行的速度很慢。下面给出示例:
private static void stringWhile(){
//获取开始时间
long start = System.currentTimeMillis();
String strV = "";
for (int i = 0; i < 100000; i++) {
strV = strV + "$";
}
//strings are immutable. So, on each iteration a new string is created.
// To address this we should use a mutable StringBuilder:
System.out.println(strV.length());
long end = System.currentTimeMillis(); //获取结束时间
System.out.println("程序运行时间: "+(end-start)+"ms");
start = System.currentTimeMillis();
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 100000; i++) {
sb.append("$");
}
System.out.println(strV.length());
end = System.currentTimeMillis();
System.out.println("程序运行时间: "+(end-start)+"ms");
}
上述示例分别在循环体中用 + 和 StringBuilder进行字符串拼接,并统计了运行的时间(毫秒),下面给出模拟电脑上的运行结果:
//+ 操作
100000
程序运行时间: 6078ms
StringBuilder操作
100000
程序运行时间: 2ms
由此可见,使用StringBuilder构建字符串速度相比于 + 拼接,效率上高出太多。究其原因,就是因为Java语言中的字符串类型是不可变的,因此 + 操作后会创建一个新的字符串,这样会涉及到大量的对象创建工作,也涉及到垃圾回收机制的介入,因此非常耗时。
4 循环时删除元素
有些情况下,我们需要从一个集合对象中删除掉特定的元素,如从一个编程语言列表中删除java语言,则就会涉及到此种场景,但是如果处理不当,则会抛出ConcurrentModificationException异常。下面给出示例:
private static void removeList() {
List<String> lists = new ArrayList<>();
lists.add("java");
lists.add("csharp");
lists.add("fsharp");
for (String item : lists) {
if (item.contains("java")) {
lists.remove(item);
}
}
}
运行上述方法,会抛出错误,此时可以用如下方法进行解决,即用迭代器iterator,具体如下所示:
private static void removeListOk() {
List<String> lists = new ArrayList<>();
lists.add("java");
lists.add("csharp");
lists.add("fsharp");
Iterator<String> hatIterator = lists.iterator();
while (hatIterator.hasNext()) {
String item = hatIterator.next();
if (item.contains("java")) {
hatIterator.remove();
}
}
System.out.println(lists);//[csharp, fsharp]
}
5 null引用
在方法中,首先应该对参数的合法性进行验证,第一需要验证参数是否为null,然后再判断参数是否是预期范围的值。如果不首先进行null判断,直接进行参数的比较或者方法的调用,则可能出现null引用的异常。下面给出示例:
private static void nullref(String words) {
//NullPointerException
if (words.equals("java")){
System.out.println("java");
}else{
System.out.println("not java");
}
}
如果此时我们用如下方法进行调用,则抛出异常:
nullref(null)
这是由于假设了words不为null,则可以调用String对象的equals方法。下面可以稍微进行一些修改,如下所示:
private static void nullref2(String words) {
if ("java".equals(words)){
System.out.println("java");
}else{
System.out.println("not java");
}
}
则此时执行则可以正确运行:
nullref2(null)
6 hashCode对equals的影响
前面提到,equals方法可以从字面值上来判断两个对象是否相等。一般来说,如果两个对象相等,则其hash code相等,但是如果hash code相等,则两个对象可能相等,也可能不相等。这是由于Object的equals方法和hashCode方法可以被Override。下面给出示例:
package com.jyd;
import java.util.Objects;
public class MySchool {
private String name;
MySchool(String name) {
this.name = name;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
MySchool _obj = (MySchool) o;
return Objects.equals(name, _obj.name);
}
@Override
public int hashCode() {
int code = this.name.hashCode();
System.out.println(code);
//return code; //true
//随机数
return (int) (Math.random() * 1000);//false }
}
Set<MySchool> mysets = new HashSet<>();
mysets.add(new MySchool("CUMT"));
MySchool obj = new MySchool("CUMT");
System.out.println(mysets.contains(obj));
执行上述代码,由于hashCode方法被Override,每次返回随机的hash Code值,则意味着两个对象的hash code不一致,那么equals判断则返回false,虽然二者的字面值都为"CUMT"。
7 内存泄漏
我们知道,计算机的内存是有限的,如果Java创建的对象一直不能进行释放,则新创建的对象会不断占用剩余的内存空间,最终导致内存空间不足,抛出内存溢出的异常。内存异常基本的单元测试不容易发现,往往都是上线运行一定时间后才发现的。下面给出示例:
package com.jyd; import java.util.Properties;
//内存泄漏模拟
public class MemoryLeakDemo {
public final String key;
public MemoryLeakDemo(String key) {
this.key =key;
}
public static void main(String args[]) {
try {
Properties properties = System.getProperties();
for(;;) {
properties.put(new MemoryLeakDemo("key"), "value");
}
} catch(Exception e) {
e.printStackTrace();
}
} /*
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
MemoryLeakDemo that = (MemoryLeakDemo) o;
return Objects.equals(key, that.key);
} @Override
public int hashCode() {
return Objects.hash(key);
}
*/ }
此示例中,有一个for无限循环,它会一直创建一个对象,并添加到properties容器中,如果MemoryLeakDemo类未给出自己的equals方法和hashCode方法,那么这个对象会被一直添加到properties容器中,最终内存泄漏。但是如果定义了自己的equals方法和hashCode方法(被注释的部分),那么新创建的MemoryLeakDemo实例,由于key值一致,则判定为已存在,则不会重复添加,此时则不会出现内存溢出。
Java编程中忽略这些细节,Bug肯定找上你的更多相关文章
- 在Java编码中,如何减少bug数量
众所周知,Java编程语言在IT行业是企业中不可缺少的.不管,从Web应用到Android应用,这款语言已经被广泛用于开发各类应用及代码中的复杂功能.但在编写代码时,bug永远是困扰每一位从业者的头号 ...
- Java 编程中关于异常处理的 10 个最佳实践
异常处理是Java 开发中的一个重要部分.它是关乎每个应用的一个非功能性需求,是为了处理任何错误状况,比如资源不可访问,非法输入,空输入等等.Java提供了几个异常处理特性,以try,catch 和 ...
- Java编程中“为了性能”尽量要做到的一些地方
最近的机器内存又爆满了,除了新增机器内存外,还应该好好review一下我们的代码,有很多代码编写过于随意化,这些不好的习惯或对程序语言的不了解是应该好好打压打压了. 下面是参考网络资源总结的一些在Ja ...
- 深入剖析Java编程中的中文问题及建议最优解决方法
摘录自:http://fafeng.blogbus.com/logs/3062998.html http://www.blogbus.com/fafeng-logs/3063006.html 深入剖析 ...
- Java编程中获取键盘输入实现方法及注意事项
Java编程中获取键盘输入实现方法及注意事项 1. 键盘输入一个数组 package com.wen201807.sort; import java.util.Scanner; public clas ...
- java编程中'为了性能'一些尽量做到的地方
原文地址:http://blog.csdn.NET/m13666368773/article/details/7796924 最近的机器内存又爆满了,出了新增机器内存外,还应该好好review一下我们 ...
- Java编程中的一些常见问题汇总
转载自 http://macrochen.iteye.com/blog/1393502 每天在写Java程序,其实里面有一些细节大家可能没怎么注意,这不,有人总结了一个我们编程中常见的问题.虽然一般 ...
- 教你几个 Java 编程中的奇技淫巧
枯燥的编程中总得有些乐趣,今天我们不谈论那些高深的技能,教你几个在编程中的奇技淫巧,说不定在某些时候还能炫耀一番呢. 1.找到最大值和最小值 不使用 if else switch 和三元运算符,在给定 ...
- 请问在JAVA编程中什么叫耦合?什么又叫解藕? 悬赏分:0 - 解决时间:2008-3-8 12:55
模块一的实现依赖于模块二,更改模块二后,模块一也得更改,那么二者就有耦合.修改程序,使得更改模块二后,模块一不受影响,那么就叫解藕 请问<java编程思想>中的“完全解藕”如何理解,和实现 ...
- JAVA编程中你一定要掌握的“快捷键”
JAVA编程常用快捷键 相信很多编程小白刚开始的时候,看向大神的时候都是双膝跪地满眼泪水的膜拜之情~不因为别的,就是因为他们可以随随便便敲出很多行代码,而且他们没有动鼠标!这时候就有人问了:“怎么才能 ...
随机推荐
- 立方骑士(lgP7354)
来水篇题解((( 图炸了,去洛谷博客看吧. 注:在本篇题解中, \(K\) 表示国王, \(N\) 表示骑士,不同颜色的 \(X\) 表示该位置国王可以达到,且被与之相同颜色的骑士封锁.方法不唯一,图 ...
- Python 既是解释型语言,也是编译型语言
哈喽大家好,我是咸鱼 不知道有没有小伙伴跟我一样,刚开始学习 Python 的时候都听说过 Python 是一种解释型语言,因为它在运行的时候会逐行解释并执行,而 C++ 这种是编译型语言 不过我今天 ...
- 【iOS开发】iOS App的加固保护原理:使用ipaguard混淆加固
摘要 在开发iOS应用时,保护应用程序的安全是非常重要的.本文将介绍一种使用ipaguard混淆加固的方法来保护iOS应用的安全.通过字符串混淆.类名和方法名混淆.程序结构混淆加密以及反调试.反注 ...
- CoreFX中Dictionary<TKey, TValue>的源码解读
无论是实际的项目中,还是在我们学习的过程中,都会重点的应用到Dictionary<TKey, TValue>这个存储类型.每次对Dictionary<TKey, TValue> ...
- 记一次线上问题引发的对 Mysql 锁机制分析
背景 最近双十一开门红期间组内出现了一次因 Mysql 死锁导致的线上问题,当时从监控可以看到数据库活跃连接数飙升,导致应用层数据库连接池被打满,后续所有请求都因获取不到连接而失败 整体业务代码精简逻 ...
- 一次显著的性能提升,从8s到0.7s
前言 最近我在公司优化了一些慢查询SQL,积累了一些SQL调优的实战经验. 我之前写过一些SQL优化相关的文章<聊聊SQL优化的15个小技巧>和<explain | 索引优化的这把绝 ...
- Excel中最牛的Index和match函数介绍
当谈到Excel中最强大的函数时,INDEX和MATCH往往会被提及.它们经常一起使用,可以用来查找和返回数据表中的特定数值或信息.下面是对这两个函数的详细介绍: INDEX 函数: INDEX函数的 ...
- Vue项目的创建、运行与端口号修改
前言:Vue-cli是Vue官方提供的一个脚手架,用于快速生成一个Vue的项目模板,依赖于NodeJS环境 NodeJS下载:NodeJS安装下载 Vue-cli下载:Vue-cli下载 一.Vue图 ...
- 使用 VS 2019 将 c# 生成 DLL 动态链接库文件
主要步骤: ChatGPT 的回答: 你可以尝试使用 Visual Studio 创建一个类库项目,然后将你写的两个类添加到该项目中,并进行编译,最终生成 DLL 文件.具体步骤如下: 打开 Visu ...
- k8s安装Ingress-Nginx
目前,DHorse(https://gitee.com/i512team/dhorse)只支持Ingress-nginx的Ingress实现,下面介绍Ingress-nginx的安装过程. 下载安装文 ...