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

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

 [[436028]]

Java语言构建的各类应用程序,在人类的日常生活中占用非常重要的地位,各大IT厂商几乎都会使用它来构建自己的产品,为客户提供服务。作为一个企业级应用开发语言,稳定和高效的运行,至关重要。在Java语言的日常编程中,也存在着容易被忽略的细节,这些细节可能会导致程序出现各种Bug,下面就对这些细节进行一些总结:

1 相等判断中的==和equals

在很多场景中,我们都需要判断两个对象是否相等,一般来说,判定两个对象的是否相等,都是依据其值是否相等,如两个字符串a和b的值都为"java",则我们认为二者相等。在Java中,有两个操作可以判断是否相当,即==和equals,但二者是有区别的,不可混用。下面给出示例:

 

  1. String a = "java"
  2. String b = new String("java"); 
  3. System.out.println(a == b);//false 
  4. 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后可能不一定)是根据字面值来判断,即相等。

下面再给出一个示例:

 

  1. //integer -128 to 127 
  2. Integer i1 = 100; 
  3. Integer i2 = 100; 
  4. System.out.println(i1 == i2);//true 
  5. i1 = 300; 
  6. i2 = 300; 
  7. System.out.println(i1 == i2);//false 
  8. System.out.println(i1.equals(i2));//true 

 

这是由于Java中的Integer数值的范围为-128到127,因此在这范围内的对象的内存地址是一致的,而超过这个范围的数值对象的内存地址是不一致的,因此300这个数值在 == 比较下,返回false,但在equals比较下返回true。

2 switch语句中丢失了break

