Set集合
- 特点:无序,无下标,元素不可重复
- 方法:全部继承自Collection中的方法
不包含重复元素的集合。
更正式地,集合不包含一对元素
e1
和e2
,使得e1.equals(e2)
,并且最多一个空元素。正如其名称所暗示的那样,这个接口模拟了数学集抽象。
Set
接口除了继承自Collection
接口的所有构造函数的合同
以及add,equals
和hashCode
方法的合同外
,还
增加
了其他规定。 其他继承方法的声明也包括在这里以方便。 (伴随这些声明的规范已经量身定做Set
接口,但它们不包含任何附加的规定。)构造函数的额外规定并不奇怪,所有构造函数都必须创建一个不包含重复元素的集合(如上所定义)。
注意:如果可变对象用作设置元素,则必须非常小心。 如果对象的值以影响
equals
比较的方式更改,而对象是集合中的元素,则不
指定集合的行为。 这种禁止的一个特殊情况是,一个集合不允许将其本身作为一个元素。一些集合实现对它们可能包含的元素有限制。 例如,一些实现禁止空元素,有些实现对元素的类型有限制。 尝试添加不合格元素会引发未经检查的异常,通常为
NullPointerException
或ClassCastException
。 尝试查询不合格元素的存在可能会引发异常,或者可能只是返回false; 一些实现将展现出前者的行为,一些实现将展现出后者。 更一般来说,尝试对不符合条件的元素的操作,其完成不会导致不合格元素插入到集合中,可能会导致异常,或者可能会成功执行该选项。 此异常在此接口的规范中标记为“可选”。
Set接口的使用
-
Set的实现类
-
HashSet【重点】:
- 基于HashCode实现元素不重复
- 当存入元素的哈希码相同时,会调用equals进行确认,如果结果为true,则拒绝后者存入。
-
TreeSet:
- 基于排列顺序实现元素不重复。
-
package com.set.demo;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
public class Demo1 {
public static void main(String[] args) {
Set<String> set = new HashSet();
/*
* Set
* 特点:
* 1.无序,无下标
* 2.不可重复
* */
set.add("苹果");
set.add("小米");
set.add("华为");
set.add("三星");
//set.add("三星");//重复不可添加
System.out.println(set.toString());
System.out.println(set.size());
// 遍历,因为Set是没有下标的,所以遍历只能通过增强for或者迭代器来实现
System.out.println("-------------增强for-------------");
for (String s : set
) {
System.out.println(s);
}
System.out.println("-------------迭代器-------------");
Iterator<String> iterator = set.iterator();
while (iterator.hasNext()) {
System.out.println(iterator.next());
}
System.out.println("==============================");
// 判断
System.out.println(set.contains("小米"));
System.out.println(set.isEmpty());
// 删除
set.remove("小米");
set.clear();
}
}
Set的实现类
HashSet【重点】:
- 基于HashCode计算元素存放的位置
- 当存入元素的哈希码相同时,会调用equals进行确认,如果结果为true,则拒绝后者存入。
- 存储结构:哈希表(数组+单向链表+红黑树)
哈希表结构:
package com.set.demo;
import com.sun.org.apache.xalan.internal.xsltc.compiler.util.StringStack;
import java.util.HashSet;
import java.util.Iterator;
public class Demo2 {
public static void main(String[] args) {
HashSet<String> strings = new HashSet<>();
/*
* HashSet
* 存储结构:哈希表(数组+链表+红黑树)
* */
strings.add("孙悟空");
strings.add("牛魔王");
strings.add("猪八戒");
strings.add("沙和尚");
System.out.println(strings.size());
System.out.println(strings.toString());
/*删除*/
strings.remove("牛魔王");
/*遍历*/
System.out.println("------------增强for--------------");
for (String string : strings) {
System.out.println(string);
}
System.out.println("------------迭代器--------------");
Iterator<String> iterator = strings.iterator();
while (iterator.hasNext()) {
System.out.println(iterator.next());
}
/*判断*/
strings.contains("牛魔王");
strings.isEmpty();
}
}
- 判断是否重复解析
package com.set.demo;
import java.util.HashSet;
public class Demo3 {
public static void main(String[] args) {
/*
* HashSet
* 1.HashSet在存储元素时,首先会比较Hashcode,如果Hashcode相同则会调用equals方法,则存储在对应的列表中所在位置没有被占据,则会直接存入
* 2.如果列表所在位置已被占据,则会形成链表。
*
* */
HashSet<Person> people = new HashSet<>();
Person p1 = new Person("孙悟空", 328);
Person p2 = new Person("猪八戒", 853);
Person p3 = new Person("牛魔王", 928);
Person p4 = new Person("沙和尚", 728);
people.add(p1);
people.add(p2);
people.add(p3);
people.add(p4);
people.add(p1);
System.out.println(people.size()); // 此时我们发现重复的P1并没有被加入集合中
System.out.println(people.toString());
people.add(new Person("孙悟空", 328)); // 此时我们发现新的对象可以被加入集合中
/*因为new 会重新再内存中申请一个空间,后者传入的引用就不再是p1了所以可以添加成功
* 如果我们不希望它添加成功,我们认为,名字和年龄相同就是同一个对象,这是我们可以重写
* Person类的 hashCode 和 equals方法来改变其判定。
* */
System.out.println(people.size()); // 此时我们发现重复的P1并没有被加入集合中
System.out.println(people.toString());
}
}
- 重写 hashCode 方法和 equals 方法
package com.set.demo;
import java.util.Objects;
public class Person {
String name;
int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
@Override
public boolean equals(Object o) {
if (o == null) {
return false;
} else {
if (o instanceof Person && this.name.equals(((Person) o).name) && this.age == ((Person) o).age) {
return true;
}
return false;
}
}
@Override
public int hashCode() {
return name.hashCode() + age;
}
}
那么为什么选用31作为基数呢?先要明白为什么需要HashCode.每个对象根据值计算HashCode,这个code大小虽然不奢求必须唯一(因为这样通常计算会非常慢),但是要尽可能的不要重复,因此基数要尽量的大。另外,31*N可以被编译器优化为
左移5位后减1,有较高的性能。其实选用31还是有争议
public int hashCode() {
int h = hash;
if (h == 0 && value.length > 0) {
char val[] = value;
for (int i = 0; i < value.length; i++) {
h = 31 * h + val[i];
}
hash = h;
}
return h;
}
- 结论:
- 31是一个质数,它可以在计算时,减少散列的重复可能性
- i*31 会被编译器优化成 (i<<5)-i, 可以优化运行效率
TreeSet
- 基于排列顺序实现不重复。
- 实现了SortedSet接口,对集合元素自动排序。
- 元素对象的类型必须实现Comparable接口,指定排序规则。
- 通过CompareTo方法确定是否为重复元素。
- 存储结构:红黑树
二分查找树:
红黑树:
package com.set.demo;
import java.util.Iterator;
import java.util.TreeSet;
public class Demo4 {
public static void main(String[] args) {
TreeSet<String> strings = new TreeSet<>();
/*
* TreeSet
* 存储结构:红黑树
* */
strings.add("xyz");
strings.add("hello");
strings.add("abc");
System.out.println("元素个数:" + strings.size());
System.out.println(strings.toString());
// strings.remove("xyz");
// strings.clear();
/*
* 增强for
* */
System.out.println("--------------增强for-------------------");
for (String s : strings) {
System.out.println(s);
}
System.out.println("--------------迭代器-------------------");
Iterator<String> iterator = strings.iterator();
while (iterator.hasNext()) {
System.out.println(iterator.next());
}
// 判断
strings.contains("xyz");
strings.isEmpty();
}
}
- ClassCastException---->实现ComParable接口,实现compareTo方法
package com.set.demo;
import java.util.TreeSet;
public class Demo5 {
public static void main(String[] args) {
TreeSet<Person> people = new TreeSet<>();
Person p1 = new Person("孙悟空", 528);
Person p2 = new Person("猪八戒", 753);
Person p3 = new Person("牛魔王", 1028);
Person p4 = new Person("沙和尚", 758);
people.add(p1);
people.add(p2);
people.add(p3);
people.add(p4);
people.add(p1);
System.out.println(people.size()); //ClassCastException 因为内部存储结构是红黑树,程序不知道该如何比较排序
System.out.println(people.toString());
/*
* 要求元素必须要实现ComParable接口,该接口中有一个方法compareTo,如果返回值为0,则认为相同。
*
* @Override
public int compareTo(Person o) {
int n1 = this.name.compareTo(o.name);
int n2 = this.age - o.getAge();
return n1 == 0 ? n2 : n1;
}
* */
}
}
package com.set.demo;
import java.util.Objects;
public class Person implements Comparable<Person> {
String name;
int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
@Override
public boolean equals(Object o) {
if (o == null) {
return false;
} else {
if (o instanceof Person && this.name.equals(((Person) o).name) && this.age == ((Person) o).age) {
return true;
}
return false;
}
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
@Override
public int hashCode() {
return name.hashCode() + age;
}
@Override
public int compareTo(Person o) {
int n1 = this.name.compareTo(o.name);
int n2 = this.age - o.getAge();
return n1 == 0 ? n2 : n1;
}
}
- 除了通过实现实现ComParable接口的方式来解决这一问题之外,TreeSet还有一个构造器可以通过实现Comparator接口的方式建立匿名内部类的方式来定制比较器来解决比较问题。
package com.set.demo;
import java.util.Comparator;
import java.util.TreeSet;
public class Demo6 {
public static void main(String[] args) {
TreeSet<Person> people = new TreeSet<>(new Comparator<Person>() {
/*
* TreeSet
* 我们除了实现ComParable接口之外
* 还可以在建立集合时,通过实现Comparator接口的方式来建立一个匿名内部类来自定义比较器
* */
@Override
public int compare(Person o1, Person o2) {
int n1 = o1.name.compareTo(o2.name);
int n2 = o1.age - o2.getAge();
return n1 == 0 ? n2 : n1;
}
});
Person p1 = new Person("孙悟空", 528);
Person p2 = new Person("猪八戒", 753);
Person p3 = new Person("牛魔王", 1028);
Person p4 = new Person("沙和尚", 758);
people.add(p1);
people.add(p2);
people.add(p3);
people.add(p4);
people.add(p1);
System.out.println(people.size());
System.out.println(people.toString());
}
}
TreeSet 小案例
- 要求字符串按照字符串长度进行排序
- 如果长度一样,就按照字母顺序进行排序
package com.set.demo;
import java.util.Comparator;
import java.util.TreeSet;
public class Demo7 {
public static void main(String[] args) {
TreeSet<String> strings = new TreeSet<>(new Comparator<String>() {
@Override
public int compare(String o1, String o2) {
int length = o1.length() - o2.length();
int n2 = o1.compareTo(o2);
return length == 0 ? n2 : length;
}
});
boolean s1 = strings.add("abc");
boolean s2 = strings.add("xyzsadjk");
boolean s3 = strings.add("helijdli");
boolean s4 = strings.add("xyz");
System.out.println(strings.toString());
}
}