无序、无下标、元素不可重复
所有方法都继承自Collection父接口,没有自己的方法
集合中的元素都是引用类型,都有自己的HashCode,基于HashCode比较可以实现元素不重复
存储结构:哈希表(数组+链表或数组+红黑树)
import java.util.HashSet; import java.util.Iterator; import java.util.Set; 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()方法,如果二者相等则认为重复,否则在该位置形成链表保存
- 两个对象的HashCode相同,并不一定表示两个对象就相同,即equals()不一定为true,只能说明这两个对象在一个散列存储结构中
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)); //直接打印是对象地址,如果想打印内容,需要重写toString()方法 System.out.println(set); } } 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; } @Override public int hashCode() { //重新定义hashCode,使得内容相同的对象hashCode就相等 int n1 = name.hashCode(); int n2 = age; return n1 + n2; } @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用来计算可以转换为位运算,提高执行效率
基于排列顺序实现元素不重复,实现了SortedSet接口,对集合元素自动排序,默认升序
存储结构:红黑树
注意:自定义类无法自动排序,必须自定义排序的顺序,有两种方法,分别为对象类实现Comparab接口和TreeSet类在构造方法的参数中实现Comparator接口(比较器)
元素对象的类实现Comparable接口,并重写CompareTo方法指定排列顺序,如果返回值为0则为重复元素
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); tree.add(a); tree.add(b); tree.add(c); //此处可以删除原有的元素,因为Test类重写了compareTo()方法,只比较对象的属性,不用再重写equals()方法 tree.remove(new Test("ty", 25)); System.out.println(tree); } } //第一种方法,对象类实现Comparable接口,泛型类型为Test,且重写其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) { int n1 = this.name.compareTo(o.name); int n2 = this.age - o.age; //如果n1 == 0,说明姓名相同,只用比较年龄 return n1 == 0 ? n2 : n1; } }
在创建集合时就定义规则,TreeSet类选择有参构造,在参数中实现一个匿名的Comparator接口类,并重写compare()方法指定排列顺序,如果返回值为0则为重复元素
import java.util.Comparator; import java.util.TreeSet; public class Hello{ public static void main(String[] args) { //第二种方法,在有参构造的参数中实现一个匿名的Comparator接口类,并重写compare()方法 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; } }); Test a = new Test("ty", 25); Test b = new Test("tao", 26); Test c = new Test("tao", 27); 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); } }