在很多场景中,我们需要根据输入参数的范围来分别进行处理,这里除了可以使用if ... else ...语句外,还可以使用switch语句。在switch语句中,会罗列出多个分支条件,并进行分别处理,但如果稍有不注意,就可能丢失关键字break语句,从而出现预期外的值。下面给出示例:

 

  1. //缺少break关键字 
  2.  public static void switchBugs(int v ) { 
  3.        switch (v) { 
  4.             case 0: 
  5.                 System.out.println("0"); 
  6.                 //break 
  7.             case 1: 
  8.                 System.out.println("1"); 
  9.                 break; 
  10.             case 2: 
  11.                 System.out.println("2"); 
  12.                 break; 
  13.             default
  14.                 System.out.println("other"); 
  15.        } 

 

如果我们使用如下语句进行调用:

 

  1. switchBugs(0); 

则我们预期返回"0",但是却返回"0" "1"。这是由于case 0 分支下缺少break关键字,则虽然程序匹配了此分支,但是却能穿透到下一个分支,即case 1分支,然后遇到break后返回值。

3 大量的垃圾回收,效率低下

字符串的拼接操作,是非常高频的操作,但是如果涉及的拼接量很大,则如果直接用 + 符号进行字符串拼接,则效率非常低下,程序运行的速度很慢。下面给出示例:

 

  1. private static void stringWhile(){ 
  2.     //获取开始时间 
  3.     long start = System.currentTimeMillis(); 
  4.     String strV = ""
  5.     for (int i = 0; i < 100000; i++) { 
  6.         strV = strV + "$"
  7.     } 
  8.     //strings are immutable. So, on each iteration a new string is created. 
  9.     // To address this we should use a mutable StringBuilder: 
  10.     System.out.println(strV.length()); 
  11.     long end = System.currentTimeMillis(); //获取结束时间 
  12.     System.out.println("程序运行时间: "+(end-start)+"ms"); 
  13.     start = System.currentTimeMillis(); 
  14.     StringBuilder sb = new StringBuilder(); 
  15.     for (int i = 0; i < 100000; i++) { 
  16.         sb.append("$"); 
  17.     } 
  18.     System.out.println(strV.length()); 
  19.     end = System.currentTimeMillis(); 
  20.     System.out.println("程序运行时间: "+(end-start)+"ms"); 

 

上述示例分别在循环体中用 + 和 StringBuilder进行字符串拼接,并统计了运行的时间(毫秒),下面给出模拟电脑上的运行结果:

 

  1. //+ 操作 
  2. 100000 
  3. 程序运行时间: 6078ms 
  4. StringBuilder操作 
  5. 100000 
  6. 程序运行时间: 2ms 

 

由此可见,使用StringBuilder构建字符串速度相比于 + 拼接,效率上高出太多。究其原因,就是因为Java语言中的字符串类型是不可变的,因此 + 操作后会创建一个新的字符串,这样会涉及到大量的对象创建工作,也涉及到垃圾回收机制的介入,因此非常耗时。

4 循环时删除元素

有些情况下,我们需要从一个集合对象中删除掉特定的元素,如从一个编程语言列表中删除java语言,则就会涉及到此种场景,但是如果处理不当,则会抛出

ConcurrentModificationException异常。下面给出示例:

 

  1. private static void removeList() { 
  2.     List<String> lists = new ArrayList<>(); 
  3.     lists.add("java"); 
  4.     lists.add("csharp"); 
  5.     lists.add("fsharp"); 
  6.     for (String item : lists) { 
  7.         if (item.contains("java")) { 
  8.             lists.remove(item); 
  9.         } 
  10.     } 

 

运行上述方法,会抛出错误,此时可以用如下方法进行解决,即用迭代器iterator,具体如下所示:

 

  1. private static void removeListOk() { 
  2.     List<String> lists = new ArrayList<>(); 
  3.     lists.add("java"); 
  4.     lists.add("csharp"); 
  5.     lists.add("fsharp"); 
  6.     Iterator<String> hatIterator = lists.iterator(); 
  7.     while (hatIterator.hasNext()) { 
  8.         String item = hatIterator.next(); 
  9.         if (item.contains("java")) { 
  10.             hatIterator.remove(); 
  11.         } 
  12.     } 
  13.     System.out.println(lists);//[csharp, fsharp] 

 

5 null引用

在方法中,首先应该对参数的合法性进行验证,第一需要验证参数是否为null,然后再判断参数是否是预期范围的值。如果不首先进行null判断,直接进行参数的比较或者方法的调用,则可能出现null引用的异常。下面给出示例:

 

  1. private static void nullref(String words)  { 
  2.     //NullPointerException 
  3.     if (words.equals("java")){ 
  4.         System.out.println("java"); 
  5.     }else
  6.         System.out.println("not java"); 
  7.     } 

 

如果此时我们用如下方法进行调用,则抛出异常:

 

  1. nullref(null

这是由于假设了words不为null,则可以调用String对象的equals方法。下面可以稍微进行一些修改,如下所示:

 

  1. private static void nullref2(String words)  { 
  2.     if ("java".equals(words)){ 
  3.         System.out.println("java"); 
  4.     }else
  5.         System.out.println("not java"); 
  6.     } 

 

则此时执行则可以正确运行:

 

  1. nullref2(null

6 hashCode对equals的影响

前面提到,equals方法可以从字面值上来判断两个对象是否相等。一般来说,如果两个对象相等,则其hash code相等,但是如果hash code相等,则两个对象可能相等,也可能不相等。这是由于Object的equals方法和hashCode方法可以被Override。下面给出示例:

 

  1. package com.jyd; 
  2. import java.util.Objects; 
  3. public class MySchool { 
  4.     private String name
  5.     MySchool(String name) { 
  6.         this.name = name
  7.     } 
  8.     @Override 
  9.     public boolean equals(Object o) { 
  10.         if (this == o) { 
  11.             return true
  12.         } 
  13.         if (o == null || getClass() != o.getClass()) { 
  14.             return false
  15.         } 
  16.         MySchool _obj = (MySchool) o; 
  17.         return Objects.equals(name, _obj.name); 
  18.     } 
  19.     @Override 
  20.     public int hashCode() { 
  21.         int code = this.name.hashCode(); 
  22.         System.out.println(code); 
  23.         //return code; //true 
  24.         //随机数 
  25.         return (int) (Math.random() * 1000);//false 
  26.  
  27.     } 
  28. Set<MySchool> mysets = new HashSet<>(); 
  29. mysets.add(new MySchool("CUMT")); 
  30. MySchool obj = new MySchool("CUMT"); 
  31. System.out.println(mysets.contains(obj)); 

 

执行上述代码,由于hashCode方法被Override,每次返回随机的hash Code值,则意味着两个对象的hash code不一致,那么equals判断则返回false,虽然二者的字面值都为"CUMT"。

7 内存泄漏

我们知道,计算机的内存是有限的,如果Java创建的对象一直不能进行释放,则新创建的对象会不断占用剩余的内存空间,最终导致内存空间不足,抛出内存溢出的异常。内存异常基本的单元测试不容易发现,往往都是上线运行一定时间后才发现的。下面给出示例:

 

  1. package com.jyd; 
  2.  
  3. import java.util.Properties; 
  4. //内存泄漏模拟 
  5. public class MemoryLeakDemo { 
  6.     public final String key
  7.     public MemoryLeakDemo(String key) { 
  8.         this.key =key
  9.     } 
  10.     public static void main(String args[]) { 
  11.         try { 
  12.             Properties properties = System.getProperties(); 
  13.             for(;;) { 
  14.                 properties.put(new MemoryLeakDemo("key"), "value"); 
  15.             } 
  16.         } catch(Exception e) { 
  17.             e.printStackTrace(); 
  18.         } 
  19.     } 
  20.  
  21.     /* 
  22.     @Override 
  23.     public boolean equals(Object o) { 
  24.         if (this == o) return true
  25.         if (o == null || getClass() != o.getClass()) return false
  26.         MemoryLeakDemo that = (MemoryLeakDemo) o; 
  27.         return Objects.equals(key, that.key); 
  28.     } 
  29.  
  30.     @Override 
  31.     public int hashCode() { 
  32.         return Objects.hash(key); 
  33.     } 
  34.     */ 
  35.  

 

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

责任编辑:华轩 来源: 今日头条
相关推荐

2011-06-10 13:57:00

SEO

2023-11-15 08:22:42

Java开发小技巧

2018-09-02 15:43:56

Python代码编程语言

2020-10-14 14:44:51

iPhone 12苹果iPhone

2023-01-28 09:38:48

接口SpringMVC

2023-09-08 08:23:29

Servlet程序MVC

2020-12-17 08:56:51

单例模式JVM

2023-01-16 08:09:51

SpringMVC句柄

2016-05-10 10:49:28

浏览网络方法

2018-10-30 10:53:42

路由器无线网络无线路由器

2023-05-10 07:42:26

Java多线程编程

2018-08-01 14:45:16

PHP编程语言

2018-04-04 08:47:16

数据中心基础设施

2023-09-08 07:23:52

2017-09-15 13:52:36

iPhone

2017-01-15 01:12:40

码农简历专业名词

2020-09-29 10:15:54

开发技能Kubernetes

2015-07-03 11:20:41

编程学习方法

2011-07-19 09:20:00

程序员编程

2021-01-07 05:40:13

BLE模块Android
点赞
收藏

51CTO技术栈公众号