Android联系人APP中如何去除列表内重复联系人?
我看到你在开发联系人APP时遇到了重复联系人去重的问题,尤其是像98*******33和+9198********33这类格式不同但实际是同一号码的情况,而且你尝试的Set和列表检查方法都没生效。问题主要出在手机号未标准化和ContactVO的equals/hashCode方法逻辑错误这两个点上,咱们一步步来解决:
一、当前代码的核心问题
1. equals方法逻辑完全写反了
你的equals方法里,当两个ContactVO的号码和姓名匹配时,居然返回false,这完全搞反了equals的语义——equals应该在两个对象相等时返回true,否则返回false。另外,字符串比较用==是错误的,应该用equals()或者equalsIgnoreCase(),==比较的是对象引用,不是字符串内容。
2. 手机号未做标准化处理
不同格式的同一手机号(比如带国家码、空格、符号)会被当成不同的字符串,比如+9198****33和98****33在未处理前是两个不同的字符串,自然无法被识别为重复。
3. contains方法使用错误
你用contactVOList.contains(phoneNumber)来判断列表是否包含某个手机号,但列表里存的是ContactVO对象,不是字符串,这个判断永远会返回false,导致所有联系人都被添加进去。
4. hashCode方法依赖未标准化的号码
hashCode是Set判断对象是否重复的关键之一,如果两个实际相同的联系人因为号码格式不同导致hashCode不同,Set也无法识别它们是重复的。
二、修复步骤和代码示例
1. 先写一个手机号标准化工具方法
这个方法会把手机号转换成统一格式,比如去掉所有非数字字符,或者针对印度号码(+91)去掉国家码,只保留核心号码:
private String normalizePhoneNumber(String phoneNumber) { if (phoneNumber == null) return ""; // 去掉所有非数字字符 String cleaned = phoneNumber.replaceAll("[^0-9]", ""); // 如果是印度号码,去掉开头的91(因为+91去掉非数字后就是91开头) if (cleaned.startsWith("91") && cleaned.length() == 12) { return cleaned.substring(2); } return cleaned; }
2. 修复ContactVO的equals和hashCode方法
现在基于标准化后的手机号来判断两个ContactVO是否相等,同时修正equals的逻辑:
public class ContactVO { private String contactImage; private String contactName; private String contactNumber; private boolean clicked; private String id; // 构造方法、getter/setter保持不变,建议变量名遵循Java小驼峰规范 // 添加获取标准化号码的方法 private String getNormalizedContactNumber() { if (contactNumber == null) return ""; String cleaned = contactNumber.replaceAll("[^0-9]", ""); if (cleaned.startsWith("91") && cleaned.length() == 12) { return cleaned.substring(2); } return cleaned; } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null || getClass() != obj.getClass()) return false; ContactVO contactVO = (ContactVO) obj; // 基于标准化后的号码判断是否为同一联系人 return getNormalizedContactNumber().equals(contactVO.getNormalizedContactNumber()); } @Override public int hashCode() { // 用标准化后的号码生成hashCode return getNormalizedContactNumber().hashCode(); } }
3. 修正联系人列表的填充逻辑
现在你可以直接用Set来存储联系人,利用Set的特性自动去重:
Cursor phones = getContext().getContentResolver().query( ContactsContract.CommonDataKinds.Phone.CONTENT_URI, null, null, null, null); Set<ContactVO> contactSet = new HashSet<>(); // 用Set自动去重 if (phones != null) { while (phones.moveToNext()) { String name = phones.getString(phones.getColumnIndex( ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME)); String phoneNumber = phones.getString(phones.getColumnIndex( ContactsContract.CommonDataKinds.Phone.NUMBER)).replace(" ", ""); String imageUri = phones.getString(phones.getColumnIndex( ContactsContract.CommonDataKinds.Phone.PHOTO_URI)); ContactVO contact = new ContactVO(imageUri, name, phoneNumber, false); contactSet.add(contact); // Set会自动判断是否重复,不会添加重复项 } phones.close(); // 记得关闭Cursor,避免内存泄漏 } // 把Set转成ArrayList使用 List<ContactVO> contactVOList = new ArrayList<>(contactSet); System.out.println("去重后的列表大小:" + contactVOList.size());
三、额外说明
- 如果你的需求是姓名和号码都相同才视为重复,可以在equals里同时判断标准化后的号码和姓名,但通常手机号唯一就能代表同一联系人。
- 记得处理Cursor为null的情况,避免空指针异常,并且用完Cursor后要关闭它。
- 标准化手机号的逻辑可以根据你的目标用户调整,比如针对不同国家的号码做不同的处理。
内容的提问来源于stack exchange,提问作者KarthikKPN

