集合框架
Set 接口
Set 具有与 Collection 完全一样的接口,只是行为上不同,Set 不保存重复的元素,而且Set接口中元素无序。
什么是哈希表呢?
哈希表底层使用的也是数组机制,数组中存放对象,这些对象存放的位置比较特殊,当把这些对象存入数组中时,会根据这些对象的特有数据结合相应的算法(hashCode),计算出这个对象在数组中的位置,然后把这个对象存放在数组中。而这样的数组就称为哈希数组,即就是哈希表。
当向哈希表中存放元素时,会调用 Object类中的 hashCode方法。由于任何对象都是Object类的子类,所以任何对象有拥有这个方法。
这里需要注意,如果两个对象 hashCode方法算出结果一样,这样现象称为哈希冲突,这时会调用对象的equals 方法,比较这两个对象是不是同一个对象,如果equals方法返回的是 true,那么就不会把第二个对象存放在哈希表中,如果返回的是 false,就会把这个值存放在哈希表中。
保证Set集合元素的唯一,其实就是根据对象的 hashCode和 equals方法来决定的。如果我们往集合中存放自定义的对象,那么保证其唯一,就必须复写 hashCode 和 equals方法建立属于当前对象的比较方式。
示例代码
public class Test01 {
public static void main(String[] args) {
// TODO Auto-generated method stub
//实例化一个Set对象
Collection<String> c = new HashSet<>();
c.add("134");
c.add("43");
c.add("56");
c.add("75");
c.add("123");
c.add("109");
c.add("134");//存储重复元素
for(String a : c){
System.out.println(a);
}
//输出结果与输入数据不相同,且不能输入相同数据
}
}
HashSet存储自定义对象
当向HashSet集合中添加一个对象时,首先会调用该对象的 hashCode() 方法来确定元素的存储位置,然后再调用对象的equals() 方法来确定该位置有没有重复元素。
特点:不包含重复元素,自定义的类必须重写hashCode、equals方法 当使用HashSet存储自定义对象时,我们要重写 hashCode()和 equals()方法
因为:
当调用HashSet的 add()方法,会先判断 hashCode()是否相同, 如果相同,就会再判断 equals(),如果 equals()也相同,那么就不存
代码示例
public class Test02 {
public static void main(String[] args) {
// TODO Auto-generated method stub
// 实例化一个 Set
Set<Student> stuSet = new HashSet<>();
// 填充元素
stuSet.add(new Student("刘德华", 20));
stuSet.add(new Student("张学友", 22));
stuSet.add(new Student("黎明", 24));
stuSet.add(new Student("郭富城", 26));
// 添加重复元素
stuSet.add(new Student("郭富城", 26));
// 遍历
for (Student stu : stuSet) {
System.out.println(stu.getName() + " " + stu.getAge());// 取出时,跟存入的顺序不同;
}
}
}
class 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) {
super();
this.name = name;
this.age = age;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + age;
result = prime * result + ((name == null) ? 0 : name.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Student other = (Student) obj;
if (age != other.age)
return false;
if (name == null) {
if (other.name != null)
return false;
} else if (!name.equals(other.name))
return false;
return true;
}
}
注意:可用快捷键生成
LinkedHashSet
LinkedHashSet 是一个特例集合,元素存取有序,使用链表维护元素的顺序,这样使得元素看起来是以插入的顺序保存的,取出时会按照元素的添加顺序来访问集合里的元素。
示例代码
public class Test03 {
public static void main(String[] args) {
// TODO Auto-generated method stub
LinkedHashSet<String> lh = new LinkedHashSet<>();
lh.add("张三");
lh.add("王五");
lh.add("李四");
lh.add("赵六");
lh.add("燕七");
lh.add("燕七");
for (String s : lh) {
System.out.println(s);
}
}
}
TreeSet集合
TreeSet 因为实现了 Set 接口,所以其集合特点是:元素不重复。TreeSet 里面存放的元素是按照元素的自然顺序来排列的 。
代码示例
public class Test04 {
public static void main(String[] args) {
// 实例化一个 TreeSet
TreeSet<String> strSet = new TreeSet<>();
// 填充集合
strSet.add("ddd");
strSet.add("aaa");
strSet.add("xxx");
strSet.add("ccc");
strSet.add("bbb");
strSet.add("yyy");
strSet.add("fff");
// 遍历
for (String s : strSet) {
System.out.println(s);
}
}
}
TreeSet 内部采用平衡二叉树来存储元素,这样的结构可以保证 TreeSet 集合中没有重复的元素,并且可以对元素进行排序,其保证元素唯一性是通过元素的 compareTo 方法的返回值来确定
如存入: 22 20 25 24 19 20 26
1. 把第一个数据看成是根节点,从第二个数据开始,每个数据都从根节点开始比较
大于0: 就往右放
小于0: 就往左放
等于0: 说明是重复的元素
- 遍历从根节点开始看,按照左中右方式遍历
TreeSet类存储自定义对象
集合数据在进行比较时,都会调用 compareTo()方法,该方法是 Comparable 接口中定义的,因此想要对集合中的数据进行排序,就必须实现 Comparable接口
示例代码:新建学生类,并将学生对象存入TreeSet集合中
public class Test05 {
public static void main(String[] args) {
TreeSet<Student> stuSet = new TreeSet<>();
stuSet.add(new Student("张三", 20));// TreeSet的 add()方法中,自动调用元素的compareTo()方法
stuSet.add(new Student("李四", 22));
stuSet.add(new Student("王五", 24));
stuSet.add(new Student("赵六", 22));
// 遍历
for (Student stu : stuSet) {
System.out.println(stu.getName() + "," + stu.getAge());
}
}
}
注意:此代码会报错,无法排序。
使用自然排序(Comparable)接口
TreeSet 在进行数据存储时,都会对该对象进行排序,但是集合不知道该以什么样的方式进行排序,所以自定义类中需要实现Comparable接口,并在 compareTo 方法内实现排序比较。该方法用来比较元素的大小关系,然后将集合元素按照升序排列。
- compareTo(T o) 方法
将此对象与指定的对象进行比较,以返回一个负整数、零或一个正整数,因为这个对象小于、等于或大于指定的对象。
参数 : o要比较的对象。
结果 : 一个负整数,零,或一个正整数,因为这个对象小于,等于,或大于指定的对象。
示例代码 :根据年龄比较
public class Test05 {
public static void main(String[] args) {
TreeSet<Student> stuSet = new TreeSet<>();
stuSet.add(new Student("张三", 20));// TreeSet的 add()方法中,自动调用元素的compareTo()方法
stuSet.add(new Student("李四", 22));
stuSet.add(new Student("王五", 24));
stuSet.add(new Student("赵六", 22));
// 遍历
for (Student stu : stuSet) {
System.out.println(stu.name+ "," + stu.age);
}
}
}
class Student implements Comparable<Student>{
String name;
int age;
public Student(String name, int age) {
super();
this.name = name;
this.age = age;
}
//obj1.comparTo(obj2),如果该方法返回0,则表明这两个对象相等;如果返回一个正整数,则表明obj1大于 obj2;如果该方法返回一个负整数,则表明obj1小于obj2.
@Override
public int compareTo(Student o) {
System.out.println("this " + name);
System.out.println("o " + o.name);
//如果该方法返回 0则表明这两个对象相等
//如果返回一个正整数,则表明当前对象大于 o
//如果该方法返回一个负整数,则表明当前对象小于 o
return this.age - o.age;
}
}
上面比较的代码还有一点小问题:因为根据年龄进行比较的,如果数据中出现了年龄相同的两个对象,那么就会有不添加的效果,所以最完整的比较应该是:先比较年龄,如果年龄相同,那么对姓名进行排序,如果名字也相同,那么认为是相同的对象,不添加,
下面为排序代码:
示例代码
public class Test05 {
public static void main(String[] args) {
TreeSet<Student> stuSet = new TreeSet<>();
stuSet.add(new Student("张三", 20));// TreeSet的 add()方法中,自动调用元素的compareTo()方法
stuSet.add(new Student("李四", 22));
stuSet.add(new Student("王五", 24));
stuSet.add(new Student("赵六", 22));
// 遍历
for (Student stu : stuSet) {
System.out.println(stu.name+ "," + stu.age);
}
}
}
class Student implements Comparable<Student>{
String name;
int age;
public Student(String name, int age) {
super();
this.name = name;
this.age = age;
}
//obj1.comparTo(obj2),如果该方法返回0,则表明这两个对象相等;如果返回一个正整数,则表明obj1大于 obj2;如果该方法返回一个负整数,则表明obj1小于obj2.
@Override
// public int compareTo(Student o) {
// System.out.println("this " + name);
// System.out.println("o " + o.name);
// //如果该方法返回 0则表明这两个对象相等
// //如果返回一个正整数,则表明当前对象大于 o
// //如果该方法返回一个负整数,则表明当前对象小于 o
// return this.age - o.age;
// }
public int compareTo(Student o) {
System.out.println("this.name = " + this.name + " o.name = " + o.name);
//1.先按姓名排序
int n1 = this.name.compareTo(o.name);
//2.如果姓名相同,按年龄排序
int n2 = (n1 == 0 ? this.age - o.age : n1);
return n2;
}
}
注意:只要放在 TreeSet 中的元素对象,在该对象的类中必须实现 Comparable 接口,必须覆盖该接口中的compareTo()方法,并在该方法中编写比较规则。
通过构造方法传入排序比较器
比较器排序,也叫定制排序通过使用该比较器,比较时需要创建第三方类,实现 Comparator接口,并且覆盖其中的 compare()方法,编写比较规则和排序方式。
方法int compare(T o1, T o2)
比较其两个顺序的参数。返回一个负整数、零或一个正整数作为第一个参数小于、等于或大于第二个参数。
比较器通过构造方法传入
示例代码
public class Test06 {
public static void main(String[] args) {
// TODO Auto-generated method stub
// 实例化一个 TreeSet
TreeSet<Student> stuSet = new TreeSet<>(new MyComparator());
stuSet.add(new Student("李四", 20));
stuSet.add(new Student("张三", 22));
stuSet.add(new Student("王五", 24));
for (Student stu : stuSet) {
System.out.println(stu.name + "," + stu.age);
}
}
}
// 排序比较器
class MyComparator implements Comparator<Student> {
public int compare(Student o1, Student o2) {
System.out.println("o1.name = " + o1.name + " o2.name = " + o2.name);
// 先按姓名比
int n1 = o1.name.compareTo(o2.name);
// 如果姓名相同,比较年龄
int n2 = (n1 == 0 ? o1.age - o2.age : n1);
return n2;
}
}
总结
- 当向TreeSet里面添加不是自定义对象时,元素会默认使用自然排序输出从小到大序列。(因为Java提供的对象实现了比较的接口)
- 当向TreeSet里面添加自定义的对象时,可以使用这2比较方式进行比较,效果上没有区别。