protected java 小心陷阱!Java中常犯的10个错误
protected java 小心陷阱!Java中常犯的10个错误
全文共2361字,预计学习时长9分钟
图源:unsplash
常在河边行走,鞋子难免会沾湿,有些失误确实难以完全避免。然而protected java 小心陷阱!Java中常犯的10个错误,根据我面试过从初学者到资深技术主管的数十位软件工程师的经验来看,他们在对基础知识的掌握上普遍存在一定的不足。
本文作者结合自身担任技术主管及面试官的实践,梳理了Java开发者常犯的失误,不妨对照一番,看看你是否也犯了类似错误。
1.忽略访问修饰符
尽管情况显得有些令人费解,然而,那些候选人确实频繁地忽略了Java语言中protected关键字所定义的访问权限范围。这或许是由于在面试环节中他们承受了极大的压力和紧张感,导致他们往往只能正确回答其中的一部分。
· 可从子类访问protected字段、方法和构造函数。
可以从同一包内获取对protected属性、函数以及构造方法的访问权限。
此外,包的作用域赋予开发人员编写测试的能力,他们能够通过测试路径访问受保护的方法。因此,忽略这一特性在面试中就相当于承认自己未曾编写过任何测试代码!
图源:unsplash
2.字符串连接
在处理众多字符串或较大的字符串时,连接操作可能会造成大量内存的无效消耗。
该示例中涉及到了构建多个StringBuilder和String实例:具体而言,是10,000,000个StringBuilder对象以及10,000,001个String对象。
解释
先退一步,看看发生了什么。
在执行字符串相加操作,即使用加号进行拼接时,系统会首先构建一个临时对象,该对象承载了拼接后的内容,随后再将这个内容赋值给目标变量。
在上面的示例中,总共生成了三个对象,其中两个对象用于承载文本信息,而第三个对象则负责建立连接。具体来说,第一个对象是字符串result的复制品,而第二个对象则附加了文本“world!”。由于字符串是不可变的特性protected java,这种形式的字符串拼接操作得以顺利完成。
然而,编译器具备足够的智能,能够将代码转换成以下形式(尽管Java 9及更高版本不适用,因为它们采用了StringContactFactory,但最终效果相当接近):
此次优化去除了中间的连接元素,内存资源被两个字符串文本以及一个StringBuilder所占用。总体来看,字符串对象的数量已从O(n²)降至O(n)。
回到第一个示例,编译器对代码的优化如下:
编译器仅对内部链接进行了优化,然而这样做会导致大量StringBuilder和String对象的生成!正确的字符串连接方式如下,仅需使用一个StringBuilder和一个String即可。
3.没有使用equals()
若你惯于运用比较运算符而非调用equals()方法,此一做法实需调整,否则protected java,所得结果恐将出乎意料。
解释
在对比两个字符串或任何其他对象时,应避免使用等号。等号仅能对两个操作数的对象引用(即内存地址)进行比对,而非对它们的内容进行比较。
在上述案例里,字符串无法激活字符串驻留功能,因为它的存储位置与变量x不一致。
4.返回null
笔者已经发现了很多次这样的方法:
当遇到返回值为null的情况时,这迫使调用者必须对返回结果进行空值判断;在这种情形下,若数据项不存在,调用者将得到一个空的数据列表。
开发人员普遍期望在特定情况下返回异常或特定对象(例如,一个空列表),若不然,依赖该代码的应用程序可能会遭遇空指针异常的问题。
5.密码为字符串
将用户所提供的密码保存在字符串形式的对象里,这构成了一种安全隐患,因为字符串形式的数据容易遭受内存层面的攻击。
应当采用字符数组,正如JPasswordField和Password4j所执行的操作。然而,在涉及Web应用程序的讨论中,多数Web容器会将纯文本密码以String的形式从HttpServletRequest对象中传递,导致开发者对此几乎束手无策。
图源:unsplash
解释
Java虚拟机(JVM)负责将字符串缓存并存储,这些字符串原先位于PermGen空间(在Java 8之前)或堆空间中。无论在哪种存储位置,字符串的缓存值只有在垃圾回收过程执行完毕后才会被清除。因此,我们无法预知某个特定值究竟会在何时从字符串池中被移除,这是因为垃圾收集器的运行机制具有不确定性。
还有一个问题需要关注,那就是String对象是不可变的特性使得我们无法直接将其删除。相对而言,char[]数组则是可变的,处理完毕后我们可以通过将每个元素替换为0的方式来将其清除。利用这一方法,攻击者只能在内存中发现一片全为0的数组,而非原始的文本密码。
6.传递null
传递空值意味着,人们自然地假定被调用的代码能够处理空值。若非如此,程序必然会出现空指针异常。
此外,直接传递空值会让程序变得愈发杂乱无章。以下是一个典型的例子:
在调用init()函数的过程中,并未发现任何可用的User对象。既然如此protected java,为何还要执行一个旨在对User对象进行操作的函数呢?若grantAccessToUser()函数中的特定逻辑确实必要,那么理应从其他函数中提取该逻辑并加以应用,而不是仅仅传递一个null值。
7.Heavy methods
以下示例可能会导致系统性能损失:
Pattern.compile()这一函数资源消耗很大,因此不宜在每次进行字符串与特定模式的匹配验证时频繁调用。
解释
通过Pattern.compile()进行模式预编译,可以转换成更高效的内存表示,从而在匹配时提升速度。然而,这一过程对计算资源的需求较高。
提升系统效能的常见策略之一,便是将Pattern对象存储于静态字段中进行缓存,具体做法如下所示:
在每次调用占用资源巨大的无状态对象时,务必采用该方案。
8.迭代时处理集合
这段代码将抛出
并发修改异常。
解释
在迭代过程中,若从列表中移除某项内容,可能会导致迭代器运行异常,诸如遗漏某些元素、重复遍历某些元素,甚至出现越界等问题。这种情况在众多数据结构中较为常见,因而使得它们在执行过程中更容易引发异常。
导致并发修改异常的原因。
使用底层数组迭代器:
9.使用“返回码”而不是抛出异常
在某种程度上,开发者们将异常视为不吉之兆,故而他们更倾向于创作那些会输出异常数值的函数,比如负一或者“C_ERR”这样的标识。
这种情况非常适合定义一个自定义的异常。例如,可以这样表述:这正是一个设立专属异常的典型场合。通过这种方式,我们可以将示例进行如下转换:
正如所见,代码的易读性和维护性显著增强。使用者仅需查阅DeviceStartException的具体信息,无需逐一解析每一个返回码。
10.使用StringBuffer
因为StringBuffer具备同步功能,所以在这个示例中会导致内存消耗显著增加。在这样的复杂环境中,读取器可能会误以为某些并非必要的同步操作是必须进行的。
项目中若涉及StringBuffer,可能源于某些遗留的API(即Java5之前版本)的依赖,而非代码在并发环境下追加String的需求。相较之下,应考虑使用StringBuilder:它自Java5引入以来,所有操作均未进行同步处理。
这仅仅是我在面试及参与项目活动过程中所观察到的若干失误,至于面向对象编程中的潜在问题,如设计陷阱、不当的设计模式、过度设计以及内存泄漏等,尚未被提及。
图源:xkcd
若你面临这些困境,那便是改换编程习惯的时候了。这样的转变并不复杂,避开这些误区能够丰富开发者的技能,同时也有助于他们为下一场面试做更充分的准备。
广泛运用诸如SonarQube等静态代码审查工具,这些工具能够揭示实际存在的错误,并凸显潜在的缺陷。
图源:unsplash
保持持续的学习至关重要,这不仅仅涉及语法知识protected java 小心陷阱!Java中常犯的10个错误,更包括对各种编程语言所依托的理论的掌握。通过不断编写和练习代码,可以减少犯错的频率,让小错误不再困扰你。
- 随机文章
- 热门文章
- 热评文章