前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >022:如果要将对象用作Map中的key,需要注意什么

022:如果要将对象用作Map中的key,需要注意什么

作者头像
阿杜
发布2019-05-15 14:02:53
1.4K0
发布2019-05-15 14:02:53
举报
文章被收录于专栏:阿杜的世界

参考答案

如果将对象作为Map中的key,需要是实现该对象的equals方法和hashCode方法;现在一般通过lombok可以简单得实现,并且可以选择具体需要哪些字段参与equals和hashCode方法的计算。

知识点梳理

Java类型系统中分为基础类型和引用类型,引用类型中所有的对象都有一个父类——java.lang.Object。基类Object提供了一些可扩展的方法:equals、hashCode、toString、clone和finalize。开发者在覆盖这些方法的时候,要遵循一定的约定,如果使用不当就会造成bug。

equals方法

如果类有自己的“逻辑相等”概念,而且父类的equals方法又无法满足期望的时候,就应该覆盖equals方法。在开发中我们有时候会将一个自定义的对象作为map中的key,或者将一个自定义的对象加入到集合中,这时候就需要覆盖equals方法。

hashCode方法

覆盖equals方法的时候,要同时覆盖hashCode方法。这里一起看一个案例。假设我定义一个用户信息类,代码如下所示:

代码语言:javascript
复制
import lombok.AllArgsConstructor;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;

@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
@EqualsAndHashCode(exclude = {"phoneNumber", "nickName", "sign", "address", "avatarUrl"})
public class UserInfo {

    private long userId;

    private String phoneNumber;

    private String nickName;

    private String sign;

    private String address;

    private String avatarUrl;
}

这里使用@EqualsAndHashCode注解生成equals和hashCode方法,并排除了除userId以外的其他字段,表示该用户信息对象的唯一性只跟userId这个字段有关。如果该类是继承了某个自定义的类,需要考虑父类的字段,那么还可以使用@EqualsAndHashCode中的callSuper字段,设置为true就会连父类的字段一起考虑,默认是只考虑当前类中的字段。关注lombok的用法,这里不展开讲了。

假设有一个场景,需要过滤确保某个列表里的用户对象是没有重复的,那么我们就需要确定用户对象的唯一id是什么?在这里是userId,可以使用集合来进行重复对象的过滤,代码如下所示:

代码语言:javascript
复制
import java.util.HashSet;
import java.util.Set;

public class ObjectExample {
    public static void main(String[] args) {
        UserInfo userInfo1 = new UserInfo();
        userInfo1.setAddress("abc");
        userInfo1.setAvatarUrl("http://test.com");
        userInfo1.setNickName("aaa");
        userInfo1.setPhoneNumber("1768909876");
        userInfo1.setSign("");
        userInfo1.setUserId(289976L);

        UserInfo userInfo2 = new UserInfo();
        userInfo2.setAddress("abc");
        userInfo2.setAvatarUrl("http://test.com");
        userInfo2.setNickName("aaa");
        userInfo2.setPhoneNumber("1768909876");
        userInfo2.setSign("");
        userInfo2.setUserId(289978L);

        UserInfo userInfo3 = new UserInfo();
        userInfo3.setAddress("abcddddd");
        userInfo3.setAvatarUrl("http://test2222.com");
        userInfo3.setNickName("aaadddd");
        userInfo3.setPhoneNumber("176890999996");
        userInfo3.setSign("xxxxx");
        userInfo3.setUserId(289978L);

        Set<UserInfo> userInfoSet = new HashSet<UserInfo>();
        userInfoSet.add(userInfo1);
        userInfoSet.add(userInfo2);
        userInfoSet.add(userInfo3);

        System.out.println(userInfoSet.size());
    }
}

这个案例的执行结果也很明显,输出的值是:2。

toString方法

所有自定义的类都要覆盖toString方法,我会使用lombok的@ToString注解来帮我生成toString方法。使用toString方法可以将对象的字段都以可读的形式展示出来。这样在打印日志的时候,要打印某个对象,就不会打印出一个对象的地址,类似于UserInfo@1768b4

clone方法

我在开发中没有用过这个方法。要完成对象的拷贝,只需要区分自己是要深拷贝还是浅拷贝。一般我会使用拷贝构造器或静态工厂方法作为替代方案。

代码语言:javascript
复制
public UserInfo(UserInfo userInfo);

代码语言:javascript
复制
public static UserInfo newInstance(UserInfo userInfo)

finalize方法

根据Java问到,finalize方法被设计出来是用于释放非Java资源,但是由于jvm的运行机制导致有很大可能不会调用到对象的finalize方法,或者调用的时机和顺序是不确定的,所以这个方法并没有达到其设计目标。Java9中这个方法已经被废弃了,不过现在很多面试还是会问到这个方法背后的原理,需要理解几个概念:

  • 自定义类的对象,就是我们自定义的类,该类覆盖了finalize方法
  • Finalizer对象,在新建一个覆盖了finalize方法的类的对象的时候,就会伴生一个Finalizer对象,并将该对象加入到一个双向列表中
  • 双向列表:ReferenceQueue<Object> queue,Finalizer对象创建出来后,就会被加入到这个双向列表
  • FinalizerThread对象,Finalizer线程是3号线程,它的作用就是不断从上面哪个队列中取Finalizer对象,然后调用它的runFinalzier方法。

在Java应用中,如果对finalize方法使用不合理,有时候会引发一类问题——Finalizer队列过长,导致一些对象的finalze方法调用延迟,如果程序在这个方法中进行了某些对时间敏感的资源的释放,那么就会有问题。

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2019.05.11 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 参考答案
  • 知识点梳理
    • equals方法
      • hashCode方法
        • toString方法
          • clone方法
            • finalize方法
            领券
            问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档