Java编程中容易忽略的细节总结

简介: 在Java语言的日常编程中,也存在着容易被忽略的细节,这些细节可能会导致程序出现各种Bug,下面就对这些细节进行一些总结,希望能够对大家有帮助。

   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;
    }
@Overridepublicbooleanequals(Objecto) {
if (this==o) {
returntrue;
        }
if (o==null||getClass() !=o.getClass()) {
returnfalse;
        }
MySchool_obj= (MySchool) o;
returnObjects.equals(name, _obj.name);
    }
@OverridepublicinthashCode() {
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"。

相关文章
|
4天前
|
设计模式 安全 Java
Java编程中的单例模式:理解与实践
【10月更文挑战第31天】在Java的世界里,单例模式是一种优雅的解决方案,它确保一个类只有一个实例,并提供一个全局访问点。本文将深入探讨单例模式的实现方式、使用场景及其优缺点,同时提供代码示例以加深理解。无论你是Java新手还是有经验的开发者,掌握单例模式都将是你技能库中的宝贵财富。
12 2
|
6天前
|
Java API Apache
Java编程如何读取Word文档里的Excel表格,并在保存文本内容时保留表格的样式?
【10月更文挑战第29天】Java编程如何读取Word文档里的Excel表格,并在保存文本内容时保留表格的样式?
35 5
|
1天前
|
安全 Java 编译器
JDK 10中的局部变量类型推断:Java编程的简化与革新
JDK 10引入的局部变量类型推断通过`var`关键字简化了代码编写,提高了可读性。编译器根据初始化表达式自动推断变量类型,减少了冗长的类型声明。虽然带来了诸多优点,但也有一些限制,如只能用于局部变量声明,并需立即初始化。这一特性使Java更接近动态类型语言,增强了灵活性和易用性。
78 53
|
1天前
|
安全 Java 编译器
Java多线程编程的陷阱与最佳实践####
【10月更文挑战第29天】 本文深入探讨了Java多线程编程中的常见陷阱,如竞态条件、死锁、内存一致性错误等,并通过实例分析揭示了这些陷阱的成因。同时,文章也分享了一系列最佳实践,包括使用volatile关键字、原子类、线程安全集合以及并发框架(如java.util.concurrent包下的工具类),帮助开发者有效避免多线程编程中的问题,提升应用的稳定性和性能。 ####
15 1
|
4天前
|
存储 设计模式 分布式计算
Java中的多线程编程:并发与并行的深度解析####
在当今软件开发领域,多线程编程已成为提升应用性能、响应速度及资源利用率的关键手段之一。本文将深入探讨Java平台上的多线程机制,从基础概念到高级应用,全面解析并发与并行编程的核心理念、实现方式及其在实际项目中的应用策略。不同于常规摘要的简洁概述,本文旨在通过详尽的技术剖析,为读者构建一个系统化的多线程知识框架,辅以生动实例,让抽象概念具体化,复杂问题简单化。 ####
|
5天前
|
Java 开发者
在Java多线程编程的世界里,Lock接口正逐渐成为高手们的首选,取代了传统的synchronized关键字
在Java多线程编程的世界里,Lock接口正逐渐成为高手们的首选,取代了传统的synchronized关键字
30 4
|
5天前
|
消息中间件 供应链 Java
掌握Java多线程编程的艺术
【10月更文挑战第29天】 在当今软件开发领域,多线程编程已成为提升应用性能和响应速度的关键手段之一。本文旨在深入探讨Java多线程编程的核心技术、常见问题以及最佳实践,通过实际案例分析,帮助读者理解并掌握如何在Java应用中高效地使用多线程。不同于常规的技术总结,本文将结合作者多年的实践经验,以故事化的方式讲述多线程编程的魅力与挑战,旨在为读者提供一种全新的学习视角。
27 3
|
4天前
|
设计模式 安全 Java
Java编程中的单例模式深入解析
【10月更文挑战第31天】在编程世界中,设计模式就像是建筑中的蓝图,它们定义了解决常见问题的最佳实践。本文将通过浅显易懂的语言带你深入了解Java中广泛应用的单例模式,并展示如何实现它。
|
6天前
|
安全 Java 调度
Java中的多线程编程入门
【10月更文挑战第29天】在Java的世界中,多线程就像是一场精心编排的交响乐。每个线程都是乐团中的一个乐手,他们各自演奏着自己的部分,却又和谐地共同完成整场演出。本文将带你走进Java多线程的世界,让你从零基础到能够编写基本的多线程程序。
19 1
|
10天前
|
缓存 Java 调度
Java中的多线程编程:从基础到实践
【10月更文挑战第24天】 本文旨在为读者提供一个关于Java多线程编程的全面指南。我们将从多线程的基本概念开始,逐步深入到Java中实现多线程的方法,包括继承Thread类、实现Runnable接口以及使用Executor框架。此外,我们还将探讨多线程编程中的常见问题和最佳实践,帮助读者在实际项目中更好地应用多线程技术。
18 3
下一篇
无影云桌面