Java语言构建的各类应用程序,在人类的日常生活中占用非常重要的地位,各大IT厂商几乎都会使用它来构建自己的产品,为客户提供服务。作为一个企业级应用开发语言,稳定和高效的运行,至关重要。在Java语言的日常编程中,也存在着容易被忽略的细节,这些细节可能会导致程序出现各种Bug,下面就对这些细节进行一些总结:
1 相等判断中的==和equals
在很多场景中,我们都需要判断两个对象是否相等,一般来说,判定两个对象的是否相等,都是依据其值是否相等,如两个字符串a和b的值都为"java",则我们认为二者相等。在Java中,有两个操作可以判断是否相当,即==和equals,但二者是有区别的,不可混用。下面给出示例:
Stringa="java"; Stringb=newString("java"); System.out.println(a==b);//falseSystem.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 127Integeri1=100; Integeri2=100; System.out.println(i1==i2);//truei1=300; i2=300; System.out.println(i1==i2);//falseSystem.out.println(i1.equals(i2));//true
这是由于Java中的Integer数值的范围为-128到127,因此在这范围内的对象的内存地址是一致的,而超过这个范围的数值对象的内存地址是不一致的,因此300这个数值在 == 比较下,返回false,但在equals比较下返回true。
2 switch语句中丢失了break
在很多场景中,我们需要根据输入参数的范围来分别进行处理,这里除了可以使用if ... else ...语句外,还可以使用switch语句。在switch语句中,会罗列出多个分支条件,并进行分别处理,但如果稍有不注意,就可能丢失关键字break语句,从而出现预期外的值。下面给出示例:
//缺少break关键字publicstaticvoidswitchBugs(intv ) { switch (v) { case0: System.out.println("0"); //breakcase1: System.out.println("1"); break; case2: System.out.println("2"); break; default: System.out.println("other"); } }
如果我们使用如下语句进行调用:
switchBugs(0);
则我们预期返回"0",但是却返回"0" "1"。这是由于case 0 分支下缺少break关键字,则虽然程序匹配了此分支,但是却能穿透到下一个分支,即case 1分支,然后遇到break后返回值。
3 大量的垃圾回收,效率低下
字符串的拼接操作,是非常高频的操作,但是如果涉及的拼接量很大,则如果直接用 + 符号进行字符串拼接,则效率非常低下,程序运行的速度很慢。下面给出示例:
privatestaticvoidstringWhile(){ //获取开始时间longstart=System.currentTimeMillis(); StringstrV=""; for (inti=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()); longend=System.currentTimeMillis(); //获取结束时间System.out.println("程序运行时间: "+(end-start)+"ms"); start=System.currentTimeMillis(); StringBuildersb=newStringBuilder(); for (inti=0; i<100000; i++) { sb.append("$"); } System.out.println(strV.length()); end=System.currentTimeMillis(); System.out.println("程序运行时间: "+(end-start)+"ms"); }
上述示例分别在循环体中用 + 和 StringBuilder进行字符串拼接,并统计了运行的时间(毫秒),下面给出模拟电脑上的运行结果:
//+ 操作100000程序运行时间:6078msStringBuilder操作100000程序运行时间:2ms
由此可见,使用StringBuilder构建字符串速度相比于 + 拼接,效率上高出太多。究其原因,就是因为Java语言中的字符串类型是不可变的,因此 + 操作后会创建一个新的字符串,这样会涉及到大量的对象创建工作,也涉及到垃圾回收机制的介入,因此非常耗时。
4 循环时删除元素
有些情况下,我们需要从一个集合对象中删除掉特定的元素,如从一个编程语言列表中删除java语言,则就会涉及到此种场景,但是如果处理不当,则会抛出ConcurrentModificationException异常。下面给出示例:
privatestaticvoidremoveList() { List<String>lists=newArrayList<>(); lists.add("java"); lists.add("csharp"); lists.add("fsharp"); for (Stringitem : lists) { if (item.contains("java")) { lists.remove(item); } } }
运行上述方法,会抛出错误,此时可以用如下方法进行解决,即用迭代器iterator,具体如下所示:
privatestaticvoidremoveListOk() { List<String>lists=newArrayList<>(); lists.add("java"); lists.add("csharp"); lists.add("fsharp"); Iterator<String>hatIterator=lists.iterator(); while (hatIterator.hasNext()) { Stringitem=hatIterator.next(); if (item.contains("java")) { hatIterator.remove(); } } System.out.println(lists);//[csharp, fsharp]}
5 null引用
在方法中,首先应该对参数的合法性进行验证,第一需要验证参数是否为null,然后再判断参数是否是预期范围的值。如果不首先进行null判断,直接进行参数的比较或者方法的调用,则可能出现null引用的异常。下面给出示例:
privatestaticvoidnullref(Stringwords) { //NullPointerExceptionif (words.equals("java")){ System.out.println("java"); }else{ System.out.println("not java"); } }
如果此时我们用如下方法进行调用,则抛出异常:
nullref(null)
这是由于假设了words不为null,则可以调用String对象的equals方法。下面可以稍微进行一些修改,如下所示:
privatestaticvoidnullref2(Stringwords) { if ("java".equals(words)){ System.out.println("java"); }else{ System.out.println("not java"); } }
6 hashCode对equals的影响
前面提到,equals方法可以从字面值上来判断两个对象是否相等。一般来说,如果两个对象相等,则其hash code相等,但是如果hash code相等,则两个对象可能相等,也可能不相等。这是由于Object的equals方法和hashCode方法可以被Override。下面给出示例:
packagecom.jyd; importjava.util.Objects; publicclassMySchool { privateStringname; MySchool(Stringname) { this.name=name; } publicbooleanequals(Objecto) { if (this==o) { returntrue; } if (o==null||getClass() !=o.getClass()) { returnfalse; } MySchool_obj= (MySchool) o; returnObjects.equals(name, _obj.name); } publicinthashCode() { intcode=this.name.hashCode(); System.out.println(code); //return code; //true//随机数return (int) (Math.random() *1000);//false } } /////////////////////////////////////////////////Set<MySchool>mysets=newHashSet<>(); mysets.add(newMySchool("CUMT")); MySchoolobj=newMySchool("CUMT"); System.out.println(mysets.contains(obj));
执行上述代码,由于hashCode方法被Override,每次返回随机的hash Code值,则意味着两个对象的hash code不一致,那么equals判断则返回false,虽然二者的字面值都为"CUMT"。