TreeSet中更新Product对象后compareTo函数失效问题求助
问题描述
使用TreeSet存储Product对象,要求按Quantity降序排列,Quantity相同时按UnitPrice升序排列。初始添加商品时排序正常,但调用updateInfo()更新商品的Quantity或UnitPrice后,排序完全失效。
我的compareTo()实现
@Override public int compareTo(Product o) { if (this.getQuantity() < o.getQuantity()) { return 1; } if (this.getQuantity() == o.getQuantity()) { return this.unitPrice - o.unitPrice; } return -1; }
我的updateInfo()实现
public void updateInfo() { Scanner sc = new Scanner(System.in); String id, productName, quantity, unitPrice, numberForAvail; while (true) { try { System.out.print("Input products's id: "); id = sc.nextLine().toUpperCase(); if (id.isEmpty()) { this.id = this.id; } else { if (!id.matches(letter_regEx) || id.length() < 5) { throw new Exception(); } this.id = id; } break; } catch (Exception e) { System.out.println("ID must be character and lenght > 5"); } } while (true) { try { System.out.print("Input prodct's name: "); productName = sc.nextLine().toUpperCase(); if (productName.isEmpty()) { this.productName = this.productName; } else { if (!productName.matches(letter_regEx)) { throw new Exception(); } this.productName = productName; } break; } catch (Exception e) { System.out.println("Invalid name"); } } while (true) { try { System.out.print("Input Unit Price: "); unitPrice = sc.nextLine(); if (unitPrice.isEmpty()) { this.unitPrice = this.unitPrice; } else { if (!unitPrice.matches("^([1-9][0-9]{0,3}|10000)$")) { throw new Exception(); } this.unitPrice = Integer.parseInt(unitPrice); } break; } catch (Exception e) { System.out.println("Unit price must be > 0 and < 10000"); } } while (true) { try { System.out.print("Input quantity: "); quantity = sc.nextLine(); if (quantity.isEmpty()) { this.quantity = this.quantity; } else { if (!quantity.matches("^([1-9][0-9]{0,2}|1000)$")) { throw new Exception(); } this.quantity = Integer.parseInt(quantity); } break; } catch (Exception e) { System.out.println("Quantity must be > 0 and < 1000"); } } while (true) { try { System.out.print("Input status [0(not available)/ 1 (available)]: "); numberForAvail = sc.nextLine(); if (numberForAvail.isEmpty()) { this.numberForAvail = this.numberForAvail; } else { if (!numberForAvail.matches("^[01]$")) { throw new Exception(); } this.numberForAvail = Integer.parseInt(numberForAvail); break; } } catch (Exception e) { System.out.println("Status must 0 for NOT AVAILABLE and 1 for AVAILABLE"); } } if (this.numberForAvail == 0) { status = "Not available"; } else { status = "Available"; } }
输出情况
- 添加商品时排序正常:

- 将NGUYEN的quantity改为125后,排序失效:

问题原因
TreeSet 基于红黑树实现,它仅在元素插入时根据compareTo()的结果确定元素在树中的位置,后续修改元素的比较属性(比如这里的quantity或unitPrice)时,TreeSet 不会自动重新计算元素位置,也不会维护有序性。一旦元素的比较属性变化,元素当前在树中的位置和compareTo()的逻辑就会冲突,导致排序失效,甚至可能出现无法正确查找、删除元素的问题。
另外你写的compareTo()存在潜在风险:用this.unitPrice - o.unitPrice来比较整数,当数值过大时会发生溢出,导致比较结果错误,应该改用Integer.compare()方法。
同时,你的Product类没有重写equals()和hashCode(),违反了Java规范中compareTo()与equals()的一致性要求——如果a.compareTo(b) == 0,则a.equals(b)必须返回true,反之亦然。这会导致TreeSet无法正确识别重复元素,也是隐藏问题。
解决方案
方案1:修改元素后重新插入
每次调用updateInfo()更新商品信息后,先从TreeSet中移除该元素,再重新添加进去,让TreeSet根据新的属性值重新计算位置:
// 假设你的TreeSet实例名为productSet Product updatedProduct = ...; // 已调用updateInfo()的商品对象 productSet.remove(updatedProduct); productSet.add(updatedProduct);
方案2:使用不可变对象(推荐)
将Product设计为不可变类,所有属性用final修饰,不提供修改属性的方法,更新时直接创建新的Product对象替换原对象,从根源上避免修改元素破坏TreeSet结构:
import java.util.Objects; public class Product implements Comparable<Product> { private final String id; private final String productName; private final int quantity; private final int unitPrice; private final int numberForAvail; private final String status; // 全参数构造方法 public Product(String id, String productName, int quantity, int unitPrice, int numberForAvail) { this.id = id; this.productName = productName; this.quantity = quantity; this.unitPrice = unitPrice; this.numberForAvail = numberForAvail; this.status = numberForAvail == 0 ? "Not available" : "Available"; } // 仅提供getter方法,无setter public String getId() { return id; } public String getProductName() { return productName; } public int getQuantity() { return quantity; } public int getUnitPrice() { return unitPrice; } public int getNumberForAvail() { return numberForAvail; } public String getStatus() { return status; } @Override public int compareTo(Product o) { // 先按quantity降序比较 int quantityCompare = Integer.compare(o.getQuantity(), this.getQuantity()); if (quantityCompare != 0) { return quantityCompare; } // quantity相同时按unitPrice升序比较 return Integer.compare(this.unitPrice, o.unitPrice); } // 重写equals和hashCode,基于唯一标识id @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; Product product = (Product) o; return Objects.equals(id, product.id); } @Override public int hashCode() { return Objects.hash(id); } }
更新时创建新对象替换原对象:
// 假设原对象为oldProduct,根据输入创建新对象 Product newProduct = new Product(newId, newProductName, newQuantity, newUnitPrice, newNumberForAvail); productSet.remove(oldProduct); productSet.add(newProduct);
内容的提问来源于stack exchange,提问作者Nguyen Dang

