之前在介绍Collection时简单提了一下, List是有序集合,存入的顺序和取出顺序一致,其中的元素可以重复。Set是无序集合,存入顺序和取出顺序不一致,其中元素不能重复。
HashSet
HashSet在存入数据判断存入的顺序是否重复是先判断存入数据的hashcode和集合中现有的所有元素的hashcode一一比较,如果没有一样的,直接存入,如果存入数据的hashcode和集合中部分元素的hashcode一样,就会调用equals方法进行判断,如果结果还是true,那就认为要存入的数据在集合中已存在,不予存入。
正常场景下hashcode代表这个对象的内存地址,equals判断的也是两个对象的内存地址是否一致,所以如果hashcode一样的情况下equals判断的结果一定也为true。
但是我们知道,如果一个类没有明确继承关系,那么这个类将默认继承于Object,所以我们在创建对象的时候可以重写hashcode方法,使其返回的int数据不是内存地址的十进制格式,再重写equasl方法,修改其判断两个对象是否相同的条件。
例如:
class Person{
String name;
int age;
public Person(String name,int age){
this.name = name;
this.age = age;
}
public String toString() {
return name + age;
}
public int hashCode(){
//this.toString()是String类型的数据,String重写了hashCode方法
//所以这里调用的String类的hashCode方法.
return this.toString().hashCode();
}
public boolean equals(Object o){
if(!(o instancesof Person)) return false;
Person per = (Person) o;
return per.toString().equals(this.toString());
}
}
java源代码中String中重写之后的hashCode();
public int hashCode() {
int h = hash;
if (h == 0 && value.length > 0) {
char val[] = value;
for (int i = 0; i < value.length; i++) {
h = 31 * h + val[i];
}
hash = h;
}
return h;
}
String重写hashCode方法之后将hashCode方法获取内存地址修改成了将字符串中每个字符以特定的规则转换成的int类型。
可能有些小伙伴会有疑问,为什么要重写hashCode,重写之前直接判断两个对象的内存地址是否一致不就可以了吗,只要内存地址不一样,那这两个对象就是不同的。这个想法其实没有问题,只不过我们重写hashCode和equals是为了我们自己制定两个对象是否一致的标准,如:我们现在规定属性全部一致的两个Person对象视为同一个对象:
public class Test{
public static void main(String[] args){
Person p1 = new Person("张三", 18);
Person p2 = new Person("张三", 18);
}
}
上述代码我们创建了两个对象,其属性值都是name : "张三",age : 18。按照我们的制定的规则来说,这两个对象属于重复对象,我们在存入时只能存入一个。但如果我们不重写hashCode方法,这里进行判断的就是两个对象的内存地址,由于创建这两个对象时都是用了new关键字,所以都会在堆中开辟一块全新的内存空间,所以他们的内存地址一定不一致。在调用hash比较的结果就是两个对象不重复,可以存入,不会再调用equals方法,当然,如果equasl方法没有重写的话也是判断两个对象的内存地址。
从这里我们可以明白,为什么要重写hashCode方法和equals方法,最主要的目的就是我们自己制定对象重复的标准。
TreeSet
TreeSet并没有提供判断要存入的数据在集合中是否已存在的标准,这个标准需要我们自己来定义。
在使用TreeSet存储数据需要给TreeSet存入具有比较性的数据,或者在创建TreeSet时传入比较器。
比较性
java提供了一个接口Comparable,实现这个接口的类就具有了比较性。
实现Comparable接口需要重写方法compareTo(就是在介绍String的时候提到的那个);
在存入数据是会调用我们要存入内容的compareTo方法去和集合中存在的数据进行比较,如果结果是负数,就排在那个数据的前面,如果是正数就排到后面,如果是0就代表要存在的数据和已存在的数据相同,不予存入。
通过这个方法我们可以也可以自定义排序的规则和重复的标准,现在我们创建一个Person类,我们规定要按照年龄从大到小排序,如果名字和年龄相同,就视为是重复对象,不予存入。
public class Person implements Comparable{
//手写代码挺累的,为了方便,访问权限修饰符就用public了
public String name;
public int age;
public Person(String name;int age){
this.name = name;
this.age = age;
}
public String toString(){
return name + age;
}
public int compareTo(Object obj){
if(!(Obj instanceof Person)) return 1;
Person per = (Person) obj;
//如果年龄不相同,按照年龄排序
if(per.age != this.age) return this.age - per.age;
//年龄相同,就判断属性是否一致,如果一致就认为是重复对象,不予存入。
if(per.toString().euqals(this.age)) return 0;
return 1;
}
}
比较器
java提供了一个比较器Comparator接口,实现这个接口需要重写compare方法,在创建TreeSet对象的时候传入这个对象。
compare方法的结果和比较性中compareTo一样,如果返回结果是正数,要存入的数据将会放在已有数据的右边,负数放在左边,0代表重复,不予存入。
public class Test{
public static void main(String[] args){
TreeSet<Person> set = new TreeSet<Person>(new Comparator(){
//匿名内部类
public int compare(Object o1,Object o2){
//o1是要传入的数据
//o2是集合中已存在的数据
Person p1 = (Person) o1;
Person p2 = (Person) o2;
//由于TreeSet规定了传入的数据类型,所以这里就不做类型判断了,直接强转
if(p1.age != p2.age) return p1.age - p2.age;
//如果年龄不一致,就按照年龄从小到大排序
if(p1.toString().equals(p2.toString())) return 0;
//年龄相同,其他属性也相同,视为重复数据,返回0,不予存入;
return 1;
}
});
}
}
通过传入的比较器,可以比较要存入的数据和已有的数据,按照我们自己制定的标准去重或排序。
LinkedHashSet
这个基本不用,他是有序的,但是不能重复,属于特殊的Set
Collections
Collections是集合的工具类。
常用方法:
public static <T> boolean addAll(Collection<? super T> c, T... elements) {
boolean result = false;
for (T element : elements)
result |= c.add(element);
return result;
}
用于向集合中插入数据。
binarySearch 使用二分法在集合中查找元素。
sort(Collection<E> e);给集合排序
sort(Collection<E> e,Comparator<E> c);自定义比较器给集合排序。
reverse(List<?> list):给List翻转,逆序。只能用于List,不能用于Set。