Java 业务开发常见错误 100 例
朱晔
贝壳金服资深架构师
立即订阅
4513 人已学习
课程目录
已更新 15 讲 / 共 37 讲
0/4登录后,你可以任选4讲全文学习。
开篇词 (1讲)
开篇词 | 业务代码真的会有这么多坑?
免费
代码篇 (12讲)
01 | 使用了并发工具类库,线程安全就高枕无忧了吗?
02 | 代码加锁:不要让“锁”事成为烦心事
03 | 线程池:业务代码最常用也最容易犯错的组件
04 | 连接池:别让连接池帮了倒忙
05 | HTTP调用:你考虑到超时、重试、并发了吗?
06 | 20%的业务代码的Spring声明式事务,可能都没处理正确
07 | 数据库索引:索引并不是万能药
08 | 判等问题:程序里如何确定你就是你?
09 | 数值计算:注意精度、舍入和溢出问题
10 | 集合类:坑满地的List列表操作
11 | 空值处理:分不清楚的null和恼人的空指针
12 | 异常处理:别让自己在出问题的时候变为瞎子
不定期加餐 (2讲)
加餐1 | 带你吃透课程中Java 8的那些重要知识点(上)
加餐2 | 带你吃透课程中Java 8的那些重要知识点(下)
Java 业务开发常见错误 100 例
登录|注册

08 | 判等问题:程序里如何确定你就是你?

朱晔 2020-03-26
你好,我是朱晔。今天,我来和你聊聊程序里的判等问题。
你可能会说,判等不就是一行代码的事情吗,有什么好说的。但,这一行代码如果处理不当,不仅会出现 Bug,还可能会引起内存泄露等问题。涉及判等的 Bug,即使是使用 == 这种错误的判等方式,也不是所有时候都会出问题。所以类似的判等问题不太容易发现,可能会被隐藏很久。
今天,我就 equals、compareTo 和 Java 的数值缓存、字符串驻留等问题展开讨论,希望你可以理解其原理,彻底消除业务代码中的相关 Bug。

注意 equals 和 == 的区别

在业务代码中,我们通常使用 equals 或 == 进行判等操作。equals 是方法而 == 是操作符,它们的使用是有区别的:
对基本类型,比如 int、long,进行判等,只能使用 ==,比较的是直接值。因为基本类型的值就是其数值。
对引用类型,比如 Integer、Long 和 String,进行判等,需要使用 equals 进行内容判等。因为引用类型的直接值是指针,使用 == 的话,比较的是指针,也就是两个对象在内存中的地址,即比较它们是不是同一个对象,而不是比较对象的内容。
这就引出了我们必须必须要知道的第一个结论:比较值的内容,除了基本类型只能使用 == 外,其他类型都需要使用 equals
取消
完成
0/1000字
划线
笔记
复制
© 版权归极客邦科技所有,未经许可不得传播售卖。 页面已增加防盗追踪,如有侵权极客邦将依法追究其法律责任。
该试读文章来自付费专栏《Java 业务开发常见错误 100 例》,如需阅读全部文章,
请订阅文章所属专栏。
立即订阅
登录 后留言

