【全球播资讯】编写Java代码时应该避免的6个坑

2023-06-01 11:29:56 来源:博客园 分享到:

通常情况下,我们都希望我们的代码是高效和兼容的,但是实际情况下代码中常常含有一些隐藏的坑,只有等出现异常时我们才会去解决它。本文是一篇比较简短的文章,列出了开发人员在编写 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) {    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 thisLocalDate.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 zonedateTime.atZone(ZoneId.of("GMT+2"));

Instant

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

// Current time in UTCInstant.now();// Note the "Z" at the end it means UTCInstant.parse("2022-06-21T12:12:12Z");// Convert instant to a different time zoneInstant.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个坑

来源:博客园 2023-06-01 11:29:56

实地探访“固废环保类”公募REIT! 世界热点评

来源:中国基金报 2023-06-01 10:57:56

光大银行重庆分行全面启动“普及金融知识 守住‘钱袋子’”暨“普及金融知识万里行”活动

来源:华龙网-新重庆客户端 2023-06-01 10:31:39

第六届“创业北京”创业创新大赛启动,首设文化创意赛道 环球快资讯

来源:中国科普网 2023-06-01 09:36:40

【天天聚看点】8车续航大横评成绩公布,打破1000km极限只有一家?

来源:盖世汽车网 2023-06-01 08:48:01

世界动态:发展保障性租赁住房应规划先行

来源:中国经济网 2023-06-01 07:55:48

4个工具,让 ChatGPT 如虎添翼!

来源:程序员客栈 2023-06-01 06:55:32

每日热议!厦门港务(000905):5月31日北向资金减持39.41万股

来源:证券之星 2023-06-01 05:13:56

高一年级英语语法填空专练(人教版英语sky几年级学的)

来源:互联网 2023-06-01 03:13:07

打好“四张牌” 推动“四化”建设 全力打造平安雅瑶

来源:广州公安 2023-06-01 01:46:10

阿斯报:曼联曼城切尔西拜仁皇萨都有意维加,巴萨处于领跑位置

来源:直播吧 2023-06-01 00:26:19

二至丸的功效与作用禁忌_二至丸的功效与作用|世界快资讯

来源:互联网 2023-05-31 23:40:22

环球热议:新款比亚迪元Pro上市,续航突破400km,售价9.58万起

来源:懂车帝 2023-05-31 22:47:38

焦点观察:君圣泰递表港交所,专注肝糖共治

来源:福布斯 2023-05-31 21:48:27

阵亡将士纪念日这天,美国3名海军陆战队员被40多名青少年围殴,其中9人被捕|今日关注

来源:极目新闻 2023-05-31 20:56:02

【独家焦点】珀莱雅大宗交易成交5.36万股 成交额649.23万元

来源:证券时报网 2023-05-31 20:21:31

【短讯】又见海选!海南面向全国选调省联社主任,要求“了解金融科技发展趋势”,原主任拟升任理事长-新要闻

来源:开利财经 2023-05-31 19:40:48

唐山德龙整体关停退出 当前快播

来源:我的钢铁网 2023-05-31 18:32:20

思想的碰撞:刘作法学演讲与对话选_天天快播报

来源:互联网 2023-05-31 15:10:52

高考第一天穿什么颜色的衣服

来源:腾阅网 2023-05-31 15:04:43

持续发力!网新恒天中标某大型股份制银行资金交易系统升级项目

来源:浙大网新官微 2023-05-31 13:39:56

头条焦点:会计师资格证需要考哪些科目(会计师资格证)

来源:互联网 2023-05-31 13:44:06

世界资讯:冀中能源股份有限公司大淑村矿四举措精准施策助力矿井安全发展

来源:魏玮 2023-05-31 13:06:16

金津老鸭粉丝汤_这样做简直太好吃了

来源:互联网 2023-05-31 13:01:51

山东电力市场绿电零售套餐上线试运行 天天热点

来源:山东能监办 2023-05-31 12:25:41

传媒板块异动拉升,南方传媒涨停 环球报道

来源:众赢智投 2023-05-31 12:36:36

【天天时快讯】A股早评:三大指数集体低开 沪指跌0.3% 电力股回调

来源:格隆汇 2023-05-31 11:32:34

深圳地王大厦的图片_这张照片是在深圳福田拍的前面是地王大厦

来源:互联网 2023-05-31 11:26:53

中国黄金:5月30日融券卖出4.78万股,融资融券余额5.72亿元 环球今热点

来源:证券之星 2023-05-31 10:45:39

娱乐圈修罗场又来了?-环球快资讯

来源:手机网易网 2023-05-31 10:23:24

Copyright   2015-2022 北方知识产权网 版权所有  备案号:京ICP备2021034106号-50   联系邮箱: 55 16 53 8@qq.com