Set接口
Set拥有如下特点:
这里有一条提到了不存在重复元素,但是List是可以存在重复元素的,那么Set是如何判定元素是否重复的呢?
Set是利用了hash算法配合equals方法来实现的.
- 无序排列
- 不能有重复元素
- 可以有null,一样只能存在一个.
- 由于无序,所以无法使用经典for循环遍历,可以使用迭代器和foreach循环.
在设计元素类型时,提供哈希算法,用于返回对象的的一个int值,在内存中开辟很多小的区域用于存储一定范围的返回值的对象.当我们想添加或查看元素时,先计算此元素的返回值(int hashCode()),之后去相应的区域进行遍历.如果没有找到值相同的,说明可以存储这个对象.如果有相同的值,查看两个对象equals方法返回值,如果为true则不能存储,反之则可以,添加至对应的链表结构中,但实际使用时应尽可能避免这种情况.
原则上,应该让所有的元素参与运算,还要尽量避免出现相同的返回值.
首先设计一个Person类,提供如下变量,并使用IDE自动生成hashCode和equals方法,以及遵循JavaBean规范生成的各种方法.
public class Person {
private String name;
private int age;
private char gender;
private String number;
/*遵从JavaBean规范,同时提供无参有参构造器*/
public Person(){}
public Person(String name, int age, char gender, String number) {
this.name = name;
this.age = age;
this.gender = gender;
this.number = number;
}
/*get/set方法*/
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 char getGender() {
return gender;
}
public void setGender(char gender) {
this.gender = gender;
}
public String getNumber() {
return number;
}
public void setNumber(String number) {
this.number = number;
}
/*重写equals方法和hashCode方法*/
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Person person = (Person) o;
if (getAge() != person.getAge()) return false;
if (getGender() != person.getGender()) return false;
if (!getName().equals(person.getName())) return false;
return getNumber() != null ? getNumber().equals(person.getNumber()) : person.getNumber() == null;
}
public int hashCode() {
int result = getName().hashCode();
result = 31 * result + getAge();
result = 31 * result + (int) getGender();
result = 31 * result + (getNumber() != null ? getNumber().hashCode() : 0);
return result;
}
/*重写toString方法*/
public String toString() {
return "("+name+","+age+","+gender+","+number+")";
}
}
来看一下自动生成的hashCode方法.
一个result变量存储了要返回的数据.
首先第一步使用了getName()的的hashCode方法,因为String类已经重写了hashCode方法,所以直接调用即可.
第二步用一个质数(任意质数都可以)与第一步的result相乘,再加上getAge()的返回值.
第三步把char强制转换为int数据,再与上一步的已经与质数相乘的result相加.
最后依旧是把上一步的result与质数相乘,再看getNumber()是否为空,不为空就调用String的hashCode方法,为空就加0.
方法末尾返回经过这些计算的result值.
可以看到经过这些计算很难再有重复的值了.
来看看重写hashCode方法的原则与注意事项.
- 如果重写了equals(),有必要重写hashCode()
- 如果equals()返回true,hashCode()的返回值要相同.
- 如果equals()返回false,hashCode()的返回值不一定不一样,如果返回值不同,可以提高检索的效率.
- hashCode值相同,equals方法可能不同.
- hashCode值不同,equals方法一定不同.
Set接口的子类
HashSet:通过实现hash算法实现的一种数据结构,无序,不重复,线程不安全.增删操作时比LinkedHashSet效率高.
LinkedHashSet:通过实现hash算法的一种数据结构,通过链表来维持顺序,顺序与添加顺序一致.查看或检索时比HashSet效率高.
TreeSet:SortedSet子接口的实现类.使用了二叉树的一种数据结构,顺序与自然排序有关,支持自定义排序.
测试类:
import java.util.HashSet;
import java.util.Set;
public class SetTest{
public static void main(String[] args) {
Set<Person> set=new HashSet<Person>();
Person p1=new Person("A",15,'F',"1234567890");
Person p2=new Person("B",19,'M',"1234567891");
Person p3=new Person("C",20,'M',"1234567892");
Person p4=new Person("D",17,'F',"1234567893");
Person p5=new Person("E",18,'F',"1234567894");
/*设置一个与p1重复的p6*/
Person p6=new Person("A",15,'F',"1234567890");
/*添加进set*/
set.add(p1);
set.add(p2);
set.add(p3);
set.add(p4);
set.add(p5);
set.add(p6);
/*输出set*/
System.out.println(set);
..........
}
}
[(E,18,F,1234567894), (D,17,F,1234567893), (A,15,F,1234567890), (B,19,M,1234567891), (C,20,M,1234567892)]
可以看到并不是按照添加顺序输出的,而且重复的p6也没有被添加进去.
现在删除一个元素,再输出.
..........
/*删除元素p2*/
set.remove(p2);
/*再次输出set*/
System.out.println(set);
..........
[(E,18,F,1234567894), (D,17,F,1234567893), (A,15,F,1234567890), (C,20,M,1234567892)]
此时修改其中一个元素,利用set方法,输出,删除p2后再输出.
...........
/*修改元素p2*/
p2.setName("FF");
/*输出修改后的set*/
System.out.println(set);
/*再尝试删除p2*/
set.remove(p2);
/*再次输出set*/
System.out.println(set);
..........
修改p2后的set[(E,18,F,1234567894), (D,17,F,1234567893), (A,15,F,1234567890), (FF,19,M,1234567891), (C,20,M,1234567892)]
删除p2后的set:[(E,18,F,1234567894), (D,17,F,1234567893), (A,15,F,1234567890), (FF,19,M,1234567891), (C,20,M,1234567892)]
可以看到并没有被删除,因为修改之后,元素还在原来的地址上,但是本身的hashCode值发生了改变,删除时找不到该元素.若输出删除元素的方法,可以看到输出-1,表示没找到元素.
如何删除呢,其实很简单,把内容改为原来的值就好了~
..........
/*将p2改为原来的状态*/
p2.setName("B");
/*再次删除*/
set.remove(p2);
System.out.println("修改为原本的p2之后删除p2后的set:"+set);
修改为原本的p2之后删除p2后的set:[(E,18,F,1234567894), (D,17,F,1234567893), (A,15,F,1234567890), (C,20,M,1234567892)]
修改之后由于hashCode值变成了原来的,所以就可以删除了.
Map接口
集合框架中的另一个父接口
用于存储一一对应的元素数据.第一个对象可以作为索引,第二个对象作为值,所以称之为key-value键值对.
存储特点
- 以key-value形式进行存储.
- key与value必须都是引用类型.
- key可以为null.
- key-value是单向一对一映射.
- key不能重复,value可以重复.
存储机制
Map是基于数组和链表的数据结构.
作为key的元素采用了散列算法计算存储的数组的位置上,即散列数组或散列桶.如果数组计算出来的位置,数组此位置上没有元素,就可以添加到散列桶内.如果有元素,使用key的equals,若返回值为false,就会存储在散列桶元素对应的单向链表中,若返回值为true,就进行替换.使用Map集合时,作为key的数据类型应重写equals()与hashCode().
注意向Map中添加元素需要使用put方法.
import java.util.HashMap;
import java.util.Map;
public class MapTest{
public static void main(String[] args) {
/*设置key为String,value为Person.*/
Map<String,Person> map=new HashMap<String, Person>();
Person p1=new Person("A",15,'F',"1234567890");
Person p2=new Person("B",19,'M',"1234567891");
Person p3=new Person("C",20,'M',"1234567892");
Person p4=new Person("D",17,'F',"1234567893");
Person p5=new Person("E",18,'F',"1234567894");
/*向map中添加元素,注意应该使用put方法.*/
Person p6=new Person("C",19,'M',"1234");
map.put("A",p1);
map.put("B",p2);
map.put("C",p3);
map.put("D",p4);
map.put("E",p5);
System.out.println(map);
/*将key值与p3相同的p6添加至map*/
Person p=map.put("C",p6);
System.out.println(p);
..........
}
}
运行结果:
{A=(A,15,F,1234567890), B=(B,19,M,1234567891), C=(C,20,M,1234567892), D=(D,17,F,1234567893), E=(E,18,F,1234567894)}
常用方法
V put(K k,V v):存储一对键值对,返回被替换的value,若未发生替换则返回null.
..........
/*将key值与p3相同的p6添加至map*/
Person p=map.put("C",p6);
System.out.println("被替换的value:"+p);
..........
运行结果:
被替换的value:(C,20,M,1234567892)
再次输出map:
发生替换的map:{A=(A,15,F,1234567890), B=(B,19,M,1234567891), C=(C,19,M,1234), D=(D,17,F,1234567893), E=(E,18,F,1234567894)}
可以看到以C为key的value被替换为p6了.
V get(K k):通过key对象获取对应的value对象,若集合中没有这个key,返回null.
..........
/*取出存在的key"B"*/
p=map.get("B");
System.out.println(p);
/*取出不存在的key"G"*/
p=map.get("G");
System.out.println(p);
运行结果:
(B,19,M,1234567891)
null
可以发现输出不存在的key:G的值时,输出为null,证明这个key没有对应的value.
Map集合的遍历
Set<K> keySet():用于获取Map中的所有key对象,返回Set集合.
/*使用Set集合接收元素*/
Set<String> set=map.keySet();
/*输出set*/
System.out.println(set);
运行结果
[A, B, C, D, E]
Set<Entry<K,V>> entrySet():将key-value封装成内部类的对象,返回Entry对象的Set集合.
/*添加至Entry中*/
Set<Map.Entry<String,Person>> set=map.entrySet();
/*输出*/
System.out.println(set);
运行结果
[A=(A,15,F,1234567890), B=(B,19,M,1234567891), C=(C,20,M,1234567892), D=(D,17,F,1234567893), E=(E,18,F,1234567894)]
Collection<V> values():将Map集合中的所有value封装成一个Collection中
/*添加至Collection中*/
Collection<Person> c=map.values();
/*输出*/
System.out.println(c);
运行结果
[(A,15,F,1234567890), (B,19,M,1234567891), (C,20,M,1234567892), (D,17,F,1234567893), (E,18,F,1234567894)]