通常情况下,我们都希望我们的代码是高效和兼容的,但是实际情况下代码中常常含有一些隐藏的坑,只有等出现异常时我们才会去解决它。本文是一篇比较简短的文章,列出了开发人员在编写 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. [ABC262E] Red and Blue Graph

    Problem Statement You are given a simple undirected graph with $N$ vertices and $M$ edges. The verti ...

  2. 华企盾科技:智能AI自动化研判分析服务系统概述

    由中企网安全资子公司北京华企盾科技有限责任公司开发的<智能AI自动化研判分析服务系统>,获得国家版权局颁发的计算机软件著作权登记证书. 智能AI自动化研判分析服务系统是基于人工智能.大数据 ...

  3. linux云服务器病毒处理

    阿里云服务器被挖矿病毒入侵,CPU跑满,需要先停止相关进程.为了根除病毒,还需要 解决系统的后门问题(这部分听从阿里云工程师的建议备份系统盘快照后重置系统,再通过快照恢复数据) 然而重置系统后依然存在 ...

  4. 为什么Java中“1000==1000”为false,而”100==100“为true?

    在日常编程中,我们经常遇到一些看似简单却隐藏着复杂逻辑的问题. 比如,你是否想过为什么在 Java 中表达式1000==1000会返回 false,而 100==100 却返回 true 呢? Int ...

  5. kubernetes安装(一)

    参考: https://www.cnblogs.com/liuyangQAQ/p/17299871.html 部署组件包 名称 安装包 kubeadm集群组件 kubelet-1.20.9 kubea ...

  6. 网站优化之favicon.ico

    本文于2015年底完成,发布在个人博客网站上. 考虑个人博客因某种原因无法修复,于是在博客园安家,之前发布的文章逐步搬迁过来. 背景 某一天在办公室分析产品首页加载速度时,无意中从Chrome浏览器的 ...

  7. 2023-10-28:用go语言,给定一个n*m的二维矩阵,每个位置都是字符, U、D、L、R表示传送带的位置,会被传送到 : 上、下、左、右, . 、O分别表示空地、目标,一定只有一个目标点, 可以

    2023-10-28:用go语言,给定一个n*m的二维矩阵,每个位置都是字符, U.D.L.R表示传送带的位置,会被传送到 : 上.下.左.右, . .O分别表示空地.目标,一定只有一个目标点, 可以 ...

  8. 文心一言 VS 讯飞星火 VS chatgpt (63)-- 算法导论6.5 2题

    文心一言 VS 讯飞星火 VS chatgpt (63)-- 算法导论6.5 2题 二.试说明 MAX-HEAP-INSERT(A,10)在堆A=(15,13,9,5,12,8,7,4,0,6,2,1 ...

  9. 格网DEM生成不规则三角网TIN

    目录 概述 详论 1️⃣数据准备 2️⃣转换算法 3️⃣TIN构建 4️⃣具体实现 5️⃣实验结果 参考 概述 在GIS(地理信息科学)中,地形有两种表达方式,一种是格网DEM,一种是不规则三角网TI ...

  10. 你的Parquet该升级了:IOException: totalValueCount == 0问题定位之旅

    摘要:使用Spark SQL进行ETL任务,在读取某张表的时候报错:"IOException: totalValueCount == 0",但该表在写入时,并没有什么异常. 本文分 ...