通常情况下,我们都希望我们的代码是高效和兼容的,但是实际情况下代码中常常含有一些隐藏的坑,只有等出现异常时我们才会去解决它。本文是一篇比较简短的文章,列出了开发人员在编写 Java 程序时常犯的错误,避免线上问题。

1、大量使用 Enum.values

Enum.Values() 的问题在于,按照规范它的返回必须是一个不可变的列表。为了实现这一点,它在每次调用时返回一个带有枚举值的新数组实例。

public enum Fruits {
APPLE, PEAR, ORANGE, BANANA; public static void main(String[] args) {
System.out.println(Fruits.values());
System.out.println(Fruits.values());
}
}
// output
[Lcom.test.Fruits;@7ad041f3
[Lcom.test.Fruits;@251a69d7

它们是内存中的两个独立对象,这好像也没啥事,但是如果在处理大量请求时使用 Fruit.values() 并且机器负载很高,这可能会导致内存升高等问题。

public class Main {
public static final Fruits[] values = Fruits.values(); public static void main(String[] args) {
System.out.println(values);
System.out.println(values);
}
}
// output
[Lcom.wayn.data.elastic.config.Fruits;@4534b60d
[Lcom.wayn.data.elastic.config.Fruits;@4534b60d

如上我们可以通过引入私有静态最终变量 values 来缓存它们来轻松解决此问题。

2、将 Optional 作为方法参数传递

如下代码

LocalDateTime getCurrentTime(Optional<ZoneId> zoneId) {
return zoneId.stream()
.map(LocalDateTime::now)
.findFirst()
.orElse(LocalDateTime.now(ZoneId.systemDefault()));
}

我们传递可选的 zoneId 参数,并根据它的存在来决定是在系统时区中给出时间还是使用指定的时区。但是,这不是正确使用 Optional 的方式。我们应该避免将它们用作参数,而是使用方法重载。

LocalDateTime getCurrentTime(ZoneId zoneId) {
return LocalDateTime.now(zoneId);
} LocalDateTime getCurrentTime() {
return getCurrentTime(ZoneId.systemDefault());
}

如上代码明显更易于阅读和调试。

3、使用字符拼接

Java 中的字符串是不可变的。这意味着一旦创建它们就不再可编辑。 JVM 维护一个字符串池,在创建一个新字符串之前,它调用 String.intern() 方法,该方法从字符串池中返回一个与值匹配的实例(如果存在)。

假设我们想通过连接东西来创建一个长字符串

String longString = "";
longString +="start";
longString +="middle";
longString +="middle";
longString +="middle";
longString +="end";

不久前,我们被告知这是一个非常糟糕的主意,因为Java的旧版本执行以下操作

  • 在第 1 行中,字符串 "start" 被插入到字符串池中,longString 指向它
  • 在第 2 行中,字符串 "startmiddle" 被添加到池中,longString 指向它
  • 在第 3 行,我们有 "startmiddlemiddle"
  • 在第 4 行 "startmiddlemiddlemiddle"
  • 最后,在第 5 行,我们将 "startmiddlemiddlemiddleend" 添加到池中并将 longString 指向它

所有这些字符串都保留在池中并且从不使用,这会浪费大量 RAM。

为了避免这种情况,我们可以使用 StringBuilder

String longString = new StringBuilder()
.append("start")
.append("middle")
.append("middle")
.append("middle")
.append("end")
.toString();

调用 toString 方法时,StringBuilder 仅创建一个字符串,从而为我们保存了最初添加到池中的所有中间字符串。但是,在 Java 5 之后,编译器会自动为我们完成此操作,并且可以安全地使用带有 "+" 的字符串连接。

此规则有一个例外,那就是在循环中进行字符串连接时

String message = "";
for (int i = 0; i < 10; i++) {
message += "msg" + i;
} System.out.println(message);

这段代码不会被 JIT 优化,每次迭代都会将新的字符串插入到字符串池中,这里我们必须使用 StringBuilder

StringBuilder msgB = new StringBuilder();
for (int i = 0; i < 10; i++) {
msgB.append("msg").append(i);
} System.out.println(msgB);

这里还有几件事要注意

即时编译器有时会重新组织代码。

String s = "1" + "2" + "3";

转换成

String s = "123";

从 Java 15 开始,可以使用文本块处理多行字符串:

String sql = """
SELECT * FROM users as u
WHERE u.name = 'John'
AND u.age > 34
""";

4、过度使用原始包装器

考虑以下两个片段

int sum = 0;
for (int i = 0; i < 1000 * 1000; i++) {
sum += i;
}
System.out.println(sum); // ---------------------- Integer sum = 0;
for (int i = 0; i < 1000 * 1000; i++) {
sum += i;
}
System.out.println(sum);

在我的机器上,第一个比第二个快 6 倍。唯一的区别是我们使用包装器 Integer 类。这样做的原因是,在第 3 行中,运行时必须将 sum 变量转换为原始 int(自动拆箱),并且在执行添加后,结果将包装在一个新的 Integer 类中(自动装箱)。这意味着我们创建了 100 万个 Integer 类并执行了 200 万个装箱操作,这解释了速度急剧下降的原因。

仅当需要将包装类存储在集合中时才应使用包装类。但是,未来的 Java 版本将支持原始类型的集合,这将使包装器过时。

5、自己编写哈希函数

当我们想将对象存储在 HashMap 中时,通常会实现对象的哈希函数。该 HashMap 由带有数字的 "桶" 组成,每个哈希码都分配给一个特定的桶。如果存入 "桶" 对象的哈希函数没有正确编写,HashMap 的性能将显着降低。一个写得很好的散列函数将确保所有键的平均分配。

在一般情况下我们需要自己编写哈希函数,但在大多数情况下,使用内置的 Objects.hash(...) 方法就行,该方法为一系列输入值生成哈希代码,生成散列代码的方式就像将所有输入值都放入一个数组中一样,并且通过调用 Arrays.hashCode(Object[]) 对该数组进行散列。

public class Car {
private final String model;
private final Integer year;
private final Instant manufactureDate; public Car(String model, Integer year, Instant manufactureDate) {
this.model = model;
this.year = year;
this.manufactureDate = manufactureDate;
} @Override
public int hashCode() {
return Objects.hash(model, year, manufactureDate);
} @Override
public boolean equals(Object obj) {
// 在实现 hashCode 时,不要忘记实现 equals
}
}

6、使用 java.util.Date

我们甚至应该避免 java.util 中的所有时间类改用 java.time 包。

Date 类已被弃用,原因有很多,它有很多设计缺陷。

  • 它不是无法被修改的
  • 它无法处理时区
  • 充满已弃用但仍在使用的遗留代码

当程序中出现对日期支持的需求时,util 包中的 Date、Calendar 和 rest time 类就出现了。鉴于如上缺陷,程序界有几次修复它们的尝试,但最后他们决定引入一个新的包 java.time。 java.time 包与第三方的 joda.time 非常相似,这意味着我们不需要在使用 joda.time,Jdk8 已经有了内置支持。

我们列出 java.time 中使用的三个最重要的类

LocalDate

表示特定时区的日期(不包括一天中的时间)。

LocalDate.of(2022, 6, 12);
LocalDate.parse("2022-06-12"); // The Date/Time API in Java works with the ISO 8601 format by default, which is (yyyy-MM-dd)
// We can overwrite it like this
LocalDate.parse("2022.06.12", DateTimeFormatter.ofPattern("yyyy.MM.dd"));

LocalDateTime

与 LocalDate 相同,但它有一天中的时间。

LocalDateTime.of(2022, 6, 12, 10, 34, 18);
var dateTime = LocalDateTime.parse("2022-06-23T10:34:18"); // it's easy to get the time in a different zone
dateTime.atZone(ZoneId.of("GMT+2"));

Instant

我最喜欢的。它本质上是 LocalDateTime,但强制使用 UTC 时区。在应用程序中需要处理时区时,最好在所有服务和数据库中使用同一个时区。当使用 Instant 时,一切都变成了 UTC,然后读者可以根据需要将其转换为不同的时区。

// Current time in UTC
Instant.now(); // Note the 'Z' at the end it means UTC
Instant.parse("2022-06-21T12:12:12Z"); // Convert instant to a different time zone
Instant.now().atZone(ZoneId.of("GMT+3"));

简单来说

  • 不要使用日期和日历(或任何与 java.util 相关的日期)
  • 不要使用 joda.time(因为它与 java.time 非常相似)
  • 如果只对某个区域的日期感兴趣,请使用 LocalDate
  • 如果对某个区域的日期和时间感兴趣,请使用 LocalDateTime
  • 如果需要日期时间并且不想处理时区,请使用 Instant

本文翻译自国外论坛 medium,原文地址:https://medium.com/@b.stoilov/things-to-avoid-while-writing-java-cd078e5aa61c

关注公众号【waynblog】每周分享技术干货、开源项目、实战经验、高效开发工具等,您的关注将是我的更新动力!

编写Java代码时应该避免的6个坑的更多相关文章

  1. 【eclipse jar包】在编写java代码时,为方便编程,常常会引用别人已经实现的方法,通常会封装成jar包,我们在编写时,只需引入到Eclipse中即可。

    Eclipse中导入外部jar包 在编写java代码时,为方便编程,常常会引用别人已经实现的方法,通常会封装成jar包,我们在编写时,只需引入到Eclipse中即可. 工具/原料 Eclipse 需要 ...

  2. 像写C#一样编写java代码

    JDK8提供了非常多的便捷用法和语法糖,其编码效率几乎接近于C#开发,maven则是java目前为止最赞的jar包管理和build工具,这两部分内容都不算多,就合并到一起了. 愿编写java代码的过程 ...

  3. 如何更规范化编写Java 代码

    如何更规范化编写Java 代码 Many of the happiest people are those who own the least. But are we really so happy ...

  4. 如何更规范化的编写JAVA 代码

    如何更规范的编写JAVA代码 一.MyBatis 不要为了多个查询条件而写 1 = 1 当遇到多个查询条件,使用where 1=1 可以很方便的解决我们的问题,但是这样很可能会造成非常大的性能损失, ...

  5. myeclipse 编写java代码提示 dead code 原因

    经常使用MyEclipse或Eclipse编辑器编写java代码的程序员,可能经常遇到一个黄线警告提示:dead code:一般程序员遇到这些问题都会置之不理,反正也不影响程序的编译执行.对,这不是b ...

  6. Jmeter自定义编写Java代码调用socket通信

    一.前言 最近需要测试一款手机游戏的性能,找不到啥录制脚本的工具,然后,另外想办法.性能测试实际上就是对服务器的承载能力的测试,和各种类型的手机客户端没有啥多大关系,手机再好,服务器负载不了,也不能够 ...

  7. 通过编写Java代码让Jvm崩溃

    在书上看到一个作者提出一个问题"怎样通过编写Java代码让Jvm崩溃",我看了之后也不懂.带着问题查了一下,百度知道里面有这样一个答案: 1 package jvm; 2 3 pu ...

  8. 在执行java代码时,设置了断点,然后莫名的没执行完方法内的代码就结束了,此刻一般在出错处代码用try,catch包括起来

    在执行java代码时,设置了断点,然后莫名的没执行完方法内的代码就结束了,此刻一般在出错处代码用try,catch包括起来就能看到是什么异常了,记住try,catch语句的作用

  9. MyEclipse 编写 JSP 代码时很卡的解决办法

    在网上看到很多方法,都是尝试过,个人感觉都没有说到重点,所以收效甚微. 后来自己总结了一下: 我们都是习惯在MyEclipse 工具,双击jsp 文件打开进行编辑.这时,工具会打开窗口的 Previe ...

  10. Java mac 上编写Java代码

    看视频学JAVA,不想下载 notepad++之类的,虽然知道mac有内嵌的JAVA sdk ,但是还是不知道怎么编写,今天终于编写了我的第一个JAVA程序,还是以 Hello World 开始吧 1 ...

随机推荐

  1. 2023年奔走的总结---吉特日化MES 项目趣事 篇一

    终于又到了2023年的年末了,既有兴奋也有遗憾,兴奋的是这难熬的一年马上就要过去了,遗憾的是今年好像没有做成太多的事情.差不多180次的大交通记录,也再次刷新了奔走的记录,忙忙碌碌兜兜转转又去了不少地 ...

  2. [CF1748D] ConstructOR

    题目描述 You are given three integers $ a $ , $ b $ , and $ d $ . Your task is to find any integer $ x $ ...

  3. Oracle-lsnrctl监听进程控制

    LSNRCTL> help The following operations are available An asterisk (*) denotes a modifier or extend ...

  4. NetCore高级系列文章04---async、await原理揭秘

    async.await本质上是C#提供的语法糖,编译器编译后是状态机的调用. 先看如下的一段代码,要main方法中调用了三个await方法 将此dll进行反编译为4.0的代码如下: 可见到两个Main ...

  5. MS17-010(永恒之蓝)漏洞分析与复现

    一.漏洞简介1.永恒之蓝介绍:永恒之蓝漏洞(MS17-010),它的爆发源于 WannaCry 勒索病毒的诞生,该病毒是不法分子利用NSA(National Security Agency,美国国家安 ...

  6. 通过 KernelUtil.dll 劫持 QQ / TIM 客户端 QQClientkey / QQKey 详细教程(附源码)

    前言 由于 QQ 9.7.20 版本后已经不能通过模拟网页快捷登录来截取 QQClientkey / QQKey,估计是针对访问的程序做了限制,然而经过多方面测试,诸多的地区.环境.机器也针对这种获取 ...

  7. ThreadLocal真的会造成内存泄漏吗?

    ThreadLoca在并发场景中,应用非常多.那ThreadLocal是不是真的会造成内存泄漏?今天给大家做一个分享,个人见解,仅供参考. 1.ThreadLocal的基本原理 简单介绍一下Threa ...

  8. apex的安装过程及问题解决(ModuleNotFoundError:No module named 'packaging')

    最近在复现代码时遇到了如下问题,在此记录一下问题的解决过程: 步骤一 git clone https://github.com/NVIDIA/apex.git 步骤二 cd apex 步骤三 pip ...

  9. PLG SaaS 案例:如何实践外链自动增长策略?

    首先任何的商业逻辑,光流量增长,没法变现是没用的. 就像博客群发提效工具,得有对应的用户,更得有对应付费用户群体的画像.剩下的就是靠增长,被动让他们找到你的产品,用产品解决他们痛点,他们自然而然会付费 ...

  10. 神经网络基础篇:详解二分类(Binary Classification)

    二分类 注:当实现一个神经网络的时候,通常不直接使用for循环来遍历整个训练集(编程tips) 举例逻辑回归 逻辑回归是一个用于二分类(binary classification)的算法.首先从一个问 ...