【Java】HashSet_Integer_ num=new HashSet_Integer_(); 与 Set_Integer_ num=new HashSet_Integer_(); 的区别详解
·

文章目录
一、问题引入
在日常 Java 开发中,你可能见过两种写法:
HashSet<Integer> num_set = new HashSet<Integer>();
以及:
Set<Integer> num_set = new HashSet<Integer>();
这两种写法在语法上都合法,功能上似乎也没区别,都能正常使用 add()、remove()、contains() 等方法。
但它们之间到底有什么不同?哪种写法更好?这背后涉及 Java 的核心思想 ——
面向接口编程(Programming to an Interface)
二、回顾:Set 与 HashSet 的关系
Set 是接口(Interface)
Set 是 Java 集合框架(java.util 包)中的一个接口,它定义了一组不允许重复元素的集合操作方法:
public interface Set<E> extends Collection<E> {
int size();
boolean isEmpty();
boolean contains(Object o);
boolean add(E e);
boolean remove(Object o);
void clear();
}
Set本身并不能被实例化,只定义了“行为规范”。
HashSet 是实现类(Implementation Class)
HashSet 是 Set 接口的一个具体实现类:
public class HashSet<E> extends AbstractSet<E>
implements Set<E>, Cloneable, java.io.Serializable {
// 内部使用 HashMap 实现
}
HashSet 内部基于 HashMap 实现,无序、元素唯一,查询和添加效率很高。
三、两种写法的区别
| 写法 | 左边类型 | 右边类型 | 特点 |
|---|---|---|---|
HashSet<Integer> num_set = new HashSet<Integer>(); |
具体类 | 具体类 | 明确使用 HashSet |
Set<Integer> num_set = new HashSet<Integer>(); |
接口 | 实现类 | 面向接口编程 |
✅ 第一种写法:绑定实现类
HashSet<Integer> num_set = new HashSet<Integer>();
这种写法的含义是:
明确需要使用一个
HashSet,并且在后续代码中可以访问HashSet特有的方法。
例如:
num_set.add(10);
num_set.addAll(List.of(20, 30));
System.out.println(num_set);
优点:
- 直接、清晰;
- 如果确实只打算用
HashSet特性(如性能、实现细节),这样没问题。
缺点:
- 代码与具体实现强耦合;
- 若以后想换成
TreeSet(有序)或LinkedHashSet(保持插入顺序),需要改动声明和实现部分。
例如:
// ❌ 需要改动两处
HashSet<Integer> num_set = new TreeSet<Integer>();
这会直接编译错误。
✅ 第二种写法:面向接口编程
Set<Integer> num_set = new HashSet<Integer>();
这种写法的思想是:
“我只关心这是一个 Set(集合),不关心它的具体实现。”
优点:
- 解耦:左侧使用接口声明,右侧使用实现类实例;
- 灵活:后期可以很容易更换实现类;
- 遵循面向对象设计原则(开闭原则、依赖倒置原则);
- 更易于单元测试与维护。
例如:
Set<Integer> num_set = new TreeSet<>(); // 换实现只需改右边
Set<Integer> num_set = new LinkedHashSet<>();
这时,不需要修改方法签名或接口类型,代码依旧能正常工作。
四、面向接口编程的思想
这是 Java 编程中非常重要的一条原则:
“面向接口编程,而不是面向实现编程。”
这意味着:
- 我们应依赖接口(如
Set),而不是具体实现(如HashSet); - 代码应该依赖抽象而不是具体类;
- 这样可以更容易扩展、更容易测试、更容易维护。
例如:
public void processData(Set<Integer> dataSet) {
dataSet.add(1);
dataSet.add(2);
System.out.println(dataSet);
}
调用时可以灵活选择实现:
processData(new HashSet<>()); // 无序
processData(new TreeSet<>()); // 有序
processData(new LinkedHashSet<>()); // 按插入顺序
💡 上层代码不需要关心底层使用哪种 Set 实现。
五、从内存角度看,两种写法是否不同?
其实,两种写法在运行时创建的对象是完全相同的。
都创建了一个 HashSet 实例,只是引用变量的类型不同。
System.out.println(num_set.getClass()); // class java.util.HashSet
无论左边是 HashSet 还是 Set,这行代码都会输出同样的结果。
区别仅在于:
- 编译阶段:编译器会根据左边声明的类型来限制可调用的方法;
- 运行阶段:对象实际类型由右边的
new HashSet<>()决定。
六、实际开发中的建议 ✅
| 场景 | 建议用法 | 原因 |
|---|---|---|
| 普通业务代码 | Set<Integer> num_set = new HashSet<>(); |
保持接口抽象、便于维护 |
| 只在局部快速使用 | HashSet<Integer> set = new HashSet<>(); |
简单直观、无需多态 |
| 方法参数类型 | 使用接口类型,如 Set<E> |
提高通用性与扩展性 |
| 想要换实现类(如 TreeSet) | 使用接口声明 | 仅改右边即可 |
七、一句话总结
“左边是接口,右边是实现” —— 是优雅、灵活、可维护的写法。
也就是说:
Set<Integer> num_set = new HashSet<>();
✅ 代码更抽象、更可扩展;
✅ 更符合面向对象设计原则;
✅ 是企业级开发的标准写法。
更多推荐



所有评论(0)