如何将ArrayList中属性唯一的TwoNumbers对象复制到新ArrayList
这个问题很典型——Java里默认是按对象引用地址判断相等性的,哪怕两个TwoNumbers对象的num1、num2数值完全一样,只要是不同实例,就会被当成不同的对象。要解决这个问题,核心是告诉Java:「当两个TwoNumbers对象的num1和num2都相等时,它们就是同一个逻辑对象」,具体要做两步:重写equals()和hashCode()方法,然后用合适的方式去重。
下面一步步来:
Java的集合类(比如HashSet、ArrayList的contains方法)判断元素是否重复,依赖的就是这两个方法:
equals()用来判断两个对象逻辑上是否相等hashCode()用来快速分组(比如HashSet会先比较哈希值,再用equals确认)
给你写个完整的TwoNumbers示例:
import java.util.Objects; public class TwoNumbers { private int num1; private int num2; // 构造方法 public TwoNumbers(int num1, int num2) { this.num1 = num1; this.num2 = num2; } // Getter方法(如果需要的话) public int getNum1() { return num1; } public int getNum2() { return num2; } // 重写equals方法 @Override public boolean equals(Object o) { // 如果是同一个引用,直接返回true if (this == o) return true; // 如果对象为null,或者不是TwoNumbers类型,返回false if (o == null || getClass() != o.getClass()) return false; // 强转为TwoNumbers,比较num1和num2 TwoNumbers that = (TwoNumbers) o; return num1 == that.num1 && num2 == that.num2; } // 重写hashCode方法 @Override public int hashCode() { // 用Objects工具类结合num1和num2生成哈希值 return Objects.hash(num1, num2); } }
这里要注意:如果你的num1/num2是包装类型(比如Integer),equals里要用Objects.equals(num1, that.num1)而不是==,避免空指针和值相等但引用不同的问题。
有两种常用方式,选哪种看你的需求:
方式一:用HashSet自动去重(简单高效)
HashSet会自动过滤掉重复元素,依赖我们刚才重写的equals和hashCode。把原ArrayList的元素放进HashSet,再转成新的ArrayList就行:
import java.util.ArrayList; import java.util.HashSet; import java.util.List; public class Main { public static void main(String[] args) { // 原ArrayList,假设已经存了一些TwoNumbers对象 List<TwoNumbers> originalList = new ArrayList<>(); originalList.add(new TwoNumbers(1, 2)); originalList.add(new TwoNumbers(3, 4)); originalList.add(new TwoNumbers(1, 2)); // 重复元素 // 用HashSet去重 HashSet<TwoNumbers> uniqueSet = new HashSet<>(originalList); // 转成新的ArrayList List<TwoNumbers> uniqueList = new ArrayList<>(uniqueSet); // 测试输出,会发现只保留了两个不重复的对象 for (TwoNumbers num : uniqueList) { System.out.println(num.getNum1() + ", " + num.getNum2()); } } }
这种方式优点是代码简洁,效率高,缺点是会打乱原列表的顺序(因为HashSet是无序的)。如果需要保留原顺序,可以用LinkedHashSet代替HashSet,它会保留元素插入的顺序。
方式二:手动遍历检查(保留原顺序,适合小列表)
如果要严格保留原列表中元素的出现顺序,也可以手动遍历原列表,每次检查新列表里是否已经存在相同属性的对象:
import java.util.ArrayList; import java.util.List; public class Main { public static void main(String[] args) { List<TwoNumbers> originalList = new ArrayList<>(); originalList.add(new TwoNumbers(1, 2)); originalList.add(new TwoNumbers(3, 4)); originalList.add(new TwoNumbers(1, 2)); List<TwoNumbers> uniqueList = new ArrayList<>(); for (TwoNumbers num : originalList) { // 调用contains时,会用到我们重写的equals方法判断是否存在 if (!uniqueList.contains(num)) { uniqueList.add(num); } } // 输出会和原列表顺序一致,且去重 for (TwoNumbers num : uniqueList) { System.out.println(num.getNum1() + ", " + num.getNum2()); } } }
这种方式的优点是保留原顺序,缺点是效率低一点(每次contains都要遍历新列表),适合元素数量不多的场景。
如果你的num1或num2是自定义的引用类型(比如不是Integer、String这些已经重写了equals/hashCode的类),那对应的类也必须重写equals和hashCode方法,否则还是会按引用判断相等性,导致去重失败。
内容的提问来源于stack exchange,提问作者Qwisatz

