Set集合:不允许元素重复,唯一的(元素可以为null) ,不能保证迭代的顺序恒久不变(底层哈希表和hascode)
无序(存储和取出不一致)
List:允许元素重复,并且存储特点:有序性(存储和取出一致)
1)hashSet集合
通过Set集合存储字符串并遍历
public class SetDemo {
public static void main(String[] args) {
//创建Set集合对象,子实现类:HashSet集合
Set<String> set = new HashSet<String>();
//添加元素
set.add("hello") ;
set.add("java") ;
set.add("world") ;
//看他的唯一性,多存储一些元素
set.add("hello") ;
set.add("java") ;
set.add("world") ;
//遍历
for(String s: set) {
System.out.println(s);
}
}
}
可以发现,Set集合的存取顺序并不一致,且存储元素的时候,可以保证元素的唯一性,原因什么?
看源码:
HashSet集合的add方法底层依赖于双列集合HashMap,它依赖于两个方法,HashCode()方法和equals()方法
先比较字符串的HashCode()码值一样,再比较equals()方法
如果hasCode码值一样,还要比较内容是否相同,由于存储String,重写了equals()方法
String本身重写了equals方法,所以不需要再重写了!
让你用Set集合存储自定义对象遍历
public class SetDemo3 {
public static void main(String[] args) {
Set<Student> set = new HashSet<Student>() ;
//创建学生对象
Student s1 = new Student("高圆圆", 27) ;
Student s2 = new Student("高圆圆", 28) ;
Student s3 = new Student("文章", 30) ;
Student s4 = new Student("马伊琍", 39) ;
Student s5 = new Student("高圆圆", 27) ;
//存储到集合中
set.add(s1) ;
set.add(s2) ;
set.add(s3) ;
set.add(s4) ;
set.add(s5) ;
//遍历
for(Student s : set) {
System.out.println(s.getName()+"---"+s.getAge());
}
}
}
运行后发现重复元素也被添加进去了,这是因为现在是自定义的类,HashSet集合的add()方法本身依赖于hashCode()和equals()方法,在Student类中并没重写这两个方法,解决方法:重写这两个方法(右键--->Source--->Generate hashCode() and equals()生成)
重写后的Student类:
public class Student {
private String name ;
private int age ;
public Student() {
super();
}
public Student(String name, int age) {
super();
this.name = name;
this.age = 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;
}
@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;
}
}
2)LinkedHashSet集合
如果在开发中,元素唯一性,并且还要保证元素有序(存储和取出一致),使用LinkedHashSet集合
LinkedHashSet集合:
底层是一种链接列表和哈希表组成
可以保证元素的唯一性,是由哈希表决定的(hashCode()和equals())
可以保证元素的迭代顺序一致(有序),存储和取出一致,是由链表决定
public class LinkedHashSetDemo {
public static void main(String[] args) {
//创建LinkedHashSet集合对象
LinkedHashSet<String> link = new LinkedHashSet<String>() ;
//添加元素
link.add("hello") ;
link.add("java") ;
link.add("world") ;
link.add("world") ;
link.add("world") ;
link.add("java") ;
//增强for遍历
for(String s: link) {
System.out.println(s);
}
}
}
可以发现,此集合既可以保证元素唯一性又可以保证元素存入和取出顺序一致。
3)TreeSet集合
TreeSet集合模拟情况下是通过自然顺序对集合中的元素排序
TreeSet:
可以保证元素唯一并且元素排序(Integer类型的元素自然升序)
自然排序
比较器排序
给TreeSet集合存储以下元素:20,23,22,18,17,19,24..
public class TreeSetDemo {
public static void main(String[] args) {
//创建一个集合对象TreeSet集合对象
TreeSet<Integer> ts = new TreeSet<Integer>() ;
//给集合中存储元素
ts.add(20) ; //add()方法底层的源码是一个Map接口实例
ts.add(22) ;
ts.add(18) ;
ts.add(23) ;
ts.add(24) ;
ts.add(17) ;
ts.add(19) ;
ts.add(18) ;
ts.add(24) ;
//遍历
for(Integer i : ts) {
System.out.print(i +" ");
}
}
}
运行发现输出的集合已经对数据进行了自然升序的排序,查看TreeSet集合add方法的源码,可以发现add方法的底层源码是一个Map接口实例,自然排序依赖于TreeMap结构实现(自平衡的红黑树结构):
1)将添加的第一个元素先存储进来作为根节点
2)之后的每一个元素在添加时都必须与根节点比较
大了,作为右孩子
小了,作为左孩子
相等,不搭理
实质上就是一个二叉树,在取出数据时,实质上就是对二叉树进行遍历(前序遍历,中序遍历,后序遍历),这里的遍历是中序遍历,即取出排好序的数据。
接下来有如下需求:
使用TreeSet集合存储自定义对象(Student类型),并遍历!
按照学生的年龄从小到大进行排序 (主要条件)
如果成员变量的值一样认为是同一个对象
对于自定义的类型,要实现自然排序,必须自定义的类型必须实现Comparable
实现接口中的方法,compareTo() :比较方法
public class Student implements Comparable<Student>{
private String name ;
private int age ;
public Student() {
super();
}
public Student(String name, int age) {
super();
this.name = name;
this.age = 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;
}
@Override
public int compareTo(Student s) { //源码 cmp = k.compareTo(t.key)
//按照某种规则,前提必须有这规则
//主要条件:按照年龄从小到大
int num = this.age - s.age ; //如果年龄相等,不一定是同一个人
//需要自己分析次要条件;
//年龄相同,姓名的内容不一定相同,比较姓名
int num2 = num==0 ? this.name.compareTo(s.getName()) : num ;
return num2 ;
}
}
测试类:
public class TreeSetDemo2 {
public static void main(String[] args) {
//创建TreeSet集合对象
TreeSet<Student> ts = new TreeSet<Student>() ;//无参构造的方式自然排序
//创建学生对象
Student s1 = new Student("gaoyuanyuan",27) ;
Student s2 = new Student("liushishi",38) ;
Student s3 = new Student("gaoyuanyuan",28) ;
Student s4 = new Student("wanglihong",35) ;
Student s5 = new Student("wanglihong",30) ;
Student s6 = new Student("fengqingy",38) ;
Student s7 = new Student("gaoyuanyuan",27) ;
//java.lang.ClassCastException: org.westos_03.Student cannot be cast to java.lang.Comparable
ts.add(s1) ;
ts.add(s2) ;
ts.add(s3) ;
ts.add(s4) ;
ts.add(s5) ;
ts.add(s6) ;
ts.add(s7) ;
//遍历
for(Student s:ts) {
System.out.println(s.getName()+"---"+s.getAge());
}
}
}
既然学习了如何把Student对象按照年龄从小到大的顺序排序,我们接下来试着按照姓名长度从短到长(默认)的条件对学生进行排序
分析:
1)判断两个对象的姓名长度是否相等
2)姓名长度相等,不一定是同一个人,需要进一步进行分析,判断姓名内容是否相等
3)姓名内容相等,依然不一定是同一个人,进一步分析年龄是否相等
4)年龄相等,是同一个人
4)年龄不等,不是同一个人,排序
3)姓名内容不等,直接进行排序
2)姓名长度不相等,直接排序
代码如下:
学生类:
public class Student implements Comparable<Student> {
private String name ;
private int age ;
public Student() {
super();
}
public Student(String name, int age) {
super();
this.name = name;
this.age = 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;
}
@Override
public int compareTo(Student s) {
//按照姓名的长度进行比较(默认从小到大)
int num = this.getName().length() - s.getName().length() ;
//如果姓名的长度一样,还要比较他们的内容是否相同
int num2 = num == 0 ? this.getName().compareTo(s.getName()) :num ;
//如果姓名长度和姓名的内容都一致,也不能保证就是同一个人,还要比较年龄
int num3 = num2== 0 ? this.age-s.age :num2 ;
return num3 ;
}
}
测试类:
public class TreeSetDemo {
public static void main(String[] args) {
//创建TreeSet集合,实现对该类型自然排序
TreeSet<Student> ts = new TreeSet<Student> ();
//创建学生对象
Student s1 = new Student("gaoyuanyuan", 27) ;
Student s2 = new Student("zhangguorong",29) ;
Student s3 = new Student("wuqilong", 40) ;
Student s4 = new Student("liushishi", 28) ;
Student s5 = new Student("fengqingy", 29) ;
Student s6 = new Student("gaoyuanyuan", 22) ;
Student s7 = new Student("gaoyuanyuan", 27) ;
//添加元素
ts.add(s1) ;
ts.add(s2) ;
ts.add(s3) ;
ts.add(s4) ;
ts.add(s5) ;
ts.add(s6) ;
ts.add(s7) ;
//遍历
for(Student s:ts) {
System.out.println(s.getName()+"---"+s.getAge());
}
}
}
TreeSet的两种排序方式
TreeSet集合的构造方式不同,使用的排序也不同
比较器排序 :public TreeSet(Comparator<E> comparator)
两种方式:
1)自定义一个类,该类实现Comparator接口,重写Comparator接口中的compare()方法
2)直接使用接口匿名内部类的方式实现
TreeSet集合保证元素唯一,是看返回值是否为0
本节前面排序的程序使用的都是自然排序,下来我们来看一下使用比较器排序的方式去遍历Student对象,并且按照姓名长度进行比较的例子。
方式一:自定义一个类实现Comparator接口,重写Comparator接口中的compare()方法
//自定义类,该类实现Comparator保证集合中的元素进行比较器排序
public class MyComparator implements Comparator<Student> {
@Override
public int compare(Student s1, Student s2) {
/**
* 自然排序:Comparable 里面compareTo(Student s)
* this---->s1
* s---s
*/
//按照姓名长度进行比较
int num = s1.getName().length() - s2.getName().length() ;
//长度一样,还要比较姓名的内容是否相同
int num2 = num==0 ?s1.getName().compareTo(s2.getName()) : num ;
//最终看年龄是否一致
int num3 = num2 ==0 ? (s1.getAge() - s2.getAge()) : num2 ;
return num3 ;
}
}
测试类:
public class TreeSetDemo {
public static void main(String[] args) {
TreeSet<Student> ts = new TreeSet<Student>(new MyComparator()) ;
//创建学生对象
Student s1 = new Student("gaoyuanyuan", 27) ;
Student s2 = new Student("zhangguorong",29) ;
Student s3 = new Student("wuqilong", 40) ;
Student s4 = new Student("liushishi", 28) ;
Student s5 = new Student("fengqingy", 29) ;
Student s6 = new Student("gaoyuanyuan", 22) ;
Student s7 = new Student("gaoyuanyuan", 27) ;
ts.add(s1) ;
ts.add(s2) ;
ts.add(s3) ;
ts.add(s4) ;
ts.add(s5) ;
ts.add(s6) ;
ts.add(s7) ;
//遍历
for(Student s:ts) {
System.out.println(s.getName()+"----"+s.getAge());
}
}
}
方式二:直接使用接口匿名内部类的方式实现
public class TreeSetDemo {
public static void main(String[] args) {
//直接使用匿名内部类的方式实现
TreeSet<Student> ts = new TreeSet<Student>(new Comparator<Student>() {
@Override
public int compare(Student s1, Student s2) {
/**
* 自然排序:Comparable 里面compareTo(Student s)
*
* this---->s1
* s---s
*/
//按照姓名长度进行比较
int num = s1.getName().length() - s2.getName().length() ;
//长度一样,还要比较姓名的内容是否相同
int num2 = num==0 ?s1.getName().compareTo(s2.getName()) : num ;
//最终看年龄是否一致
int num3 = num2 ==0 ? (s1.getAge() - s2.getAge()) : num2 ;
return num3 ;
}
}) ;
//创建学生对象
Student s1 = new Student("gaoyuanyuan", 27) ;
Student s2 = new Student("zhangguorong",29) ;
Student s3 = new Student("wuqilong", 40) ;
Student s4 = new Student("liushishi", 28) ;
Student s5 = new Student("fengqingy", 29) ;
Student s6 = new Student("gaoyuanyuan", 22) ;
Student s7 = new Student("gaoyuanyuan", 27) ;
ts.add(s1) ;
ts.add(s2) ;
ts.add(s3) ;
ts.add(s4) ;
ts.add(s5) ;
ts.add(s6) ;
ts.add(s7) ;
//遍历
for(Student s:ts) {
System.out.println(s.getName()+"----"+s.getAge());
}
}
}