先介绍下Java中的集合的概念
1.集合是JavaAPI所提供的一系列类,用来动态存放多个对象----集合只能存放对象
2.集合与数组的区别是,集合长度可变,且存放元素不受限定,主要是引用类型。
3.集合全部支持泛型,是一种数据安全的用法
集合框架图
集合分两大家族:
Collection(接口)家族:存放单一对象
Map(接口)家族:存放键值对(key,value)
先说Collection(接口)家族
Collection接口定义了存取对象的方法,它有两个很常用的子接口
List接口:存放的元素有序且重复
Set接口:存放的元素无序且唯一
List接口的两个重要实现类:ArrayList和LinkedList
这个不是我们要说的重点,就不多BB了,反正想用集合但不知道用什么集合,那就用ArrayList。
Set接口的两个重要实现类:HashSet和TreeSet
下面看看他们的具体存储原理:
HashSet
图中简单模拟了下hash算法,即数字转为二进制后与一串特定的二进制序列相与,得到hashcode。
对!你也许已经发现了:
不同数字的hashcode可能是相同的,在图中也可以看到hashcode相同时,元素是以链表的形式存放在该位置的。
相同数字的hashcode必然相同,比较相同元素的过程被大大简化。
下面我们来检测下HashSet存放元素的唯一性
public class Test {
public static void main(String[] args) {
Set
set = new HashSet<>(); set.add(11);
set.add(22);
set.add(44);
set.add(11);
System.out.println(set);
//=======遍历1:基本for-无下标不能使用
//-----------增强for(实现原理就是迭代器)
System.out.println();
for(Integer a:set)
System.out.print(a+"-");
System.out.println();
//迭代器
Iterator
it = set.iterator(); while (it.hasNext()) {
System.out.print(it.next()+"-");
}
}
}
HashSet确实去掉了重复元素11
这里存放的是Integer类型对象,那么如果存放我们自己定义的对象呢?
定义一个Teacher类:
public class Teacher {
private String name;
private int age;
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "Teacher [name=" + name + age+"]";
}
public Teacher() {
super();
// TODO Auto-generated constructor stub
}
public Teacher(String name, int age) {
super();
this.name = name;
this.age = age;
}
}
我们再来测试下:
public class Test2 {
public static void main(String[] args) {
Set
set = new HashSet<>(); set.add(new Teacher("苍老师",18));
set.add(new Teacher("陈老师",18));
set.add(new Teacher("苍老师",18));
System.out.println(set);
}
}
输出结果:
这次输出了两个苍老师,HashSet居然没有去重。
这是为什么呢?
我们注释掉Teacher类中的toString方法,再看下输出
我们看下输出,可以发现输出的对象地址都是不同的(尽管存放的都是苍老师)
因为我们存放的元素是对象,而不同的对象其地址肯定是不同的,而hashcode也是用地址去计算的,不同地址计算出来的hashcode当然也不同。
那么问题来了,我们上面存放的Integer对象,为什么就没出现这样的问题呢?
我们看下Integer类的源码
public Integer(int value) {
this.value = value;
}
@Override
public int hashCode() {
return Integer.hashCode(value);
}
public boolean equals(Object obj) {
if (obj instanceof Integer) {
return value == ((Integer)obj).intValue();
}
return false;
}
原来Integer类内部悄悄重写了hashcode和equals方法,改为用value值去获取hashcode和比较。
那么给我们的Teacher类也实现hashcode和equals方法吧。
但怎么实现呢,大佬可以参照源码实现的方式自己写。
懒人方法:eclipse已经给我们提供自动实现了,就和getter和setter方法一样,按下alt+shift+s,即可看到Generate hashCode() and equals()选项,一键点击即可在我们自己写的类里实现hashSet的去重功能。
再来测试下:
可以看到,我们终于去掉了重复的苍老师。
下面重点来了:
TreeSet
先了解下TreeSet的存储方式
由于TreeSet的存储特性,可知TreeSet存储特点是唯一且有序。
和前面一样,我们先来测试Java自定义的Integer类对象及TreeSet的遍历
public class Test {
public static void main(String[] args) {
Set
set = new TreeSet<>(); set.add(1);
set.add(3);
set.add(2);
set.add(1);//唯一
System.out.println(set);
//遍历也是不能用基本for
//可以进行增强for和迭代器遍历
System.out.println("===============");
for(Integer i:set){
System.out.println(i);
}
System.out.println("===============");
Iterator
it = set.iterator(); while (it.hasNext()) {
System.out.println(it.next());
}
}
}
输出:
可以看到TreeSet不仅实现了去重,还进行了升序排序。
那么将Integer类对象换为我们定义的Teacher对象呢
public class Test2 {
public static void main(String[] args) {
Set
set = new TreeSet<>(); //ClassCastException: Student cannot be cast to Comparable
set.add(new Student("zs",20));
set.add(new Student("ls",30));
set.add(new Student("ww",20));
set.add(new Student("zs",80));
System.out.println(set);
}
}
这次不仅仅是无法去重排序了,直接报了个Bug。
那该如何解决呢?
和之前一样,看下Integer类源码
public final class Integer extends Number implements Comparable
{ public int compareTo(Integer anotherInteger) {
return compare(this.value, anotherInteger.value);
}
}
原来Integer类实现了Comparable接口里的compareTo方法,
compareTo是Comparable接口定义的方法,那么里面调用的compare方法又是什么呢?
由于Integer类连compare方法都重写了,那该如何查看compare方法源码呢
这里补充个知识点,HashSet和TreeSet都是调用Map家族下的HashMap和TreeMap中的方法来实现的,所以我们得去TreeMap中找compare源码
final int compare(Object k1, Object k2) {
return comparator==null ? ((Comparable super K>)k1).compareTo((K)k2)
: comparator.compare((K)k1, (K)k2);
}
这是个三目运算,当comparator为空时,执行第一个表达式,否则执行第二个表达式。
即实现了comparator接口的话,就调用其compare方法实现比较大小;而未实现comparator接口就调用comparable接口的compareTo方法。
也就是说源码给我们提供了两种比较方法,一种是实现comparator接口,另一种是实现comparable接口,这也就是我们所说的比较器法和自然排序法。
自然排序法
方式1:自然排序法------》自定义类实现Comparable接口
public class Student implements Comparable{
private String name;
private int age;
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "Student [name=" + name + ", age=" + age + "]";
}
public Student() {
super();
// TODO Auto-generated constructor stub
}
public Student(String name, int age) {
super();
this.name = name;
this.age = age;
}
@Override
public int compareTo(Student o) {
if(o.age==this.age)
return o.name.compareTo(this.name);//升序
//return o.name.compareTo(this.name);//降序
return o.age - this.age; //按年龄降序,相同则按名字首字母升序排列
}
}
方式2:比较器法------》自定义实现比较器comparator类
这里用匿名内部类实现:
//匿名内部类
Map
map3 = new TreeMap<>(new Comparator () { @Override
public int compare(Student o1, Student o2) {
if(o1.getName().equals(o2.getName()))
return o1.getAge()-o2.getAge();
return o1.getName().compareTo(o2.getName());
}
});
虽是TreeMap集合,但TreeSet与其除相差无几
Map集合中的HashMap、TreeMap与上面说的HashSet、TreeSet实现方式大致相同。
值得一提的就是Map集合的遍历了
//遍历
Set
set1 = map.keySet(); for(String a:set1){
System.out.println(a+" "+map.get(a));
}
System.out.println("============");
//键值对遍历
Set
> set = map.entrySet(); Iterator
> it = set.iterator(); while (it.hasNext()) {
Entry
entry = (Entry ) it.next(); System.out.println(entry.getKey()+"-->"+entry.getValue());
}