Java集合框架03:Collection集合之Set
Set子接口
无序、无下标、元素不可重复
所有方法都继承自Collection父接口,没有自己的方法
HashSet实现类
集合中的元素都是引用类型,都有自己的HashCode,基于HashCode比较可以实现元素不重复,元素默认的存储顺序也是根据这个计算的
存储结构:哈希表(数组+链表或数组+红黑树)
- 先根据HashCode计算保存的位置,如果该位置为空则直接保存,否则进行下一步比较
- 再执行equals()方法,如果二者相等则认为重复,否则在该位置形成链表保存
- 两个对象的HashCode相同,并不一定表示两个对象就相同,即equals()不一定为true,只能说明这两个对象在一个散列存储结构中。所以自定义规则时必须同时重写hashCode()和equals()方法才能保证元素的唯一性
import java.util.HashSet;
import java.util.Iterator;
public class Hello{
public static void main(String[] args) {
//Set<String> set = new HashSet<>();也可以,不常用
HashSet<String> set = new HashSet<>();
set.add("小米");
set.add("苹果");
set.add("华为");
set.add("华为");
//添加了重复元素无效,并且顺序和添加的不一样
System.out.println(set);
//增强for循环遍历
for (String i : set){
System.out.println(i);
}
//iterator()迭代器方法遍历
Iterator<String> i = set.iterator();
while (i.hasNext()){
System.out.println(i.next());
}
}
}
拓展:可以同时重写hashCode()和equals()方法,更改规则,使得内容相同的元素就算重复
import java.util.HashSet;
public class Hello{
public static void main(String[] args) {
HashSet<Test> set = new HashSet<>();
Test a = new Test("ty", 25);
Test b = new Test("tao", 26);
set.add(a);
set.add(b);
//重写hashCode()和equals()方法后,元素内容相同就算重复
set.add(new Test("ty", 25));
System.out.println(set);
}
}
class Test {
String name;
int age;
public Test(String name, int age) {
this.name = name;
this.age = age;
}
//重写toString()方法,自定义打印内容
@Override
public String toString() {
return name + " : " + age;
}
//重写hashCode()方法,使得内容相同的对象hashCode就相等
@Override
public int hashCode() {
int n1 = name.hashCode();
int n2 = age;
return n1 + n2;
}
//必须同时重写equals()方法
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (obj instanceof Test) {
Test ob = (Test) obj;
//注意比较字符串的内容是否相等,用equals()方法而不是==
if (this.name.equals(ob.name) && this.age == ob.age){
return true;
}
}
return false;
}
}
拓展:哈希表计算hashCode时为什么用31?
- 31是一个质数,计算时可以减少散列冲突,尽可能生成不同的hashCode
- 31 * i = (i<<5) - i,31用来计算可以转换为位运算,提高执行效率
TreeSet实现类
基于字典顺序实现元素不重复,默认实现了SortedSet接口,对集合元素自动排序,默认升序
存储结构:红黑树
注意:有多个属性的元素是无法自动排序的,必须自定义排序的顺序,有两种方法,自然排序和比较器排序
-
自然排序(对象类实现Comparable接口)
元素对象类实现Comparable接口,并重写CompareTo方法指定排列顺序
import java.util.TreeSet;
public class Hello{
public static void main(String[] args) {
TreeSet<Test> tree = new TreeSet<>();
Test a = new Test("ty", 25);
Test b = new Test("tao", 26);
Test c = new Test("tao", 27);
//从源码分析,每次调用add()方法时,都会自动调用compareTo()方法将该元素和其他所有元素进行比较
tree.add(a);
tree.add(b);
tree.add(c);
//此处可以删除原有的元素,因为Test类重写了compareTo()方法,只比较对象的属性,类似于重写了equals()方法
tree.remove(new Test("ty", 25));
System.out.println(tree);
}
}
//1.自然排序,对象类实现Comparable接口,泛型为该对象类型,必须重写其compareTo()方法
class Test implements Comparable<Test>{
String name;
int age;
public Test(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public String toString() {
return name + " : " + age;
}
//必须重写接口的compareTo()方法
@Override
public int compareTo(Test o) {
//下面的compareTo()是String类的方法,和上面那个不是同一个
//按照字典顺序比较,对象字符串在参数字符串前返回负数,在后返回正数,相同返回0
int n1 = this.name.compareTo(o.name);
int n2 = this.age - o.age;
//如果n1 == 0,说明姓名相同,只用比较年龄
//正数表示放在红黑树的右边,即顺序输出;负数表示放在红黑树的左边,即逆序输出;0表示元素相同,仅存放第一个元素
//默认是升序,如果要降序,可以加个负号(return n1 == 0 ? -n2 : -n1;)
return n1 == 0 ? n2 : n1;
}
}
-
比较器排序(TreeSet类在构造方法的参数中实现Comparator接口)
在创建集合时自定义规则,TreeSet类选择有参构造,在参数中实现一个匿名的Comparator接口类,并重写compare()方法指定排列顺序,如果返回值为0则为重复元素
import java.util.Comparator;
import java.util.TreeSet;
public class Hello{
public static void main(String[] args) {
//2.比较器排序,在构造方法的参数中实现一个匿名接口类Comparator,并重写其compare()方法
//比较器可用于Collections.sort()和Arrays.sort()方法,但元素必须是引用类型
TreeSet<Test> tree = new TreeSet<>(new Comparator<Test>() {
//必须重写接口的compare()方法
@Override
public int compare(Test o1, Test o2) {
int n1 = o1.name.compareTo(o2.name);
int n2 = o1.age - o2.age;
return n1 == 0 ? n2 : n1;
}
});
// //2.比较器排序,也可以创建一个成员内部类实现Comparator接口
// class TestComparator implements Comparator<Test>{
//
// @Override
// public int compare(Test o1, Test o2) {
// int n1 = o1.name.compareTo(o2.name);
// int n2 = o1.age - o2.age;
// return n1 == 0 ? n2 : n1;
// }
// }
//
// TreeSet<Test> tree = new TreeSet<>(new TestComparator());
Test a = new Test("ty", 25);
Test b = new Test("tao", 26);
Test c = new Test("tao", 26);
tree.add(a);
tree.add(b);
tree.add(c);
//此处可以删除原有的元素,因为重写了compare()方法,只比较对象的属性,类似于重写了equals()方法
tree.remove(new Test("ty", 25));
System.out.println(tree);
}
}
class Test {
String name;
int age;
public Test(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public String toString() {
return name + " : " + age;
}
}
练习:将字符串按照长度排序
import java.util.Comparator;
import java.util.TreeSet;
public class Hello{
public static void main(String[] args) {
TreeSet tree = new TreeSet<>(new Comparator<String>() {
//重写compare()方法,先判断长度,再判断字典顺序
@Override
public int compare(String o1, String o2) {
int n1 = o1.length()-o2.length();
int n2 = o1.compareTo(o2);
return n1 == 0 ? n2 : n1;
}
});
tree.add("beijing");
tree.add("tianjin");
tree.add("xian");
tree.add("jinan");
System.out.println(tree);
}
}