当前位置: 首页 > news >正文

Java 开发最容易犯的 10 个错误

🧑 博主简介:CSDN博客专家历代文学网(PC端可以访问:https://literature.sinhy.com/#/literature?__c=1000,移动端可微信小程序搜索“历代文学”)总架构师,15年工作经验,精通Java编程高并发设计Springboot和微服务,熟悉LinuxESXI虚拟化以及云原生Docker和K8s,热衷于探索科技的边界,并将理论知识转化为实际应用。保持对新技术的好奇心,乐于分享所学,希望通过我的实践经历和见解,启发他人的创新思维。在这里,我希望能与志同道合的朋友交流探讨,共同进步,一起在技术的世界里不断学习成长。
技术合作请加本人wx(注明来自csdn):foreast_sea


Java 开发最容易犯的 10 个错误

  • Java 开发最容易犯的 10 个错误
    • 错误一:Array 转换成 ArrayList
    • 错误二:检查数组是否包含某个值
    • 错误三:在 List 中循环删除元素
    • 错误四:Hashtable 和 HashMap
    • 错误五:使用原始类型的集合
    • 错误六:访问级别问题
    • 错误七:ArrayList 和 LinkedList
    • 错误八:可变和不可变
    • 错误九:构造函数
    • 错误十:到底是使用 “” 还是构造函数

作为 Java 开发,我们在写代码的过程中难免会产生各种奇思妙想的 bug ,有些 bug 就挺让人无奈的,比如说各种空指针异常,在 ArrayList 的迭代中进行删除操作引发异常,数组下标越界异常等。

错误一:Array 转换成 ArrayList

Array 转换成 ArrayList 还能出错?这是哪个笨。。。。。。

等等,你先别着急说,先来看看是怎么回事。

如果要将数组转换为 ArrayList,我们一般的做法会是这样

List<String>list=Arrays.asList(arr);

Arrays.asList() 将返回一个 ArrayList,它是 Arrays 中的私有静态类,它不是 java.util.ArrayList 类。如下图所示

Arrays 内部的 ArrayList 只有 set、get、contains 等方法,但是没有能够像是 add 这种能够使其内部结构进行改变的方法,所以 Arrays 内部的 ArrayList 的大小是固定的。

如果要创建一个能够添加元素的 ArrayList ,你可以使用下面这种创建方式:

ArrayList<String>arrayList=newArrayList<String>(Arrays.asList(arr));

因为 ArrayList 的构造方法是可以接收一个 Collection 集合的,所以这种创建方式是可行的。

错误二:检查数组是否包含某个值

检查数组中是否包含某个值,部分程序员经常会这么做:

Set<String>set=newHashSet<String>(Arrays.asList(arr));returnset.contains(targetValue);

这段代码虽然没错,但是有额外的性能损耗,正常情况下,不用将其再转换为set,直接这么做就好了:

returnArrays.asList(arr).contains(targetValue);

或者使用下面这种方式(穷举法,循环判断)

for(Strings:arr){if(s.equals(targetValue))returntrue;}returnfalse;

上面第一段代码比第二段更具有可读性。

错误三:在 List 中循环删除元素

这个错误我相信很多小伙伴都知道了,在循环中删除元素是个禁忌,有段时间内我在审查代码的时候就喜欢看团队的其他小伙伴有没有犯这个错误。

说到底,为什么不能这么做(集合内删除元素)呢?且看下面代码

ArrayList<String>list=newArrayList<String>(Arrays.asList("a","b","c","d"));for(inti=0;i<list.size();i++){list.remove(i);}System.out.println(list);

这个输出结果你能想到么?是不是蠢蠢欲动想试一波了?

答案其实是 [b,d]

为什么只有两个值?我这不是循环输出的么?

其实,在列表内部,当你使用外部remove 的时候,一旦 remove 一个元素后,其列表的内部结构会发生改变,一开始集合总容量是 4,remove 一个元素之后就会变为 3,然后再和 i 进行比较判断。。。。。。所以只能输出两个元素。

你可能知道使用迭代器是正确的 remove 元素的方式,你还可能知道 for-each 和 iterator 这种工作方式类似,所以你写下了如下代码

ArrayList<String>list=newArrayList<String>(Arrays.asList("a","b","c","d"));for(Strings:list){if(s.equals("a"))list.remove(s);}

然后你充满自信的 run xxx.main() 方法,结果。。。。。。ConcurrentModificationException

为啥呢?

那是因为使用 ArrayList 中外部 remove 元素,会造成其内部结构和游标的改变。

在阿里开发规范上,也有不要在 for-each 循环内对元素进行 remove/add 操作的说明。

所以大家要使用 List 进行元素的添加或者删除操作,一定要使用迭代器进行删除。也就是

ArrayList<String>list=newArrayList<String>(Arrays.asList("a","b","c","d"));Iterator<String>iter=list.iterator();while(iter.hasNext()){Strings=iter.next();if(s.equals("a")){iter.remove();}}

.next() 必须在 .remove() 之前调用。 在 foreach 循环中,编译器会在删除元素的操作后调用 .next(),导致ConcurrentModificationException。

错误四:Hashtable 和 HashMap

这是一条算法方面的规约:按照算法的约定,Hashtable 是数据结构的名称,但是在 Java 中,数据结构的名称是 HashMap,Hashtable 和 HashMap 的主要区别之一就是 Hashtable 是同步的,所以很多时候你不需要 Hashtable ,而是使用 HashMap。

错误五:使用原始类型的集合

这是一条泛型方面的约束:

在 Java 中,原始类型和无界通配符类型很容易混合在一起。以 Set 为例,Set 是原始类型,而 Set<?> 是无界通配符类型。

比如下面使用原始类型 List 作为参数的代码:

publicstaticvoidadd(Listlist,Objecto){list.add(o);}publicstaticvoidmain(String[]args){List<String>list=newArrayList<String>();add(list,10);Strings=list.get(0);}

这段代码会抛出java.lang.ClassCastException异常,为啥呢?

使用原始类型集合是比较危险的,因为原始类型会跳过泛型检查而且不安全,Set、Set<?> 和 Set<Object>存在巨大的差异,而且泛型在使用中很容易造成类型擦除。

大家都知道,Java 的泛型是伪泛型,这是因为 Java 在编译期间,所有的泛型信息都会被擦掉,正确理解泛型概念的首要前提是理解类型擦除。Java 的泛型基本上都是在编译器这个层次上实现的,在生成的字节码中是不包含泛型中的类型信息的,使用泛型的时候加上类型参数,在编译器编译的时候会去掉,这个过程成为类型擦除

如在代码中定义List<Object>List<String>等类型,在编译后都会变成List,JVM 看到的只是List,而由泛型附加的类型信息对 JVM 是看不到的。Java 编译器会在编译时尽可能的发现可能出错的地方,但是仍然无法在运行时刻出现的类型转换异常的情况,类型擦除也是 Java 的泛型与 C++ 模板机制实现方式之间的重要区别。

比如下面这段示例

publicclassTest{publicstaticvoidmain(String[]args){ArrayList<String>list1=newArrayList<String>();list1.add("abc");ArrayList<Integer>list2=newArrayList<Integer>();list2.add(123);System.out.println(list1.getClass()==list2.getClass());}}

在这个例子中,我们定义了两个ArrayList数组,不过一个是ArrayList<String>泛型类型的,只能存储字符串;一个是ArrayList<Integer>泛型类型的,只能存储整数,最后,我们通过list1对象和list2对象的getClass()方法获取他们的类的信息,最后发现结果为true。说明泛型类型StringInteger都被擦除掉了,只剩下原始类型。

所以,最上面那段代码,把 10 添加到 Object 类型中是完全可以的,然而将 Object 类型的 “10” 转换为 String 类型就会抛出类型转换异常。

错误六:访问级别问题

我相信大部分开发在设计 class 或者成员变量的时候,都会简单粗暴的直接声明public xxx,这是一种糟糕的设计,声明为 public 就很容易赤身裸体,这样对于类或者成员变量来说,都存在一定危险性。

错误七:ArrayList 和 LinkedList

哈哈哈,ArrayList 是我见过程序员使用频次最高的工具类,没有之一。

当开发人员不知道 ArrayList 和 LinkedList 的区别时,他们经常使用 ArrayList(其实实际上,就算知道他们的区别,他们也不用 LinkedList,因为这点性能不值一提),因为看起来 ArrayList 更熟悉。。。。。。

但是实际上,ArrayList 和 LinkedList 存在巨大的性能差异,简而言之,如果添加/删除操作大量且随机访问操作不是很多,则应首选 LinkedList。如果存在大量的访问操作,那么首选 ArrayList,但是 ArrayList 不适合进行大量的添加/删除操作。

错误八:可变和不可变

不可变对象有很多优点,比如简单、安全等。但是不可变对象需要为每个不同的值分配一个单独的对象,对象不具备复用性,如果这类对象过多可能会导致垃圾回收的成本很高。在可变和不可变之间进行选择时需要有一个平衡。

一般来说,可变对象用于避免产生过多的中间对象。 比如你要连接大量字符串。 如果你使用一个不可变的字符串,你会产生很多可以立即进行垃圾回收的对象。 这会浪费 CPU 的时间和精力,使用可变对象是正确的解决方案(例如 StringBuilder)。如下代码所示:

Stringresult="";for(Strings:arr){result=result+s;}

所以,正确选择可变对象还是不可变对象需要慎重抉择。

错误九:构造函数

首先看一段代码,分析为什么会编译不通过?

发生此编译错误是因为未定义默认 Super 的构造函数。 在 Java 中,如果一个类没有定义构造函数,编译器会默认为该类插入一个默认的无参数构造函数。 如果在 Super 类中定义了构造函数,在这种情况下 Super(String s),编译器将不会插入默认的无参数构造函数。 这就是上面 Super 类的情况。

要想解决这个问题,只需要在 Super 中添加一个无参数的构造函数即可。

publicSuper(){System.out.println("Super");}

错误十:到底是使用 “” 还是构造函数

考虑下面代码:

Stringx="abc";Stringy=newString("abc");

上面这两段代码有什么区别吗?

可能下面这段代码会给出你回答

Stringa="abcd";Stringb="abcd";System.out.println(a==b);// TrueSystem.out.println(a.equals(b));// TrueStringc=newString("abcd");Stringd=newString("abcd");System.out.println(c==d);// FalseSystem.out.println(c.equals(d));// True

这就是一个典型的内存分配问题。

http://www.cnnetsun.cn/news/27499.html

相关文章:

  • 用 Reader 建个私人图书馆,加上cpolar随时随地畅快阅读
  • 下一代盲盒系统核心架构解析:JAVA-S1如何打造极致公平与全球化体验
  • LangGraph深度解析:从图基础到人机交互的AI工作流框架实践
  • C++--
  • 算法练习4--数组:长度最小的子数组
  • Spring Cloud Gateway为什么要推出 WebMVC 版本?深度解析两大版本的差异与选型
  • git和github的区别
  • 小白从零开始勇闯人工智能Linux初级篇(MySQL库)
  • Bootstrap 模态框详解
  • MinerU终极安全离线部署指南:完全断网环境解决方案
  • 练题100天——DAY24:罗马数字转整数+环形链表+大小端判断
  • 网站域名:关键的战略资产
  • Airflow 做 ETL,真不是“排个 DAG 就完事儿”:那些年我踩过的坑与悟出的道
  • 数据库连接池监控最佳实践:用 Prometheus + Grafana 打造可视化监控体系
  • Windows验机
  • 别让孩子视力提早“透支” ,这份护眼指南请收好
  • 儿童青少年近视干预科学指引,破解家长近视防控焦虑
  • 解析 .NET 核心基石:CTS、CLS 与 CLR 的核心价值与协同作用
  • Selinux权限的检测
  • 常见报错org.apache.ibatis.binding.BindingException: Invalid bound statement (not found): org.example.dem
  • 甲骨文AI投资支出激增致股价创24年最大跌幅
  • TinyMCE4粘贴word超链接自动解析域名
  • TinyMCE6处理微信公众号音频视频嵌入
  • 昇腾 Ascend 自定义算子开发全攻略:从 TBE DSL 到 AICPU,打通 AI 加速最后一公里
  • 当电机开始“唱歌“:NVH工程师的降噪日常
  • AI界的“经济适用男“!80亿参数小模型完胜GPT-5,成本降低70%,CSDN程序员必藏的智能调度方案
  • FPGA教程系列-Vivado Aurora 8B/10B 例程解读
  • 227827827
  • MCU的启动流程你了解么?
  • 逻辑回归(Logistic Regression)进行多分类的实战