在Java语言的日常编程中,也存在着容易被忽略的细节,这些细节可能会导致程序出现各种Bug。

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");//breakcase 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)  {//NullPointerExceptionif (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;}@Overridepublic 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);}@Overridepublic 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();}}/*@Overridepublic 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);}@Overridepublic int hashCode() {return Objects.hash(key);}*/}

此示例中,有一个for无限循环,它会一直创建一个对象,并添加到properties容器中,如果MemoryLeakDemo类未给出自己的equals方法和hashCode方法,那么这个对象会被一直添加到properties容器中,最终内存泄漏。但是如果定义了自己的equals方法和hashCode方法(被注释的部分),那么新创建的MemoryLeakDemo实例,由于key值一致,则判定为已存在,则不会重复添加,此时则不会出现内存溢出。

IT技术分享社区

个人博客网站:https://programmerblog.xyz

文章推荐程序员效率:画流程图常用的工具程序员效率:整理常用的在线笔记软件远程办公:常用的远程协助软件,你都知道吗?51单片机程序下载、ISP及串口基础知识硬件:断路器、接触器、继电器基础知识

后端技术:Java编程中忽略这些细节,Bug肯定少不了相关推荐

  1. Java编程中忽略这些细节,Bug肯定找上你

    摘要:在Java语言的日常编程中,也存在着容易被忽略的细节,这些细节可能会导致程序出现各种Bug. 本文分享自华为云社区<Java编程中容易忽略的细节总结丨[奔跑吧!JAVA]>,作者:j ...

  2. Java编程中的注意点

    Java编程中的注意点 Java基础部分 1.Java存放浮点数精度问题 Java中在HashMap存放浮点数会有精度问题,可以使用字符串来存放 2.while(k–)的相关问题 上面那个while的 ...

  3. java编程中的异常分析及面向对象的思考总结[图]

    java编程中的异常分析及面向对象的思考总结[图] 1.异常: 程序中出现的不正常现象. 2.异常的由来: 程序在运行的过程中出现了不正常的情况,程序把它看成对象提取了属性行为(名字,原因,位置等信息 ...

  4. Java编程中“为了性能”需做的26件事

    下面是参考网络资源总结的一些在Java编程中尽可能要做到的一些地方. 1.尽量在合适的场合使用单例 使用单例可以减轻加载的负担,缩短加载的时间,提高加载的效率,但并不是所有地方都适用于单例,简单来说, ...

  5. JAVA 代码交互率低的原因分析,深入剖析Java编程中的中文问题及建议最优解决方法...

    说明:本文为作者原创,作者联系地址为: josserchai@yahoo.com .由于 Java 编程中的中文 问题是一个老生常谈的问题,在阅读了许多关于 Java 中文问题解决方法之后,结合作者的 ...

  6. Java编程中“为了性能”尽量要做到的一些地方 [转]

    最近的机器内存又爆满了,除了新增机器内存外,还应该好好review一下我们的代码,有很多代码编写过于随意化,这些不好的习惯或对程序语言的不了解是应该好好打压打压了. 下面是参考网络资源总结的一些在Ja ...

  7. 正确的初始化,在 Java 编程中至关重要!

    有人说,你应该关注时事.财经,甚至流行的电影.电视剧,才有可能趁着热点写出爆文:有人说,你别再写"无聊"的技术文了,因为程序员的圈子真的很小,即便是像圈中鸿祥那样的招牌大牛,文章是 ...

  8. Java编程中“为了性能”尽量要做的26点

    最近的机器内存又爆满了,除了新增机器内存外,还应该好好review一下我们的代码,有很多代码编写过于随意化,这些不好的习惯或对程序语言的不了解是应该好好打压打压了. 下面是参考网络资源总结的一些在Ja ...

  9. 总结在java编程中的经验教训

    J2EE综合--总结在java编程中的经验教训 我实际工作中的经验教训,在这里与大家共享,空的字符串是"",不带空格outputStream在打印时,打印的内容后面不要加/n,否则 ...

最新文章

  1. crontab 备份mysql数据库_crontab定时备份mySQL数据库
  2. Windows10 搭建java环境——JDK11的安装与eclipse的安装
  3. 在数组中查找一个数并输出所处位置
  4. ecshop调用指定分类(包含子分类)下所有产品的评论信息
  5. 从16.6%到74.2%,谷歌新模型刷新ImageNet纪录,第一作者是上海交大毕业生谢其哲...
  6. 什么是DQL、DML、DDL、DCL
  7. C#.net调用Excel出现问题
  8. 开源硬件基金是如何运作的_如何在没有任何风险投资资金的情况下通过开源硬件赚钱
  9. php递归简单例子,php递归json类实例
  10. 元旦,请查收程序员专属祝福礼!
  11. [渝粤教育] 西南科技大学 交通运输经济 在线考试复习资料
  12. 计算机科学类期刊排名,计算机学术期刊排名:Computer Science Journal Ranki
  13. “马赛克”真能去除了?老司机狂喜!这一神器一键去除!
  14. CoordinatorLayout+AppBarLayout+CollapsingToolbarLayout+Toolbar实现渐变透明的状态栏
  15. 技术面试官的9大误区
  16. 【转】以太坊 2.0 中的验证者经济模型
  17. PyCharm调试时一直显示collecting data
  18. ide项目文件夹浅黄色编写代码无提示或看不到项目中的文件夹
  19. 读SQL进阶教程笔记12_地址与三值逻辑
  20. python取元素_python 如何提取对象内的元素

热门文章

  1. Exchange2010外部传输域的使用方法
  2. 电视百科常识 九大视频接口全接触
  3. AA级与AAA级台灯 重要指标对比
  4. HALCON示例程序high.hdev使用不同方法提取区域
  5. ole2高级编程技术 pdf_21天快速掌握Python语言,《21天学通Python》PDF版送给你去学...
  6. 金蝶系统服务器要求,金蝶服务器安装及其相关要求.doc
  7. centos7 docker安装和使用_入门教程
  8. golang log日志
  9. 省选之前的未完成的计划(截至到省选)
  10. Python自动化--语言基础5--面向对象、迭代器、range和切片的区分