当前位置: 首页 > 科技观察

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

时间:2023-03-22 17:30:57 科技观察

Java语言构建的各种应用程序在人类日常生活中占有非常重要的地位。几乎所有主要的IT制造商都使用它来构建自己的产品。为客户提供服务。作为企业级应用开发语言,稳定高效的运行非常重要。在Java语言的日常编程中,也有一些容易被忽视的细节。这些细节可能会导致程序出现各种错误。以下是对这些细节的总结:1==和相等判断中的equals用在很多场景中,我们都需要判断两个对象是否相等。一般来说,两个对象是否相等取决于它们的值是否相等。比如两个字符串a和b的值都是“java”,那么我们就认为两者相等。在Java中,有两种操作可以判断它们是否等价,即==和equals,但两者是不同的,不能混用。下面给出一个例子:Stringa="java";Stringb=newString("java");System.out.println(a==b);//falseSystem.out.println(a.equals(b));//真正的字符串a和b的字面值都是“java”。如果用a==b判断,会输出false,即不相等,a.equals(b)会输出true,即相等。为什么是这样?在Java中,String是一种不可变类型。一般来说,如果两个String的值相等,默认会指定相同的内存地址,但是这里Stringb使用了newString方法强制生成了一个新的String对象,所以两个String的内存地址不一致。由于==需要判断对象的内存地址是否一致,所以返回false,equals默认(override后不一定)根据字面值判断,即相等。这是另一个例子://integer-128to127Integeri1=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。2break在switch语句中丢失了。在很多场景下,我们需要根据输入参数的范围分别进行处理。除了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”。这是因为case0分支下没有break关键字。程序虽然匹配到了这个分支,但是可以穿透到下一个分支,即case1分支,遇到break后再返回值。3大量垃圾回收,效率低字符串的拼接操作是一个非常高频的操作,但是如果涉及的拼接量很大,如果直接使用+号进行字符串拼接,效率很低,而且程序运行的速度很慢。下面给出一个例子:privatestaticvoidstringWhile(){//获取开始时间longstart=System.currentTimeMillis();StringstrV="";for(inti=0;i<100000;i++){strV=strV+"$";}//stringsareimmutable.So,oneachiterationanewstringiscreated.//为了解决这个问题,我们应该使用可变的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语言,就会涉及到这样的场景,但是如果处理不当,会抛出A抛出ConcurrentModificationException。下面给出一个例子:privatestaticvoidremoveList(){Listlists=newArrayList<>();lists.add("java");lists.add("csharp");lists.add("fsharp");(Stringitem:lists){if(item.contains("java")){lists.remove(item);}}}运行上面的方法会报错。这时可以使用下面的方法解决,即使用iteratoriterator,具体如下:privatestaticvoidremoveListOk(){Listlists=newArrayList<>();lists.add("java");lists.add("csharp");lists.add("fsharp");IteratorhatIterator=lists.iterator();while(hatIterator.hasNext()){Stringitem=hatIterator.next();if(item.contains("java")){hatIterator.remove();}}System.out.println(lists);//[csharp,fsharp]}5nullreferences方法中,首先要验证参数的合法性。首先需要验证参数是否为null,然后判断参数是否在期望的范围值内。如果不先进行参数比较或方法调用,可能会出现空引用异常。下面给出一个例子:privatestaticvoidnullref(Stringwords){//NullPointerExceptionif(words.equals("java")){System.out.println("java");}else{System.out.println("notjava");}}如果我们此时使用下面的方法调用,会抛出异常:nullref(null)这是因为假设words不为null,可以调用String对象的equals方法。下面可以稍微修改如下:privatestaticvoidnullref2(Stringwords){if("java".equals(words)){System.out.println("java");}else{System.out.println("notjava");}}此时可以正确执行:nullref2(null)6hashCode对equals的影响前面提到equals方法可以从字面值判断两个对象是否相等。一般来说,如果两个对象相等,则它们的哈希码相等,但如果哈希码相等,则两个对象可能相等也可能不相等。这是因为Object的equals方法和hashCode方法是可以Override的。下面给出了一个例子:(o==null||getClass()!=o.getClass()){returnfalse;}MySchool_obj=(MySchool)o;returnObjects.equals(name,_obj.name);}@OverridepublicinthashCode(){intcode=this.name.hashCode();System.out.println(code);//returncode;//true//随机数return(int)(Math.random()*1000);//false}}Setmysets=newHashSet<>();mysets.add(newMySchool("CUMT"));MySchoolobj=newMySchool("CUMT");System.out.println(mysets.contains(obj));执行上面的代码,因为hashCode方法是Override,每次返回一个随机的hashCode值,说明两个对象的hashcode不一致,那么equals判断返回false,虽然两者的字面值是“CUMT”。7内存泄漏我们知道计算机的内存是有限的。如果Java创建的对象无法释放,新创建的对象会继续占用剩余的内存空间,最终导致内存空间不足,抛出内存不足异常。内存异常的基本单元测试不容易发现,往往是在线运行一定时间后才发现。下面给出一个例子:packagecom.jyd;importjava.util.Properties;//内存泄漏模拟getProperties();for(;;){properties.put(newMemoryLeakDemo("key"),"value");}}catch(Exceptione){e.printStackTrace();}}/*@Overridepublicbooleanequals(Objecto){if(this==o)returntrue;if(o==null||getClass()!=o.getClass())returnfalse;MemoryLeakDemothat=(MemoryLeakDemo)o;returnObjects.equals(key,that.key);}@OverridepublicinthashCode(){returnObjects.hash(key);}*/}在这个例子中,有一个无限循环,如果MemoryLeakDemo类没有提供自己的equals,它总是会创建一个对象并将其添加到属性容器中method和hashCodemethod,那么这个对象会一直被添加到属性容器中,最终内存泄漏。但是如果你自己定义equals方法和hashCode方法(注释部分),那么新创建的MemoryLeakDemo实例,因为key值是一致的,所以确定已经存在,不会重复添加,不会出现内存溢出此时。