集合框架 Set的特点:无序,不可以重复元素。
一、HashSet相关知识
HashSet中的元素不可以重复,如果重复添加,则只会显示一个。
原理如下:
HashSet:底层数据结构是哈希表。是线程不安全的。不同步。
HashSet是如何保证元素唯一性的呢?
答:是通过元素的两个方法,hashCode和equals来完成。
如果元素的HashCode值相同,才会判断equals是否为true。如果元素的hashcode值不同,不会调用equals。
*******对于判断元素是否存在,以及删除等操作,依赖的方法是元素的hashcode和equals方法**********
代码1:
package com.package2; import java.util.*; public class HashSet222 { public static void main(String[] args) { HashSet hs = new HashSet(); hs.add("java01"); hs.add("java01"); hs.add("java02"); hs.add("java03"); hs.add("java03"); hs.add("java04"); Iterator it = hs.iterator(); while(it.hasNext()) { System.out.println(it.next()); } } }此demo将String类型的字符串添加进去,并且没有重复,结果如下:
java04
java02
java03
java01
由此我们可以断定,String类已经实现了hashcode()方法和equals()方法。打开,帮助文档,确实(这不是废话么^_^)
但是,如果我们要将自定义的元素add进HashSet中,则必须定义其自己的hashcode()方法和equals()方法。如下所示:
代码2:
package com.package2; import java.util.*; public class HashSet3 { public static void main(String[] args) { HashSet hs = new HashSet(); hs.add(new Person("a1",11)); hs.add(new Person("a2",12)); hs.add(new Person("a3",13)); hs.add(new Person("a2",12)); hs.add(new Person("a4",14)); Iterator it = hs.iterator(); while(it.hasNext()) { Person p = (Person)it.next(); System.out.println(p.getName()+"::"+p.getAge()); } } } class Person { private String name; private int age; Person(String name,int age) { this.name = name; this.age = age; } public int hashCode() { System.out.println(this.name+"....hashCode"); return name.hashCode()+age*37; //保证此元素的返回值尽量不一致。 } public boolean equals(Object obj) { if(!(obj instanceof Person)) return false; Person p = (Person)obj; System.out.println(this.name+"...equals.."+p.name); return this.name.equals(p.name) && this.age == p.age; } public String getName() { return name; } public int getAge() { return age; } }输出结果如下:
a1....hashCode
a2....hashCode
a3....hashCode
a2....hashCode
a2...equals..a2
a4....hashCode
a1::11
a3::13
a2::12
a4::14
由此可以看出,将元素add时,会首先调用元素的hashcode()方法,当返回值重复时,会调用其equals方法。缺少任何一种方法都构不成一个HashSet集合。
二、TreeSet相关知识
TreeSet有俩种排序方式。
TreeSet排序的第一种方式:让元素自身具备比较性。TreeSet的第二种排序方式。当元素自身不具备比较性时,或者具备的比较性不是所需要的。这时就需要让集合自身具备比较性(即利用其另一种构造函数建立对象)。在集合初始化时,就有了比较方式。
步骤:利用某个指定类实现Comparator接口,并且覆盖compare()方法,则此类会成为一个具备比较方法的类。在建立TreeSet的时候,将此类对象传入其中。
则此时,添加进TreeSet中的元素可按照指定比较方法进行排序。
下边举例演示俩种排序方式。
排序方式一:代码1:
package com.package1; import java.util.*; public class TreeSettest { public static void main(String[] args) { //创建对象 TreeSet ts=new TreeSet(); //添加元素 ts.add("abcjjj"); ts.add("abb"); ts.add("daccc"); ts.add("gfg"); ts.add("geee"); ts.add("r"); //进行迭代 Iterator it=ts.iterator(); //循环取出元素 while(it.hasNext()) { System.out.println(it.next()); } } }输出结果如下:
abb
abcjjj
daccc
geee
gfg
r
由此可见,此时元素具备可比性,即按照其自然顺序进行排序。打开帮助文档,我们可以清晰的发现String已经实现了Comparable接口,并且已经覆盖了compareTo(),“这不是废话么^_^”,如图所示:
ps……如果我们要往TreeSet里添加的元素是自己刚刚定义的,我们也可以自己定义该元素的类实现Comparator接口,并且覆盖compareTo()方法,如下所示:代码2:
package com.package1; import java.util.*; class Student implements Comparable//该接口强制让学生具备比较性。 { private String name; private int age; Student(String name,int age) { this.name = name; this.age = age; } public int compareTo(Object obj) { //return 0; if(!(obj instanceof Student)) throw new RuntimeException("不是学生对象"); Student s = (Student)obj; //System.out.println(this.name+"....compareto....."+s.name); if(this.age>s.age) return 1; if(this.age==s.age) { return this.name.compareTo(s.name); } return -1; /**/ } public String getName() { return name; } public int getAge() { return age; } } public class TreeSet2 { public static void main(String[] args) { TreeSet ts = new TreeSet(); ts.add(new Student("lisi02",22)); ts.add(new Student("lisi02",21)); ts.add(new Student("lisi007",20)); ts.add(new Student("lisi09",19)); ts.add(new Student("lisi06",18)); ts.add(new Student("lisi06",18)); ts.add(new Student("lisi007",29)); //ts.add(new Student("lisi007",20)); //ts.add(new Student("lisi01",40)); Iterator it = ts.iterator(); while(it.hasNext()) { Student stu = (Student)it.next(); System.out.println(stu.getName()+"..."+stu.getAge()); } } }
lisi06...18
lisi09...19
lisi007...20
lisi02...21
lisi02...22
lisi007...29
此例子将学生元素,强行按照年龄来排序,这就是我们想要的排序方式。
第二种排序如下:
还是刚刚代码1的例子,我们使用第二种排序方式,使得String类型的元素按照长度来排序。
代码3:
/* * 使元素按照长度来排序,若长度相同,则按照自然排序。 */ public class TreeSettest { public static void main(String[] args) { //创建对象 TreeSet ts=new TreeSet(new MyCompare()); //添加元素 ts.add("abc"); ts.add("bcc"); ts.add("das"); ts.add("bcde"); ts.add("asdfg"); ts.add("befqfca"); //进行迭代 Iterator it=ts.iterator(); //循环取出元素 while(it.hasNext()) { System.out.println(it.next()); } } } //定义一个类实现Comparator接口,并且覆盖compare()方法。 class MyCompare implements Comparator { @Override public int compare(Object o1, Object o2) { //进行强制类型转换 String s1=(String) o1; String s2=(String) o2; //进行比较 if(s1.length()>s2.length()) return 1; if(s1.length()<s2.length()) return -1; if(s1.length()==s2.length()) { return s1.compareTo(s2); } return 0; } }结果如下:
abc
bcc
das
bcde
asdfg
befqfca
此种情况下,我们不方便修改源代码,而只需要修改比较方法时,我们就可以自己创建一个比较器。在建立TreeSet时,将比较器传入即可使元素按照特定比较方式输出。
总结:
Comparable(方式一)接口和Compartor(方式二)接口的比较:
两种方法各有优劣, 用Comparable 简单, 只要实现Comparable 接口的对象直接就成为一个可以比较的对象,但
是需要修改源代码。
用Comparator 的好处是不需要修改源代码, 而是另外实现一个比较器, 当某个自定义的对象需要作比较的时候,把
比较器和对象一起传递过去就可以比大小了, 并且在Comparator 里面用户可以自己实现复杂的可以通用的逻辑,使其
可以匹配一些比较简单的对象,那样就可以节省很多重复劳动了。