精选留言(16)

  • Darren
    稍微补充一点,可能因为篇幅的原因,老师没提到,关于equals其实还有一个大坑,equals比较的对象除了所谓的相等外,还有一个非常重要的因素,就是该对象的类加载器也必须是同一个,不然equals返回的肯定是false;之前遇到过一个坑:重启后,两个对象相等,结果是true,但是修改了某些东西后,热加载(不用重启即可生效)后,再次执行equals,返回就是false,因为热加载使用的类加载器和程序正常启动的类加载器不同。关于类加载器部分,JDK 9 之前的 Java 应用都是由「启动类加载器」、「扩展类加载器」、「应用程序类加载器」这三种类加载器互相配合来完成加载的,如果有需要还可以加入自定义的类加载器来进行拓展;JDK 9 为了模块化的支持,对双亲委派模式做了一些改动:扩展类加载器被平台类加载器(Platform ClassLoader)取代。平台类加载器和应用程序类加载器都不再继承自 java.net.URLClassLoader,而是继承于 jdk.internal.loader.BuiltinClassLoader。具体细节可以自行搜索。

    现在回答下问题:
    第一个问题:
    instanceof进行类型检查规则是:你是该类或者是该类的子类;
    getClass获得类型信息采用==来进行检查是否相等的操作是严格的判断。不会存在继承方面的考虑;

    第二个问题:
    HashSet本质上就是HashMap的key组成的不重复的元素集合,contains方法其实就是根据hashcode和equals去判断相等的
    TreeSet本质TreeMap的key组成的,数据结构是红黑树,是自带排序功能的,可以在放入元素的时候指定comparator(比较器),或者是放入的元素要实现Comparable接口后元素自己实现compareTo方法,contains方法是根据比较器或者compareTo去判断相等

    作者回复: 👍🏻👍🏻👍🏻
    这位同学作为本课课代表 😀

    2020-03-26
    14
    40
  • 👽
    2 . HashSet 底册是HashMap。TreeSet底层是TreeMap
    HashSet就是使用HashMap调用equals,判断两对象的HashCode是否相等。
    TreeSet因为是一个树形结构,则需要考虑数的左右。则需要通过compareTo计算正负值,看最后能否找到compareTo为0的值,找到则返回true。

    简单来说,TreeSet底层使用compareTo方法比较,HashSet底层使用hash值比较。

    作者回复: 👍🏻

    2020-03-26
    5
  • Sun
    老师的课程,真的是干货,每天凌晨更新完看一遍,早上上班前在看一遍,感受都不一样,期待出更多干货,共同进步

    作者回复: 设计篇和安全篇还会有更丰富的内容,跟紧脚步,细细品味

    2020-03-26
    4
  • Z_CHP
    老师的每一篇文章都是满满的干货呀,手动点赞👍👍👍

    作者回复: 觉得好可以多转发分享

    2020-03-26
    3
  • 👽
    问题1:
    Father father = new Father();
        Son son = new Son();
        System.out.println(son.getClass()==father.getClass());
        System.out.println(son instanceof Father);
    打印结果
    false
    true

    区别在此,getClass更加严格,而instanceof 子类instanceof 父类,也是true
    2020-03-26
    3
  • 东方奇骥
    看到这节,说起Lombok,老师觉得Lombok 适合用于生产环境吗?之前一直都是自己业余练习使用,但是工作中项目都还是没有使用。

    作者回复: 只要你理解它各种注解会生成怎样的代码,就没问题

    2020-03-26
    3
  • Huodefa_0426
    老师,文中你说:在启动程序时设置 JVM 参数 -XX:+PrintStringTableStatistics,程序退出时可以打印出字符串常量表的统计信息。调用接口后关闭程序,输出如下。我设置了关闭程序怎么没看见输出的信息,是输出在控制台还是在日志文件中?如果是文件 是哪个文件?

    作者回复: 控制台,确保参数生效了

    2020-03-26
    2
  • pedro
    1楼的回答已经趋于完美,我也翻了一下 JDK 源码,HashSet 的本质是 HashMap,会通过 hash 函数来比较值,TreeSet 的本质是 TreeMap 会通过 compareTo 比较。
    至于类加载器的问题,我想这个不好复现,有没有楼下的小伙伴补充一下的。

    作者回复: 👍🏻

    2020-03-26
    2
    2
  • 失火的夏天
    hashset和treeSet从根本上来说,没什么关系,只是有个N代以前的祖宗了,哈哈,一个玩hash,一个玩comparator。一个底层是散列表,一个底层是红黑树。
    2020-03-28
    1
  • yihang
    另外对于 intern 也有它的用武之处,据说 twitter 使用它减少重复地址(字符串)大量节约了内存
    2020-03-28
    1
  • yihang
    补充一点,浮点数的==比较也有坑,跟浮点数小数精度有关

    作者回复: 是的,这个下篇提到了

    2020-03-28
    1
  • justin
    老师您好,就是关于这个例子,加了intern()会在常量池驻留,导致每个bucket size变得很大。如果没有加intern()方法的时候,Average bucket size就变得很低,测试的是0.276,这个帮忙解释下
    @GetMapping("internperformance")
    public int internperformance(@RequestParam(value = "size", defaultValue = "10000000")int size) {
        //-XX:+PrintStringTableStatistics
        //-XX:StringTableSize=10000000
        long begin = System.currentTimeMillis();
        list = IntStream.rangeClosed(1, size)
                .mapToObj(i-> String.valueOf(i).intern())
                .collect(Collectors.toList());
        log.info("size:{} took:{}", size, System.currentTimeMillis() - begin);
        return list.size();
    }

    作者回复: 因为没有进常量池

    2020-04-02
  • EchosEcho
    getclass需要具体一种类型才能做比较,instanceof可以在子类和父类间实现equals方法
    2020-03-29
  • 努力奋斗的Pisces
    1.instanceof 涉及到继承的子类是都属于父类的判断
    2.treeSet 是 treeMap的实现,使用了compareTo来比判断是否包含
    2020-03-29
  • Collections.sort(list);
    也调用了compareTo吧,所以返回下标index2是不是应该等于0?
    2020-03-27
  • Geek_3b1096
    上班前看一遍+1
    2020-03-27
收起评论
16
返回
顶部