集合-Set
1. Set简介
Set接口没有提供Collection接口额外的方法,但实现Set接口的集合类中的元素是不可重复的。
JDKAPI中所提供的Set集合类常用的有:
HashSet:散列存放(重点)
TreeSet:有序存放(重点)
LinkedHashSet: 有次序
2. HashSet集合
2.1 HashSet的实现原理
是基于HashMap实现的,默认构造函数是构建一个初始容量为16,负载因子为0.75 的HashMap。封装了一个 HashMap 对象来存储所有的集合元素,所有放入 HashSet 中的集合元素实际上由 HashMap 的 key 来保存,而 HashMap 的 value 则存储了一个 PRESENT,它是一个静态的 Object 对象。
当我们试图把某个类的对象当成 HashMap的 key,或试图将这个类的对象放入 HashSet 中保存时,重写该类的equals(Object obj)方法和 hashCode() 方法很重要,而且这两个方法的返回值必须保持一致:当该类的两个的 hashCode() 返回值相同时,它们通过 equals() 方法比较也应该返回 true。通常来说,所有参与计算 hashCode() 返回值的关键属性,都应该用于作为 equals() 比较的标准。
HashSet的其他操作都是基于HashMap的。
思考:如何把同一个对象在HashSet集合中存入两次?
import java.util.*; public class HashSetDemo { public static void main(String[] args) { // 创建 HashSet对象 Set set = new HashSet(); //加入元素到 HashSet 中 set.add("F"); set.add("B"); set.add("D"); set.add("E"); set.add("C"); set.add("B");//当添加相同的元素时,因为Set集合的元素时唯一的,所以会覆盖之前的B System.out.println("set 最初的内容:" + set); //删除元素 set.remove("F"); System.out.println("set 被改变之后:" + set); } }输出结果:
通过上面的案例,我们发现Set的特点是无序的,元素是唯一的,我们放元素的顺序是”F—B—D—E--C”,而获取的顺序是”B-C-D-E-F”,同样我们发现Set集合的元素是唯一的,我们往Set集合放了两个”B”,但是在Set集合中只有一个”B”
2.2 hashCode()和equals()
因为hashCode()和equals()方法的返回值共同决定了两个对象是否相等,所以覆写着两个方法时一般要保证两个方法的返回值保证兼容。
重写hashCode()和equals()方法的基本规则:
1、 如果两个对象通过equals()方法比较时返回true,则两个对象的hashCode()方法返回值应该也相等。
2、 对象中用作equals()比较标准的成员变量(属性),也应该参与到hashCode的计算。
3. Iterator(迭代器)接口
3.1 Iterator概述
由于集合中存有很多元素,很多时候需要遍历集合中的所有元素,java专门为集合提供了遍历集合的API:迭代器接口
Iterator是专门的迭代输出接口。所谓的迭代输出就是将元素进行判断,判断是否有内容,如果有内容则把内容取出。
Iterator对象称作迭代器,用以方便的实现对集合内元素的遍历操作。
3.2 获得Iterator对象
调用集合对象的iterator()方法,可以获得一个与该集合对象关联的迭代器对象。
例如:
List<String> list = new ArrayList<>();
Iterator<String>iterator = list.iterator(); //获得Iterator对象
3.3 Iterator的常用方法
Iterator定义如下三个方法:
boolean hasNext(); //判断游标右边是否有元素。如果有返回true,否则false
Object next() ; //返回游标右边的元素并将游标移动到下一个位置
void remove(); //删除游标左面的元素(即next的时候跳过的对象)
注意:迭代方向是单向的,只能从前朝后(Iterator有个专为list集合设计的子接口ListIterator可以实现双向迭代)。
示例代码如下:
package cn.sz.gl.no1; import java.util.ArrayList; import java.util.Iterator; import java.util.List; public class TestIterator { public static void main(String[] args) { List<String> list = new ArrayList<>(); list.add("abc"); list.add("bcd"); Iterator<String> iterator = list.iterator(); while (iterator.hasNext()) {// 判断后面是否还有元素 String string = iterator.next(); // 获得下一个元素 System.out.println(string); } } }注意:在用迭代器遍历集合的时候,如果要删除集合中的元素,只能调用迭代器的remove(),禁止调用集合对象的rmove()方法,否则有可能会出现异常:
java.util.ConcurrentModificationException。//并发访问异常
多学一招:
对于List集合,我们知道List集合是有序的,也就是说,它会给每一个元素添加一个下标,那我们可以使用之前我们学过的for循环去遍历List集合,代码如下:
for(int i = 0; i < list.size(); i++) { System.out.println(list.get(i)); }
而对于Set集合,我们知道是无序的那我们就不能通过普通for换去获取它们的下标,因为Set元素没有下标,那就是我们不能使用普通for循环来遍历Set集合.
但是我们知道for循环还有一个增强的for循环,对于所有的集合我们可以使用增强for循环来遍历集合:
for(Object ele: list){ System.out.println(ele); }
3.TreeSet集合
3.1 TreeSet实现原理
TreeSet使用红黑树结构对加入的元素进行排序存放,所以放入TreeSet中元素必须是可“排序”的。
3.2 可排序的对象
TreeSet可是采用两种方法实现排序:自然排序和定制排序。默认情况,TreeSet采用自然排序。
TreeSet调用调用集合元素的CompareTo()方法,根据该方法的返回值来比较元素之间的大小,然后进行“升序”排列,这种排序方式我们称之为自然排列。
注意:如果想采用自然排序,则要存储的对象所属类必须实现Comparable 接口。该接口只有一个方法public int compareTo(Object obj),必须实现该方法。
compareTo方法的实现规则:
返回 0,表示 this == obj。//则不会添加新对象
返回正数,表示 this> obj //添加到原来对象的右边
返回负数,表示 this < obj // 添加到原来对的左边
public class Student implements Comparable<Student> { private String name; private int 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; } public Student(String name, int age) { this.name = name; this.age = age; } public Student() { } @Override public String toString() { final StringBuilder sb = new StringBuilder("Student").append('[') .append("name=") .append(name) .append(",age=") .append(age) .append(']'); return sb.toString(); } @Override public int compareTo(Student o) { //根据年龄升序排序 int rs = this.age - o.age; if(rs == 0 ){ if(o == this){ return 0; }else{ return -1; } } return rs; } }
public class TreeSetDemo { public static void main(String[] args) { TreeSet treeSet = new TreeSet(); treeSet.add(new Student("张三",21)); treeSet.add(new Student("李四",18)); treeSet.add(new Student("王五",20)); treeSet.add(new Student("李琦",19)); System.out.println(treeSet); } }
结果:
3.3 定制排序
使用Comparable接口定义排序顺序有局限性:实现此接口的类只能按compareTo()定义的这一种方式排序。
如果需要更加灵活地排序,我们可以自定义(Comparator)比较器,在创建TreeSet集合对象时把我们自定义的比较器传入,则可以TreeSet会按照我们的比较器中定义的规则进行排序。
自定义比较器类,需要实现Comparator接口。Comparator接口只有一个抽象方法需要实现:public int compare(Object a, Object b);
判断规则:
返回 0,表示a == b
返回正数,表示b > b
返回负数,表示a < b
创建TreeSet集合对象时,把自定义比较器对象传入即可,TreeSet会自动按照比较器中的规则进行排序。
public class TreeSetDemo { public static void main(String[] args) { TreeSet treeSet = new TreeSet(new Comparator() { @Override public int compare(Object o1, Object o2) { Student stu1 = (Student)o1; Student stu2 = (Student)o2; //按照年龄的降序排序 return stu2.getAge() - stu1.getAge() ; } }); treeSet.add(new Student("张三",21)); treeSet.add(new Student("李四",18)); treeSet.add(new Student("王五",20)); treeSet.add(new Student("李琦",19)); System.out.println(treeSet); } }
结果:
4. LinkedHashSet
根据元素的hashCode值来决定元素的存储位置,但是它同时使用链表维护元素的次序
LinkedHashSet集合特点:
底层是一个哈希表(数组+链表/红黑树)+链表,多了一条链表(记录元素的存储顺序),保证元素有序
遍历的时候,LinkedHashSet将会以元素的添加顺序访问集合的元素
LinkedHashSet的方法与它父类HashSet方法是一样的
public static void main(String[] args) { LinkedHashSet set = new LinkedHashSet(); set.add("hello"); set.add("word"); set.add("zhangsan"); set.add("ls"); set.add("zhangsan"); System.out.println(set); }
运行结果:
我们看到set元素输出的顺序与添加元素的顺序是一致的.
如果我们需要迭代的顺序为插入顺序或者访问顺序,并且保证元素的唯一.那么 LinkedHashSet 是需要你首先考虑的。