系统:Win10
JDK:1.8.0_121
IDE:IntelliJ IDEA 2017.3.7
java.util.Set接口和java.util.List接口一样,同样继承自Collection接口,它与Collection接口中的方法基本一致,并没有对Collection接口进行功能上的扩充,只是Collection接口更加严格。与List接口不同的是,Set接口中元素无序,并且都会以某种规则保证存入的元素不出现重复
Set集合有多个子类,这里主要学习其中的java.util.HashSet、java.util.LinkedHashSet这两个集合
小知识:Set集合取出元素的方式可以采用:迭代器、增强for
java.util.HashSet是Set接口的一个实现类,它所存储的元素是不可重复的,并且元素都是无序的(即存取顺序不能保证一致)。java.util.HashSet底层的实现其实是一个java.util.HashMap支持。
HashSet是根据对象的哈希值来确定元素在集合中的存储位置,因此具有良好的存取和查找性能。保证元素唯一性的方式依赖于:hashCode与equals方法
这里我们先使用一下Set集合存储,再进行原理的分析:
public class HashSetDemo01 {
public static void main(String[] args) {
// 创建HashSet集合
HashSet<String> set = new HashSet<>();
// 添加元素
set.add("abc");
set.add("bac");
set.add("cab");
set.add("abc");
set.add("你好");
System.out.println(set);
// 用增强for遍历集合
for (String s : set) {
System.out.println(s);
}
}
}
小知识:根据结果我们发现字符串abc只存储了一次,也就是说重复的元素set集合不存储,而且输出的顺序和我们存储的顺序也不一致,说明是无序的
什么是哈希表?
在JDK1.8之前,哈希表底层采用数组+链表实现的,即使用链表处理冲突,同一hash值的不同元素都存储在一个链表里。但是当位于一个桶中的元素较多,即hash值相等的元素较多时,通过key值依次查找的效率较低。而在JDK1.8中,哈希表存储采用数组+链表+红黑树实现,当链表长度超过阙值(8)时,将链表转换为红黑树,这样可以大大缩减查询时间
简单来说:哈希表是由数组+链表+红黑树(JDK1.8增加了红黑树部分)实现的,如下图所示
如果对上面这图的数据存储过程有点疑问的话,我们可以结合一个存储流程图来理解:
总而言之,JDK1.8引入红黑树大程度优化了HashMap的性能,那么对于我们来讲保证HashMap集合元素的唯一,其实就是根据对象的hashCode和equals方法来决定的。如果我们往集合中存放自定义的对象,那么保证其唯一,就必须重写hashCode和equals方法,建立属于当前对象的比较方式
给HashSet中存放自定义类型元素时,需要重写对象中的hashCode和equals方法,建立自己的比较方式,才能保证HashSet集合的对象唯一
创建自定义的Person类
public class Person {
private String name;
private int age;
public Person() {
}
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Person person = (Person) o;
return age == person.age &&
Objects.equals(name, person.name);
}
@Override
public int hashCode() {
return Objects.hash(name, age);
}
// 用于等会直接打印Person对象
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
创建测试方法
public class HashSetDemo02 {
public static void main(String[] args) {
// 创建集合对象,用于存储Person对象
HashSet<Person> set = new HashSet<>();
// 往集合中添加对象
set.add(new Person("张三", 18));
set.add(new Person("李四", 18));
set.add(new Person("李四", 19));
set.add(new Person("张三", 18));
// 使用增强for打印set集合
for (Person person : set) {
System.out.println(person);
}
}
}
我们知道HashSet保证元素唯一,可是元素存放进去和取出来的顺序是不能保证一致的,如果我们需要存取有序,该怎么办?
在HashSet下面有一个子类java.util.LinkedHashSet,它是链表和哈希表组合的一个数据存储结构。与HashSet不同之处在于,它维护着一个运行于所有条目的双重链表。此链表定义了迭代顺序,即按照将元素插入set中的顺序进行迭代
代码演示如下:
public class LinkedHashSetDemo01 {
public static void main(String[] args) {
Set<String> linkedSet = new LinkedHashSet<>();
linkedSet.add("张三");
linkedSet.add("李四");
linkedSet.add("王二");
linkedSet.add("麻子");
linkedSet.add("张三");
// 使用迭代器进行迭代
Iterator<String> it = linkedSet.iterator();
while (it.hasNext()) {
String str = it.next();
System.out.println(str);
}
}
}
在JDK1.5之后,如果我们定义一个方法需要接受多个参数,并且多个参数类型一致,我们可以将其简化为如下格式:
修饰符 返回值类型 方法名(参数类型... 形参名){
// 方法体
}
其实这个书写完全等价于
修饰符 返回值类型 方法名(参数类型[] 形参名){
// 方法体
}
只是后面这种定义,在调用时必须传递数组,而前者可以直接传递数据即可
JDK1.5以后,出现了简化操作。…用在参数上,称之为可变参数
同样是代表数组,但是在调用这个带有可变参数的方法时,不用创建数组(这就是简单之处),直接将数组中的元素作为实际参数进行传递,其实编译成的class文件,将这些元素先封装到一个数组中,再进行传递。这些动作都在编译.class文件时,自动完成了
代码演示:
/*
写一个方法,随便输入几个整数,返回这几个数的和
*/
public class ChangeParamDemo01 {
public static void main(String[] args) {
int res = add(1, 3, 5, 7, 9);
System.out.println(res);
}
// 当方法参数的:类型确定,数量不确定时,使用可变参数
private static int add(int...param) {
int sum = 0;
for (int i : param) {
sum = sum + i;
}
return sum;
}
}