赞
踩
先看看 Set 系列集合的位置:
Set接口中的方法上基本上与Collection的API一致。
Collection 是单列集合的祖宗接口,它的功能是全部单列集合都可以使用的。
回顾一下:
int index=( 数组长度-1 ) & 哈希值
所以说第一个元素存入的位置不一定是 0 索引处,如下
获取元素时就从左到右遍历,所以说 HashSet 是无序的
如果集合中存储的是自定义对象 ,必须要重写 hashCode 和 equals 方法(有的类已经重写过了,如 String Integer,会自动去重)不然 操作的都是地址值(一般来说,我们对于地址值是没有需求的)。
创建 Student 类–此时未重写 hashCode 和 equals 方法
public Student{
private String name;
private int age;
//构造方法+set+get
//此时未重写hashCode 和 equals 方法
}
创建测试类
Student1 s1=new Student("zhangsan",23);
Student1 s2=new Student("lisi",24);
Student1 s3=new Student("zhangsan",23);//已重复,不应该存入
//创建HashSet集合
HashSet <Student>set=new HashSet<>();
//重写hashCode和equals前,都能添加成功,这不是我们想要的
System.out.println(set.add(s1));//true
System.out.println(set.add(s2));//true
System.out.println(set.add(s3));//true
首先来说一下:为什么 s3 能添加成功:
因为此时在 Student 类中还未重写 hashCode ,所以使用的是地址值来获取的哈希值,由于不同对象地址值肯定不同,所以 s1 和 s3 被存在不同的位置上
此时在 Student 类中重写 hashCode 和 equals 方法(alt 和 insert 快捷键)
......
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Student1 student1 = (Student1) o;
return age == student1.age && Objects.equals(name, student1.name);
}
@Override
public int hashCode() {
return Objects.hash(name, age);
}
再来看测试类
Student1 s1=new Student("zhangsan",23);
Student1 s2=new Student("lisi",24);
Student1 s3=new Student("zhangsan",23);//属性重复,不应该存入
//创建HashSet集合
HashSet <Student>set=new HashSet<>();
/*重写hashCode和equals后,s3添加失败。 */
System.out.println(set.add(s1));//true
System.out.println(set.add(s2));//true
System.out.println(set.add(s3));//false
说明一下此时为什么 s3 能添加成功:
重写 hashCode 后,通过属性来获取哈希值,而 s1 和 s3 的属性一样,所以会有一样的哈希值,所以存入的位置一样,此时就体现重写 equals 的作用了,s3 会和 s1 属性比较,发现一样,则不存
。
这个例子中,重写的 equals 方法拦截了相同哈希值,相同属性的对象的存入(实现了去重)
有时又不会拦截,如下哈希碰撞情况
它们有相同的哈希值,会放入同一个位置,重写的 equals 方法会比较它们的属性值,发现不一样,所以 acD 会挂在 abc 的下面,形成链表。
在集合体系中的位置:是 HashSet 的子类
底层数据结构依旧是哈希表(是 HashSet 的子类)
使用双链表记录添加顺序
如图:遍历时就按记录的添加顺序来获取元素,
注意点:
在以后如果要数据去重,我们使用 HashSet 还是 LinkedHashSet?
默认使用 HashSet,如果 要求去重 且 存取有序,才使用LinkedHashSet
要知道:HashSet比LinkedHashSet效率更高
在集合体系中的位置
Tree Set集合底层是基于红黑树的数据结构实现排序的,
删改查性能都较好。
排序练习题:
//创建TreeSet集合对象 TreeSet<Integer>treeSet=new TreeSet<>(); treeSet.add(1); treeSet.add(4); treeSet.add(2); treeSet.add(5); treeSet.add(3); //自然排序 默认从小到大排序 //1.迭代器 Iterator<Integer> it = treeSet.iterator(); while (it.hasNext()){ int i=it.next();//jdk5后自动装箱,拆箱 System.out.print(i+" ");//1 2 3 4 5 } System.out.println(); //2.增强for for (Integer i : treeSet) { System.out.print(i+" "); } System.out.println(); //3.lambda treeSet.forEach(i-> System.out.print(i+" "));
控制台:
1 2 3 4 5
1 2 3 4 5
1 2 3 4 5
TreeSet_对象自然排序_练习题:
要求:
1.根据年龄排序
可知 可以通过 自然排序 解决。又因为是自定义类,所以要手动实现Comparable接口和compareTo()方法,否则找不到排序方法,报错
Student 类
public class Student {
private String name;
private int age;
//构造方法+set+get+toString
}
测试类
public class Test { public static void main(String[] args) { //创建对象 Student stu1 = new Student("zhangsan", 23); Student stu2 = new Student("lisi", 24); Student stu3 = new Student("wangwu", 25); Student stu4 = new Student("zhaoliu", 26); //创建集合 TreeSet<Student> ts = new TreeSet<>(); //添加对象 ts.add(stu3); ts.add(stu2); ts.add(stu1); ts.add(stu4); //打印集合 System.out.println(ts); } }
此时打印会报错,因为集合内是自定义类,要手动给出排序方式:
步骤:
给出排序方式后:Student 类
package com.lt.treeset; public class Student implements Comparable<Student>{ private String name; private int age; ..... public String toString() { return "Student{name = " + name + ", age = " + age + "}"; } //this:表示当前要添加的元素 //o:表示已经在红黑树存在的元素 //返回值: //负数:表示当前要添加的元素是小的,存左边 //正数:表示当前要添加的元素是大的,存右边 //0:表示当前要添加的元素已经存在,含弃 @Override public int compareTo(Student o) { //只看年龄,升序,(降序就调换位置即可) return this.getAge()-o.getAge(); } }
测试类
public class Test { public static void main(String[] args) { //创建对象 Student stu1 = new Student("zhangsan", 23); Student stu2 = new Student("lisi", 24); Student stu3 = new Student("wangwu", 25); Student stu4 = new Student("zhaoliu", 26); //创建集合 TreeSet<Student> ts = new TreeSet<>(); //添加对象 ts.add(stu3); ts.add(stu2); ts.add(stu1); ts.add(stu4); //打印集合 System.out.println(ts); } }
控制台:
[Student{name = zhangsan, age = 23}, Student{name = lisi, age = 24}, Student{name = wangwu, age = 25}, Student{name = zhaoliu, age = 26}]
**若改变一下题目:
年龄相同则比较字母大小。
CompareTo 就可以这样写
其实元素存储的原理就是红黑树:
我们针对年龄来演示:
添加顺序:
红黑树添加规则:
-----开始:
创建TreeSet对象时候,传递比较器Comparator指定规则
练习:
要按照字符串长度来比较,用自然排序无法比较,所以要使用自定义
步骤
public class Test02 { public static void main(String[] args) { //创建集合 TreeSet<String> ts = new TreeSet<>(new Comparator<String>() { @Override //o1:表示当前要添加的元素 //o2表示已经在红黑树存在的元素 public int compare(String o1, String o2) { //按照长度排序 int i = o1.length() - o2.length(); //如果一样长则按照首字母排序 if (i == 0) { //调用默认的字符排序,就不会被丢弃 return o1.compareTo(o2); } return i; } }); //添加 ts.add("c"); ts.add("qwer"); ts.add("df"); ts.add("ab"); //打印: System.out.println(ts);//[c, ab, df, qwer] } }
上面的匿名内部类可以用 Lambda 简化
练习题 :
TreeSet对象自定义排序练习题:
Teacher 类:
public class Teacher {
private String name;
private int age;
//构造+set+get+ toString
}
测试类:
package com.lt.treeset; import java.util.Comparator; import java.util.TreeSet; public class MyTreeSet4 { public static void main(String[] args) { //创建集合对象 TreeSet<Teacher> ts = new TreeSet<>(new Comparator<Teacher>() { @Override public int compare(Teacher o1, Teacher o2) { //o1表示现在要存入的那个元素 //o2表示已经存入到集合中的元素 //主要条件 int result = o1.getAge() - o2.getAge(); //次要条件 result = result == 0 ? o1.getName().length()-o2.getName().length() : result; return result; } }); //创建老师对象 Teacher t1 = new Teacher("zhangsan",22); Teacher t2 = new Teacher("lisi",22); Teacher t3 = new Teacher("wangwu",24); Teacher t4 = new Teacher("zhaoliu",24); //把老师添加到集合 ts.add(t1); ts.add(t2); ts.add(t3); ts.add(t4); //遍历集合 for (Teacher teacher : ts) { System.out.println(teacher); } } }
当要给字符串长度排序
或
数字要从大到小排序
若排序方式一和方式二同时存在会以什么方式为准?
答:第二种
总结:
它们的 Set 系列的集合是基于 Map 接口的
HashSet:
add 方法:
LinkedHashSet:
TreeSet:
后面讲 Map 接口再说。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。