List、Map、Set是Java集合框架中的三个核心接口,它们在存取元素时各自具有独特的特点。以下是对这三个接口存取元素特点的详细分析:
List接口
有序性:
List中的元素是有序的,它们按照插入的顺序进行排列。
可重复性:
List允许存储重复的元素,即List中可以有多个相同的元素。
索引访问:
List提供了基于索引的访问方式,可以通过索引(位置)来访问和修改List中的元素。这类似于数组的访问方式。
实现类:
List接口的主要实现类包括ArrayList和LinkedList。ArrayList基于动态数组实现,适合随机访问,但插入和删除效率较低;LinkedList基于双向链表实现,插入和删除操作效率高,但查询效率较低。
Set接口
元素唯一性:
Set中的元素是唯一的,不允许重复。如果尝试向Set中添加一个已经存在的元素,则添加操作会失败。其实通过查看源码就知道其实是Set的add方法中通过map不能存储重复的key来保证唯一的,Set 集合的 add 方法有一个 boolean 的返回值,当集合中没有某个元素,此时 add 方法可成功加入该元素时, 则返回 true,当集合含有与某个元素 equals 相等的元素时,此时 add 方法无法加入该元素, 返回结果为 false。
public boolean add(E e) {
return map.put(e, PRESENT)==null;
}
无序性:
Set中的元素没有特定的顺序,即不保证元素存储的顺序与插入顺序一致。但是,某些Set的实现类(如LinkedHashSet)可以维护元素的插入顺序。
无索引访问:
Set不支持通过索引来访问和修改元素。它主要通过迭代器(Iterator)或增强型for循环来遍历元素。
实现类:
Set接口的主要实现类包括HashSet、LinkedHashSet和TreeSet。HashSet基于哈希表实现,查找效率高,但不保证顺序;LinkedHashSet继承自HashSet,使用双向链表维护插入顺序;TreeSet基于红黑树实现,可以按元素的自然顺序或自定义顺序进行排序。
HashSet延伸:
HashSet的存储方式
注意HashSet的存储方式是按照 hashcode 值的某种运算方式进行存储,而不是直接按 hashCode 值的大小进行存储。通过查看源码可以看到运算方式如下:
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
HashSet如何比较相等
在 Java 中,HashSet
是一个基于哈希表的数据结构,用于存储不重复的元素。当你将一个对象添加到 HashSet
中时,它会根据对象的哈希码(通过调用对象的 hashCode()
方法)来确定该对象应该存放在哪个桶(bucket)中。为了确定两个对象是否相等,HashSet
依赖两个条件:
- 哈希码相等:两个对象的哈希码必须相等。这是通过调用对象的
hashCode()
方法来确定的。 - equals 方法:如果两个对象的哈希码相等,那么
HashSet
会进一步调用对象的equals()
方法来确认它们是否确实相等。
因此,要使 HashSet
正确比较两个对象是否相等,必须满足以下要求:
- 覆盖
hashCode()
方法:确保两个相等的对象具有相同的哈希码。 - 覆盖
equals()
方法:确保equals()
方法定义了两个对象相等的逻辑。
代码实例:
class Student {
private String name;
private int age;
public Student(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Student student = (Student) o;
return age == student.age && Objects.equals(name, student.name);
}
@Override
public int hashCode() {
return Objects.hash(name, age);
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
public class HashSetEqualsTest {
public static void main(String[] args) {
HashSet<Student> set = new HashSet<>();
Student p1 = new Student("Alice", 30);
Student p2 = new Student("Alice", 30);
set.add(p1);
set.add(p2);
System.out.println("Set size: " + set.size()); // 输出应为 1,因为 p1 和 p2 相等
System.out.println(set); // 输出 Student{name='Alice', age=30}
}
}
输出结果:
Set size: 1
[Student{name='Alice', age=30}]
如果Student不实现hashcode和equals方法,则输入结果如下:
Set size: 2
[Student{name='Alice', age=30}, Student{name='Alice', age=30}]
Map接口
键值对存储:
Map以键值对(key-value)的形式存储数据。每个键都是唯一的,但值可以重复。
无序性:
Map中的键值对没有特定的顺序,即不保证键值对的存储顺序与插入顺序一致。但是,某些Map的实现类(如LinkedHashMap和TreeMap)可以维护键值对的顺序。
键的唯一性:
Map中的键是唯一的,每个键最多映射到一个值。Map使用键的equals()和hashCode()方法来比较键是否相等。
值的多重性:
Map中的值可以重复,即多个键可以映射到相同的值。
实现类:
Map接口的主要实现类包括HashMap、TreeMap、Hashtable、LinkedHashMap和ConcurrentHashMap。HashMap基于哈希表实现,查找效率高,但不保证顺序;TreeMap基于红黑树实现,按键的自然顺序或自定义顺序进行排序;LinkedHashMap继承自HashMap,使用双向链表维护插入顺序或访问顺序;Hashtable是线程安全的HashMap,但性能较低;ConcurrentHashMap是线程安全的HashMap,适用于高并发场景。
总结
综上所述,List、Map、Set三个接口在存取元素时各有其独特的特点。List适合存储有序的元素列表,并允许重复元素;Set适合存储不重复的元素集合,并提供了高效的查找性能;Map则适合存储键值对映射,其中键是唯一的,而值可以重复。在选择使用哪个接口时,应根据具体的应用场景和需求来决定。