Set集合
Set接口,存储无序的、不可重复性的元素
一:HashSet主要实现类
注:1.无序性!=随机性,以后每一次运行都会是这个位置。存储位置离散不连续
2.不可重复性:向Set中添加相同的元素时,下一个相同的元素添加不进去;(要求添加进Set元素的类,一定要重写equals和hashCode方法,进而保证Set元素的不可重复性)
不可重复性:Set元素如何存储保证不可重复性? 使用哈希算法。
哈希算法:当向set中添加对象时,首先调用此对象所在类的hasCode()方法,计算此对象的hasCode值,此哈希值决定了此对象在set中的存储位置,
若此位置之前没有对象存储,则这个对象直接存到此位置;若此位置已有对象存储,再通过equals方法比较这两个对象是否相同;
如果相同,后一个对象就不能再添加进来;万一返回false,要求hasCode方法要与equals方法一致;
(自己的理解:相当于一个大教室有n个座位号,进来一个学生需要判断该学生对象的座位(hasCode)有没有人,如果座位为空,直接入座。如果有人已经做了,再次调用equals方法比较两个人是不是一个号,如果是,那么只能坐一个人。如果不是,一般不建议这样;所以要求一个学生对应一个号!即hasCode方法要和equals方法保持一致,两个都需要重写)
实例:
package Exercise2;import java.util.HashSet; import java.util.Set; public class TestSet { public static void main(String[] args) { testHashSet(); } public static void testHashSet(){ Set s=new HashSet(); s.add(123); s.add(45); s.add("aa"); s.add(null); Person p1=new Person("laura",21); System.out.println("p1的hasCode:"+p1.hashCode()); Person p2=new Person("la",21); System.out.println("p2的hasCode:"+p2.hashCode()); s.add(p1); s.add(p2); System.out.println(s.size()); System.out.println(s);//重写了toString()方法 } }
若为两个相同的对象(前提需要该类重写equals和hasCode方法):
Person类中equals和hasCode方法的重写:
@Override public boolean equals(Object o) { if (this == o) return true; if (!(o instanceof Person)) return false; Person person = (Person) o; return Objects.equals(getName(), person.getName()) && Objects.equals(getAge(), person.getAge()); } @Override public int hashCode() { final int prime=31; int result=1; result=prime*result+((age==null)? 0:age.hashCode()); result=prime*result+((name==null)? 0:name.hashCode()); return result; }
二:LinkedHashSet 链表的方式实现Set集合
注: 为什么按照存进去的方式打印出来,不是Set集合具有无序性嘛?
Set集合的无序性指的是存储的元素集合在内存中无序(物理地址无序),但是打印的时候是按照链表的方式,从头到尾链起来,这也是区别于HashSet类的地方。
对比:LinkedHashSet插入性能略低于HashSet(需要同时维护链表),但在迭代访问Set里的全部元素时有很好的性能。
LinkedHashSet继承自HashSet,其底层是基于LinkedHashMap来实现的,是一个有序(插入和取出顺序一致),没有重复元素,非同步的集合,允许插入null值。其底层结构:同LinkedHashMap,是基于哈希表和双向链表结构。
LinkedHashSet集合同样是根据元素的hashCode值来决定元素的存储位置,但是它同时使用链表维护元素的次序。这样使得元素看起来像是以插入顺序保存的,也就是说,当遍历该集合时候,LinkedHashSet将会以元素的添加顺序访问集合的元素。
三:TreeSet
TreeSet是一个不允许重复的无序(此处说的无序是指插入和取出不一致,但输出时是按从小到大的顺序)集合,其底层是基于TreeMap(使用的是红黑树)实现的,非同步的。TreeSet可以确保集合元素处于排序状态。TreeSet支持两种排序方式,自然排序和定制排序,其中自然排序为默认的排序方式。当我们构造TreeSet时,若使用不带参数的构造函数,则TreeSet的使用自然比较器;若用户需要使用自定义的比较器,则需要使用带比较器的参数。
1.向TreeSet中添加的元素必须是同一类型的(区别于前两个)
2.自然排序:可以按照添加进元素的指定顺序排序,像包装类String类(a-z)等有默认的排序方式。
如果是自定义类,则该类可以通过实现Comparable接口并重写compareTo方法实现按照特定的属性排序.
如想要按照Person类的年龄排序,测试方法:
public static void testTreeSet1(){ Set s=new TreeSet(); //当Person类没有实现Comparable接口时,不能实现比较 //Exception in thread "main" java.lang.ClassCastException: Exercise2.Person cannot be cast to java.lang.Comparable s.add(new Person("aa",21)); s.add(new Person("bb",23)); s.add(new Person("cc",22)); s.add(new Person("dd",26)); s.add(new Person("ee",20)); s.add(new Person("ff",20)); Iterator i=s.iterator(); while (i.hasNext()){ System.out.print(i.next()+" "); } }
Person类:
//当向TreeSet中添加Person类对象时,依据此方法,确定按照那个属性排列 @Override public int compareTo(Object o) { if(o instanceof Person){ Person p=(Person)o; //如果想从大到小排,整个负号return -this.age.compareTo(p.age); return this.age.compareTo(p.age);//因为age是Integer属性的,可以直接调用该类的compareTo方法 } return 0; }
但是如果有两个仅有年龄一样的,这样会产生的问题是后一个进来的对象就会被以为是相同对象而不会被放入集合,而这时明明是两个不同的集合:
所以需要改进compareTo方法,即在一个属性相同的情况下,继续比较另一个属性:
所以,要保证compareTo方法、hasCode方法和equals方法一致
3.定制排序:实现Comparator接口,重写compare方法
但是如果仅有两个相同的id,就会产生和之前相同的问题,所以当两个id一样时,还需要判断name
还可以添加匿名对象:
注:开发过程中,如果可以去修改这个类,那么就实现Comparable接口实现自然排序;若该类不能修改,则使用定制排序通过Comparator接口实现。如果两个接口同时重写,则按照定制接口优先。(即Comparator接口规定的顺序去排,一般不会这样...)
Comparable和Comparator接口详细应用见博客: 泛型以及Comparable、Comparator接口
附完整代码:
package Exercise2; import java.util.*; /** * 2020/1/14.15.16 * Set接口,存储无序的、不可重复的元素,Set中常用的方法都是Collection下定义的 * Set元素如何存储?使用哈希算法。 * 哈希算法:当向set中添加对象时,首先调用此对象所在类的hasCode()方法,计算此对象的hasCode值,此哈希值决定了此对象在set中的存储位置, * 若此位置之前没有对象存储,则这个对象直接存到此位置;若此位置已有对象存储,再通过equals方法比较这两个对象是否相同, * 如果相同,后一个对象就不能再添加进来; * 万一返回false,要求hasCode方法要与equals方法一致; * * HashSet (主要实现类) * LinkedHashSet */ public class TestSet { public static void main(String[] args) throws Exception { // testHashSet(); //testLikedHashSet(); testTreeSet3(); } /** * 1.向TreeSet添加的元素必须是同一个类的 * 2.可以按照添加进集合中的元素指定的顺序遍历,像String等包装类默认按照从小到大的顺序排列 * 3.当向TreeSet中添加自定义类型的对象时,有两种排序方法:自然排序、定制排序 * 4.自然排序:要求自定义类实现java.lang.Comparable,并重写compareTo方法,在此方法中,指明按照自定义类的哪个属性排序 * 5.向TreeSet中添加元素时,首先按照compareTo方法比较;一旦返回0,虽然仅是这两个对象的此属性值相同, * 但是程序会认为这两个对象是相同的,进而后一个对象就不能添加进来。 * 所以:要保证compareTo方法和hasCode方法和equals方法保持一致! */ public static void testTreeSet1() throws Exception { Set s=new TreeSet(); // s.add(123); // s.add(45); // s.add("aa") //当Person类没有实现Comparable接口时,不能实现比较 //Exception in thread "main" java.lang.ClassCastException: Exercise2.Person cannot be cast to java.lang.Comparable s.add(new Person("aa",21)); s.add(new Person("bb",23)); s.add(new Person("cc",22)); s.add(new Person("dd",26)); s.add(new Person("ee",20)); s.add(new Person("ff",20)); Iterator i=s.iterator(); while (i.hasNext()){ System.out.print(i.next()+" "); } } public static void testTreeSet3(){ //直接向Set集合中添加匿名对象Comparator接口 TreeSet set=new TreeSet(new Comparator() { @Override public int compare(Object o1, Object o2) { if(o1 instanceof Customer&&o2 instanceof Customer){ Customer c1=(Customer)o1; Customer c2=(Customer)o2; int i= c1.getId().compareTo(c2.getId()); if(i==0){ return c1.getName().compareTo(c2.getName()); }else { return i; } } return 0; } }); //3.向集合中添加Comparator接口中的涉及的类的对象(本次只能是Customer类) set.add(new Customer("aa",1001)); set.add(new Customer("bb",1004)); set.add(new Customer("cc",1005)); set.add(new Customer("dd",1008)); set.add(new Customer("rr",1008)); Iterator i=set.iterator(); while (i.hasNext()){ System.out.print(i.next()+" "); } } /** * 定制排序:实现 Comparator接口 */ public static void testTreeSet2(){ //1.向TreeSet中添加Custom类对象,指明是按照Custom哪个属性排序的 Comparator com=new Comparator() { @Override public int compare(Object o1, Object o2) { if(o1 instanceof Customer&&o2 instanceof Customer){ Customer c1=(Customer)o1; Customer c2=(Customer)o2; int i= c1.getId().compareTo(c2.getId()); if(i==0){ return c1.getName().compareTo(c2.getName()); }else { return i; } } return 0; } }; //2.将此对象作为形参传递给TressSet构造器 TreeSet set=new TreeSet(com); //3.向集合中添加Comparator接口中的涉及的类的对象(本次只能是Customer类) set.add(new Customer("aa",1001)); set.add(new Customer("bb",1004)); set.add(new Customer("cc",1005)); set.add(new Customer("dd",1008)); set.add(new Customer("rr",1008)); Iterator i=set.iterator(); while (i.hasNext()){ System.out.print(i.next()+" "); } } public static void testLikedHashSet(){ Set s=new LinkedHashSet(); s.add(123); s.add(45); s.add("aa"); s.add(null); Iterator i=s.iterator(); while (i.hasNext()){ System.out.print(i.next()+" "); } } /** * 1.无序性!=随机性 (第一次随机出现,后面运行都是该序列) * 2.无序性指的是元素在内存中存储的位置是无序的(离散的,不像List开辟一块空间存储) * 3,不可重复性:向Set中添加相同的元素时,下一个相同的元素添加不进去; * 注:要求添加进Ste元素的类,一定要重写equals和hashCode方法,进而保证Set元素的不可重复性 */ public static void testHashSet(){ Set s=new HashSet(); s.add(123); s.add(45); s.add("aa"); s.add(null); // Person p1=new Person("laura",21); // System.out.println("p1的hasCode:"+p1.hashCode()); // Person p2=new Person("laura",21); // System.out.println("p2的hasCode:"+p2.hashCode()); // System.out.println(p1.equals(p2)); /** * p1,p2指向不同的地址,所以都能添加进去 * 但是如果该类重写了equals和hashCode方法,并且属性的值都相同,那么即为相同对象 */ // s.add(p1); // s.add(p2); //System.out.println(s.size()); System.out.println(s);//重写了toString()方法 } }
对应的Person类:
package Exercise2; import java.lang.reflect.Field; import java.util.Objects; public class Person implements Comparable{ private String name; private Integer age; public String getName() { return name; } public void setAge(Integer age) { this.age = age; } public Integer getAge() { return age; } public void setName(String name) { this.name = name; } @Override public String toString() { return "Person{" + "name='" + name + '\'' + ", age=" + age + '}'; } public Person(String name,Integer age) { this.name = name; this.age=age; } @Override public boolean equals(Object o) { if (this == o) return true; if (!(o instanceof Person)) return false; Person person = (Person) o; return Objects.equals(getName(), person.getName()) && Objects.equals(getAge(), person.getAge()); } @Override public int hashCode() { final int prime=31; int result=1; result=prime*result+((age==null)? 0:age.hashCode()); result=prime*result+((name==null)? 0:name.hashCode()); return result; } //当向TreeSet中添加Person类对象时,依据此方法,确定按照那个属性排列 @Override public int compareTo(Object o) { if(o instanceof Person){ Person p=(Person)o; //如果想从大到小排,整个负号return -this.age.compareTo(p.age); //return this.age.compareTo(p.age);//因为age是Integer属性的,可以直接调用该类的compareTo方法 //如果开始的age声明成了小的int类型,那么就两个相减 // return this.age-p.age; int i=this.age.compareTo(p.age); if(i==0){ return this.name.compareTo(p.name); }else { return i; } } return 0; } }
Custom实现类:
package Exercise2; import java.util.Objects; public class Customer { private String name; private Integer id; public String getName() { return name; } public void setName(String name) { this.name = name; } public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public Customer(String name,Integer id) { this.name = name; this.id=id; } @Override public String toString() { return "Customer{" + "name='" + name + '\'' + ", id=" + id + '}'; } @Override public boolean equals(Object o) { if (this == o) return true; if (!(o instanceof Customer)) return false; Customer customer = (Customer) o; return Objects.equals(getName(), customer.getName()) && Objects.equals(getId(), customer.getId()); } @Override public int hashCode() { return Objects.hash(getName(), getId()); } }
练习:1,定义一个Employee类,包含name,age,birthday;其中birthday为MyDate类的对象,重写toString方法来输出
2.定义一个MyDate类,私有成员变量month,day,year;创建该类的5个对象,并按这些对象放入TreeSet集合中,分别按照以下两种方式对集合中的元素进行排序,并遍历输出。
1).使Employee实现Comparable接口,并按照name排序
2).创建TreeSet时传入Comparator对象,按照生日日期先后排序。
注:Employee类要实现Comparable接口,重写compareTo方法;同时两个类都要重写hasCode和equals方法
@Override public int compareTo(Object o) { if(o instanceof Employee){ Employee e=(Employee)o; int i= this.getName().compareTo(e.getName()); if(i==0){ return this.getAge().compareTo(e.getAge()); }else { return i; } } return 0; }
package Exercise2; import java.util.Comparator; import java.util.Iterator; import java.util.TreeSet; public class TestEmployee { public static void test1() { Employee e1 = new Employee("LA", 20, new MyDate(2000, 1, 23)); Employee e2 = new Employee("UU", 23, new MyDate(1997, 4, 25)); Employee e3 = new Employee("BA", 24, new MyDate(1996, 6, 03)); Employee e4 = new Employee("DT", 23, new MyDate(1997, 9, 27)); Employee e5 = new Employee("UR", 22, new MyDate(1998, 8, 21)); TreeSet set = new TreeSet(new Comparator(){ public int compare(Object o1, Object o2) { if (o1 instanceof Employee && o2 instanceof Employee) { Employee e1 = (Employee) o1; Employee e2 = (Employee) o2; MyDate bir1 = e1.getBirthday(); MyDate bir2 = e2.getBirthday(); //涉及到年、月、日 int i = bir1.getYear().compareTo(bir2.getYear()); int j = bir1.getMonth().compareTo(bir2.getMonth()); int z = bir1.getDay().compareTo(bir2.getDay()); if (i != 0) { return i; } else if (j != 0) { return j; } else if (z != 0) { return z; } } return 0; } }); set.add(e1); set.add(e2); set.add(e3); set.add(e4); set.add(e5); Iterator i = set.iterator(); while(i.hasNext()) { System.out.println(i.next()); } } public static void test2(){ Employee e1 = new Employee("LA", 20, new MyDate(2000, 1, 23)); Employee e2 = new Employee("UU", 23, new MyDate(1997, 4, 25)); Employee e3 = new Employee("BA", 24, new MyDate(1996, 6, 03)); Employee e4 = new Employee("DT", 23, new MyDate(1997, 4, 27)); Employee e5 = new Employee("UU", 22, new MyDate(1998, 8, 21)); TreeSet set = new TreeSet(); set.add(e1); set.add(e2); set.add(e3); set.add(e4); set.add(e5); Iterator i = set.iterator(); while(i.hasNext()) { System.out.println(i.next()); } } public static void main(String[] args) { test1(); System.out.println("---------------"); test2(); } }