集合类
- 为什么出现集合类?
- 面向对象语言对事物的体现都是以对象形式存在的,所以为了方便对多个对象的操作,就对对象进行存储,集合就是存储对象最常用的一种方式
- 数组和集合类同是容器,有何不同?
- 数组虽然也可以存储对象,但是长度是固定的。集合长度是可变的,数组中可以存储基本数据类型,集合只能存储对象。
- 集合类的特点
- 集合只用于存储对象,集合长度是可变的,集合可以存储不同类型的对象。
集合框架
集合有自己的体系,根据每个集合类的共性方法,向上抽取获得抽象类(或者接口):Collection就是一层一层抽出来的接口
- Collection
- List
- ArrayList
- LinkedList
- Vector
- Set
- TreeSet
- HashSet
- List
为什么会出现这么多的容器呢?
因为每一个容器对数据的存储方式都不同,这个存储方式称为数据结构。
import java.util.*;
//简单的示例一下集合中的基本操作
public class CollectionDemo {
public static void sop(Object obj)
{
System.out.println(obj);
}
public static void main(String[] args)
{
//创建一个集合Collection的对象,因为Collection是接口,所以用ArrayList
ArrayList al = new ArrayList();
//在集合中添加元素,用add方法
al.add("123");//字符串也是对象
al.add("456");
al.add("789");
al.add("101");
//打印原集合
sop("原集合为:"+al);
//获取集合中对象的个数,用size方法
sop("集合中元素的个数为 "+al.size());
//删除集合中的元素
al.remove("789");
sop("删除789后的集合为 "+al);
//清空集合
al.clear();
sop("集合中元素的个数为 "+al.size());
}
}
运行结果为
这里需要注意的是:
add方法的参数类型是Object,以便于接收任意类型实例对象
集合中存储的都是对象的引用(地址)
集合中的其他共性方法:取交集
import java.util.*;
//演示集合中的共性方法二:取交集,
public class CollectionDemo2{
public static void sop(Object obj)
{
System.out.println(obj);
}
public static void main(String[] args)
{
ArrayList al1 = new ArrayList();
al1.add("java01");
al1.add("java02");
al1.add("java03");
al1.add("java04");
ArrayList al2 = new ArrayList();
al2.add("java03");
al2.add("java04");
al2.add("java05");
al2.add("java06");
sop(al1);
sop(al2);
//al1.retainAll(al2);//al1和al2中的共有的元素会保留下来。
//运行后的结果为:al1 = [java03,java04]
//al1.removeAll(al2);//al1和al2中的不同的元素会保留下来。
//运行后的结果为:al1 = [java01,java02]
sop(al1);
sop(al2);
}
}
用迭代器操作集合中的元素
什么是迭代器呢?其实就是集合中元素的取出方式
示例:
import java.util.*;
//演示迭代器的使用方法
public class CollectionDemo3{
public static void sop(Object obj)
{
System.out.println(obj);
}
public static void main(String[] args)
{
ArrayList al = new ArrayList();
al.add("java01");
al.add("java02");
al.add("java03");
al.add("java04");
Iterator it = al.iterator();//获取迭代器,
//iterator方法返回的是一个接口,所以这里的it是一个实现了Iterator接口的子类对象
while(it.hasNext())//hasNext方法:查看集合里还有没有元素(可迭代),如果有元素,则返回真
{
sop(it.next());//next方法:返回迭代的下一个元素
}
}
}
运行结果:
**关于迭代器 **
在Collection中有很多的集合类,因为存储的数据结构不同,所以取出数据的方式也肯定不同,
于是取出方式就定义在了集合的内部,形成了一个内部类,这样可以直接访问集合中的元素。
这个内部类实现了Iterator接口,重写了里边的hasNext(判断)和next(取出)方法,对应的每一种数据类型,在内部类中都有对应的判断和取出方法。
然后内部类对外提供了一个获取迭代器的方法,iterator();返回一个内部类的对象。
一般的,为了节省内存空间,也把获取元素的方式写成for循环的形式
for(Iterator it = al.iterator();it.hasnext(); )
{
sop(it.next());
}
如果用while判断形式的迭代方式的话,会建立一个it对象,并且对象会一直被引用着,不会被垃圾回收机制所回收,
但是用for循环的话,建立下对象,迭代完成之后,随着for循环的结束,建立下的对象也就没有了。这样子节省空间的。
List集合中的共性方法
- Collection
- List:元素是有序的,元素可以重复,因为该集合体系有索引
- Set:元素是无序的,元素不可以重复。因为里边没有索引
List:
特有方法,凡是可以操作角标的方法都是该体系中的特有方法
增:
add(index,element); 在指定位置添加元素
addAll(index,Collection);在指定位置添加另一个集合中的元素
删:
remove(index);删除指定位置的元素
改:
set(index,element);将指定位置的元素改为参数中的元素
查:
get(index);获取角标处的元素
subList(from,to);获取角标区域之间的元素(包含头不包含尾)
ListIterator();
示例代码:
//List中的基本方法演示
import java.util.*;
public class ListDemo {
public static void sop(Object obj)
{
System.out.println(obj);
}
public static void main(String[] args)
{
ArrayList al = new ArrayList();
al.add("java01");
al.add("java02");
al.add("java03");
sop("原集合为:"+al);
//添加元素
al.add(3,"java04");//在三角标位添加“java04”
sop("修改后的集合为"+al);
//删除元素
al.remove(3);//删除角标为3的元素
sop("修改后的集合为"+al);
//修改元素
al.set(0,"java00");//将角标位0的元素改为"java00"
sop("修改后的集合为"+al);
//查:
//获取单个元素
sop(al.get(1));//获取角标位为0的元素
//获取所有的元素 两种方法
for (int x = 0;x<al.size() ;x++ )
{
sop(al.get(x));
}
/*效果和上边的for循环一样的
for (Iterator it = al.iterator();it.hasNext() ; )
{
sop(it.next());
}
*/
//获取指定对象的角标:
sop("获取的对象的角标是"+al.indexOf("java02"));//获取“java02在集合中的角标”
//根据指定角标范围获取集合的子集合,
List sub = al.subList(0,2);//将0,到2角标的对象取出存进一个新的List集合中。
sop("原集合为:"+al);
sop("获取的子集合为"+sub);
}
}
运行结果:
ListIterator
集合Collection不是有自己的迭代器么?为什么他的子接口List还要有自己的迭代器呢?
我们看下边的代码:
需求是,遍历集合中的对象,当遍历到某一个对象的时候,在集合中增加一个对象。
import java.util.*;
public class ListIteratorDemo {
public static void sop(Object obj)
{
System.out.println(obj);
}
public static void main(String[] args)
{
ArrayList al = new ArrayList();//创建一个List集合对象并在里边添加对象
al.add("java00");
al.add("java01");
al.add("java02");
al.add("java03");
Iterator it = al.iterator();
while(it.hasNext())
{
Object obj = it.next();//Object对象指向了取出来的对象的地址
sop(obj);//打印取出来的对象
if (obj.equals("java03"))//当取出来的对象的内容和“java03”一样时,在集合中添加一个对象元素
{
al.add("java04");
}
}
}
}
运行结果为:
从运行结果可以看到,前边执行打印对象的时候程序都正常执行了,
到后来判断符合条件将集合中添加元素的时候,出现了异常ConcurrentModificationException
查找API找到这个异常:
检测到并发修改,这是什么意思呢?
在程序中,当获取迭代器对象时(al.iterator()),迭代器得到了集合中的元素,并且在判断(hasNext)和取出(next)的过程中,迭代器一直在操作集合中的元素,这个时候,突然调用集合的方法add对集合中的元素进行操作,一个数据同时被两个方法操作,这就是对集合的并发修改。
简单说就是,在迭代时,不可以通过集合的方法操作集合中的元素
在查找API中发现,Iterator中年还有一个方法可以使用,就是remove删除。
import java.util.*;
public class ListIteratorDemo {
public static void sop(Object obj)
{
System.out.println(obj);
}
public static void main(String[] args)
{
ArrayList al = new ArrayList();//创建一个List集合对象并在里边添加对象
al.add("java00");
al.add("java01");
al.add("java02");
al.add("java03");
sop("原集合为:"+al);
Iterator it = al.iterator();
while(it.hasNext())
{
Object obj = it.next();//Object对象指向了取出来的对象的地址
//sop(obj);//打印取出来的对象
if (obj.equals("java03"))//当取出来的对象的内容和“java03”一样时,在集合中删除一个对象元素
{
//al.add("java04");
it.remove();//这里用迭代器对象调用,而且不用传参数,他会把next()返回来的对象直接删除掉
}
}
sop("迭代器修改后的集合为"+al);
}
}
运行结果为:
发现Iterator中除了判断和取出的方法,就剩下一个删除的方法了,
那我们要对迭代器取出来的数据进行更多的操作应该怎么做呢?
这时候就用到了ListIterator
**ListIterator是Iterator的子接口,**如果想要在迭代中对其中的数据进行操作,就需要用到其子接口ListIterator,
该接口只能通过List集合的ListIterator方法获取
ListIterator出现之后,,可以实现对集合中的元素在遍历过程中的增删改查操作。
使用方法如下:
import java.util.*;
public class ListIteratorDemo {
public static void sop(Object obj)
{
System.out.println(obj);
}
public static void main(String[] args)
{
ArrayList al = new ArrayList();//创建一个List集合对象并在里边添加对象
al.add("java00");
al.add("java01");
al.add("java02");
al.add("java03");
sop("原集合为:"+al);//[java00, java01, java02, java03]
ListIterator li = al.listIterator();//获取listIterator对象
sop(li.hasPrevious());//false
while(li.hasNext())
{
Object obj = li.next();
if (obj.equals("java03"))
{
//li.add("java04");//添加元素
//打印集合的结果为:[java00, java01, java02, java03,java04]
//li.remove();//删除元素
//打印集合的结果为:[java00, java01, java02]
//li.set("java04");//修改元素
//打印集合的结果为:[java00, java01, java02, java04]
}
}
sop(li.hasNext());//false
sop(li.hasPrevious());//true
}
}
List体系中的成员
实现了List接口的类:
- ArrayList:底层的数据结构是数组结构,特点:查询速度很快,但是增删稍慢,线程不同步.jdk1.2开始使用
- LinkedList:底层数据结构式链表结构,特点:增删速度很快,查询速度稍慢,
- Vector:底层是数组数据结构,线程同步,被ArrayList替代了。jdk1.0开始使用。
ArrayList和Vector在构造函数的区别:
两个都是基本数据结构为数组的集合,而且数组都是可变长度的,两者都是怎么实现可变长度的呢?
ArrayList在构造的时候会创建一个长度为10 的数组,如果添加元素的时候,空间不够用了,他会50%延长,就是说他会再建立一个长度为15的数组集合,将之前集合中的元素转移过来,然后再添加元素,
Vector在构造的时候会创建一个长度为10的数组,如果添加元素的时候,空间不够用了,他会100%延长,就是说会再建立一个长度为20的数组集合,
Vector比较浪费资源,所以要使用的话还是建议使用ArrayList。
Vector中的特有方法
Vector中有自己特有的取出数据的方法:枚举
import java.util.*;
//演示Vector的特有方法:
public class VectorDemo {
public static void main(String[] args)
{
Vector v = new Vector();
v.add("java00");
v.add("java01");
v.add("java02");
v.add("java03");
Enumeration e = v.elements();//获取枚举对象
while(e.hasMoreElements())
{
System.out.println(e.nextElement());
}
}
}
运行结果:
从以上代码中可以发现,枚举和迭代器很像,其实呢,枚举和迭代器是一样的,
因为枚举的名称以及方法的名称都过长,所以被迭代器取代了。枚举就不用了。
要说ArrayLIst和Vector的区别就是:Vector支持枚举,ArrayList没有。
LinkedList
LinkedList中的特有方法:
addFirst();在集合的首位添加元素
addLast();在集合的末尾添加元素
getFirst();获取最首位元素
getLast();获取最末尾元素
如果集合中没有元素,会出现NoSuchElementException
removeFirst();删除并且返回首位元素
removeLast();删除并且返回末位元素
如果集合中没有元素,会出现NoSuchElementException
演示特有方法的使用:
//演示LinkedList中的特有方法
import java.util.*;
public class LinkedListDemo {
public static void sop(Object obj)
{
System.out.println(obj);
}
public static void main(String[] args)
{
LinkedList link = new LinkedList();//建立集合对象
link.addFirst("java01");//在首位添加元素
link.addFirst("java02");
link.addFirst("java03");
link.addFirst("java04");
sop(link);//打印集合中的元素
}
}
运行结果:
为什么是倒着的呢?
因为每次存储的时候都是在首位添加的,java01放在第一位之后,java02进来放在了首位,也就是java01的前边,以此类推,存进去的方式就是倒着喽。
接下来演示其他方法:
//演示LinkedList中的特有方法
import java.util.*;
public class LinkedListDemo {
public static void sop(Object obj)
{
System.out.println(obj);
}
public static void main(String[] args)
{
LinkedList link = new LinkedList();//建立集合对象
link.addFirst("java01");//在首位添加元素
link.addFirst("java02");
link.addFirst("java03");
link.addFirst("java04");
sop("原集合为"+link);//[java04, java03, java02, java01]
//获取首位元素,获取末位元素
sop("获取的第一位元素是:"+link.getFirst());//java04
sop("获取的最后一位元素是:"+link.getLast());//java01
//删除并返回首位元素,打印集合长度
sop("删除的第一位元素是:"+link.removeFirst());//java04
sop("删除第一位后集合的长度是:"+link.size());//3
sop("再一次删除的第一位元素是:"+link.removeFirst());//java03
sop("最后集合的长度是:"+link.size());//2
//因为removeFirst方法是删除并返回某一个元素,所以第一个元素java04被返回来,并且打印出来
//而打印size的时候,集合的长度已经少了一个。
}
}
运行结果:
因为集合中没有元素的时候会蹦出来异常,所以在jdk1.6版本开始,出现了替代方法:
添加元素:
offerFirst();
offerLast();
获取元素但不删除元素:
peekFirst();
peekLast();
获取元素同时删除元素:
poolFirst();
poolLast();
LinkedList练习:
用LinkedList模拟一个堆栈或者队列的数据结构
堆栈:数据存储和取出方式:先进后出,如同一个杯子
队列:数据存储和取出方式:先进先出,如同一个水管
堆栈示例:
//用LInkedList模拟一个堆栈和队列结构:
/*
思路:根据堆栈先进先出的特点,想到LinkedList中有特有的添加头,添加尾,取出头,取出尾的操作,根据这样的方法,可以实现堆栈的操作
*/
import java.util.*;
class DuiZhan//将堆栈存取方式封装成一个对象,
{
private LinkedList link;//因为堆栈类的底层使用LinkedList的方法
DuiZhan()//所以类一初始化的时候就要创建一个LinkedList集合对象
{
link = new LinkedList();
}
public void myAdd(Object obj)
{
link.addFirst(obj);//添加元素的时候一直在最前边添加(添加完以后的顺序就是倒着的)
}
public Object myGet()
{
return link.removeFirst();//取出元素的时候就要从前边取,以实现堆栈中先进后出
}
public boolean isNull()
{
return link.isEmpty();//底层的判断为空的方法
}
}
public class LinkedListTest {
public static void sop(Object obj)
{
System.out.println(obj);
}
public static void main(String[] args)
{
DuiZhan dz = new DuiZhan();
dz.myAdd("java01");
dz.myAdd("java02");
dz.myAdd("java03");
dz.myAdd("java04");
//sop(dz.myGet());
while (!dz.isNull())
{
sop(dz.myGet());
}
}
}
运行结果为:
队列示例;这就简单了,存储的方式不变,取出的方式从后面开始取就好了,代码如下:
//用LInkedList模拟一个堆栈和队列结构:
/*
思路和堆栈一样的,都是利用LinkedList中的特有方法,实现先进先出的效果。
*/
import java.util.*;
class DuiLie//将队列存取方式封装成一个对象,
{
private LinkedList link;//因为堆栈类的底层使用LinkedList的方法
DuiLie()//所以类一初始化的时候就要创建一个LinkedList集合对象
{
link = new LinkedList();
}
public void myAdd(Object obj)
{
link.addFirst(obj);//添加元素的时候一直在最前边添加(添加完以后的顺序就是倒着的)
}
public Object myGet()
{
return link.removeLast();//取出元素的时候就要从后边取,以实现堆栈中先进先出
}
public boolean isNull()
{
return link.isEmpty();//底层的判断为空的方法
}
}
public class LinkedListTest2 {
public static void sop(Object obj)
{
System.out.println(obj);
}
public static void main(String[] args)
{
DuiLie dl = new DuiLie();
dl.myAdd("java01");
dl.myAdd("java02");
dl.myAdd("java03");
dl.myAdd("java04");
//sop(dl.myGet());
while (!dl.isNull())
{
sop(dl.myGet());
}
}
}
运行结果为:
下面看一个ArrayList的练习,
需求是:删除掉ArrayList中的重复的元素:
//需求:删除ArrayList中的重复元素
/*
思路:在ArrayList中是没有办法判断的,
新建一个ArrayList集合newAl,在al中取一个元素,判断在newAl中有没有相同的元素,
没有,则将该元素,存进newAl中,如果有,则不做操作
最后将newAl返回一个ArrayList,赋值给al就行了
*/
import java.util.*;
public class ArrayListTest {
public static void sop(Object obj)
{
System.out.println(obj);
}
public static ArrayList singleElement(ArrayList al)//删除重复元素的方法
{
ArrayList newAl = new ArrayList();//先建立一个新的集合
for (Iterator it = al.iterator();it.hasNext() ; )//遍历老集合
{
Object obj = it.next();//将遍历出来的元素用obj引用了
if (!newAl.contains(obj))//判断该obj对象在新集合中不存在
{
newAl.add(obj);//则将该元素添加进心集合中,
}
}
return newAl;//最后返回新集合
}
public static void main(String[] args)
{
ArrayList al = new ArrayList();
al.add("java01");
al.add("java02");
al.add("java02");
al.add("java03");
al.add("java03");
al.add("java04");
sop(al);
al = singleElement(al);
sop(al);
}
}
运行结果:
运行完以后,我们的需求就提升了,要将自定义的对象,存进集合当中,然后删除相同的元素。
有了上边的例子,我们信手拈来:
还没有写删除重复元素的代码,先看一下程序能不能运行起来
//需求:将自定义对象存入集合中,并删除重复的元素
/*
定义一个Person类,类中有姓名和年龄,姓名和年龄相同视为同一个人
*/
import java.util.*;
class Person//定义Person类
{
private String name;
private int age;
public void set(String name,int age)
{
this.name = name;
this.age = age;
}
public String getName()
{
return name;
}
public int getAge()
{
return age;
}
}
public class ArrayListTest2 {
public static void sop(Object obj)
{
System.out.println(obj);
}
public static void main(String[] args)
{
ArrayList al = new ArrayList();//创建集合
Person p1 = new Person();//创建对象
Person p2 = new Person();
Person p3 = new Person();
Person p4 = new Person();
p1.set("zhangsan01",21);//给对象设置参数
p2.set("zhangsan02",22);
p3.set("zhangsan03",23);
p4.set("zhangsan04",24);
al.add(p1);//在集合中天剑参数
al.add(p2);
al.add(p3);
al.add(p4);
for (Iterator it = al.iterator();it.hasNext() ; )//通过迭代器列出集合中的元素,
{
Object obj = it.next();
sop(obj.getName()+"......"+obj.getAge());//为了显示的清楚一写,直接显示的是对象中的姓名和年龄属性。
}
}
}
编译的时候出现了如下错误:
图中显示,编译失败,为什么失败呢?找不到getName和 getAge方法。
这是因为使用add方法添加的时候,add中的参数是(Object obj),
所以将Person对象存入集合的时候,对Person对象进行了提升操作,提升成他的父类Object,
Object p = new Person();
所以在迭代器中使用obj.getName()方法的时候,这里的obj是Object类型的,
Object中没有getName()方法,所以会提示出这样的错误
看不懂的话返回去重新学习继承那一篇
解决方法就是在打印前对obj进行强制转换。
//需求:将自定义对象存入集合中,并删除重复的元素
/*
定义一个Person类,类中有姓名和年龄,姓名和年龄相同视为同一个人
*/
import java.util.*;
class Person//定义Person类
{
private String name;
private int age;
public void set(String name,int age)
{
this.name = name;
this.age = age;
}
public String getName()
{
return name;
}
public int getAge()
{
return age;
}
}
public class ArrayListTest2 {
public static void sop(Object obj)
{
System.out.println(obj);
}
public static ArrayList singleElement(ArrayList al)//删除重复元素的方法
{
ArrayList newAl = new ArrayList();//先建立一个新的集合
for (Iterator it = al.iterator();it.hasNext() ; )//遍历老集合
{
Object obj = it.next();//将遍历出来的元素用obj引用了
if (!newAl.contains(obj))//判断该obj对象在新集合中不存在
{
newAl.add(obj);//则将该元素添加进心集合中,
}
}
return newAl;//最后返回新集合
}
public static void main(String[] args)
{
ArrayList al = new ArrayList();//创建集合
Person p1 = new Person();//创建对象
Person p2 = new Person();
Person p3 = new Person();
Person p4 = new Person();
Person p5 = new Person();
Person p6 = new Person();
p1.set("zhangsan01",21);
p2.set("zhangsan02",22);
p3.set("zhangsan03",23);
p4.set("zhangsan04",24);
p5.set("zhangsan02",22);//重复元素
p6.set("zhangsan03",23);//重复元素
al.add(p1);
al.add(p2);
al.add(p3);
al.add(p4);
al.add(p5);//重复元素
al.add(p6);//重复元素
al = singleElement(al);//去除重复元素
for (Iterator it = al.iterator();it.hasNext() ; )//再迭代打印出来
{
Object obj = it.next();
Person p = (Person)obj;
sop(p.getName()+"......"+p.getAge());
}
}
}
运行结果为:
为什么为出现这样的情况呢?
这是因为,在singleElement方法中迭代判断的时候,用到了contains方法,
contains方法:集合中是否包含参数中的对象,
该方法的底层原理就是将自己集合中的每一个元素和参数中的元素进行比较,
怎么比较呢?当然是用equals方法,
而在ArrayList中的equals方法比较的是地址值,
集合中添加进来的东西都是新创建的,地址值肯定不一样的,所以他会认为添加的每一个元素都不同,所以就不会清楚元素。
而我们的实际需求是,姓名年龄相同就视为同一个人,
所以在这里我们要重写Person类的equals方法:目的在于当使用Person类对象进行比较的时候,用我自己定义的规则来比较,不再比较哈希值
修改代码:值需要将Person类中多写一个equals方法就行了。
//需求:将自定义对象存入集合中,并删除重复的元素
/*
定义一个Person类,类中有姓名和年龄,姓名和年龄相同视为同一个人
*/
import java.util.*;
class Person//定义Person类
{
private String name;
private int age;
public void set(String name,int age)
{
this.name = name;
this.age = age;
}
public String getName()
{
return name;
}
public int getAge()
{
return age;
}
//重写了equals方法
public boolean equals(Object obj)
{
if (!(obj instanceof Person))//如果传进来的对象不是Person类的
return false;//直接返回假
Person p = (Person)obj;//否则将obj强制转换成person类型的
return this.name.equals(p.name) && this.age==p.age;//然后比较他们的name属性是否相同,和age属性是否相同
}
}
public class ArrayListTest2 {
public static void sop(Object obj)
{
System.out.println(obj);
}
public static ArrayList singleElement(ArrayList al)//删除重复元素的方法
{
ArrayList newAl = new ArrayList();//先建立一个新的集合
for (Iterator it = al.iterator();it.hasNext() ; )//遍历老集合
{
Object obj = it.next();//将遍历出来的元素用obj引用了
if (!newAl.contains(obj))//判断该obj对象在新集合中不存在
{
newAl.add(obj);//则将该元素添加进心集合中,
}
}
return newAl;//最后返回新集合
}
public static void main(String[] args)
{
ArrayList al = new ArrayList();//创建集合
Person p1 = new Person();//创建对象
Person p2 = new Person();
Person p3 = new Person();
Person p4 = new Person();
Person p5 = new Person();
Person p6 = new Person();
p1.set("zhangsan01",21);
p2.set("zhangsan02",22);
p3.set("zhangsan03",23);
p4.set("zhangsan04",24);
p5.set("zhangsan02",22);
p6.set("zhangsan03",23);
al.add(p1);
al.add(p2);
al.add(p3);//重复元素
al.add(p4);
al.add(p5);
al.add(p6);//重复元素
al = singleElement(al);//去除重复元素
for (Iterator it = al.iterator();it.hasNext() ; )//再迭代打印出来
{
Object obj = it.next();
Person p = (Person)obj;
sop(p.getName()+"......"+p.getAge());
}
}
}
运行结果为:
这个小程序就结束了,,然后需要我们注意的是,在contains方法中,底层调用的Person类的equals方法,因为他要判断自己里边的元素和传进来的元素是否一样,调用equals方法,
删除元素:底层调用的也是equals方法,比如在以上的代码中,
al.remove(new Person("zhangsan01",21));
//他会拿括号中的这个参数的对象去找自己集合里边有没有这样一个元素,拿自己的元素调用equals方法和参数中的对象进行比较,如果如果存在返回真,如果不存在,返回假。
那么在开发的时候用哪个集合呢?
看需求:涉及到比较频繁的增删操作,用LinkedList
涉及到查询操作,但不是很频繁的时候,用ArrayList
只要没有太频繁的操作,一般都是用ArrayList
Set
元素是无序的(就是说存入的顺序和取出的顺序不一样),而且元素不可以重复。
查看API发现Set集合的方法和Collection的方法是一样的。
实现了Set接口的类有:
- HashSet:底层数据结构是哈希表;
- TreeSet:
HashSet集合
什么是哈希值呢?
当我们创建好一个对象,并且直接打印对象的时候,打印出来的东西就是哈希值。
对象在HashSet中是怎么存储的呢?
当创建好一个对象d1,d1就有了自己的哈希值(hashCode方法可以算出来),当将d1添加进集合中的时候,会将d1的哈希值给了集合,
当创建了又一个对象d2的时候,将d2天剑进来的时候,再将d2的哈希值给了集合,
两个对象的哈希值会进行比较,哈希值比较大的就会排在后边,哈希值比价小的就会排在前边。
这也是为什么HashSet中对象为什么是无序的了。
HashSet集合中的存储方式
对象被添加进HashSet中的时候,会那自己的哈希值和集合中的哈希值进行比较,
如果哈希值不一样,就会存储进来,
如果哈希值一样,会再次进行比较对象是否一样,
如果对象一样,则不存储,add方法返回假,
如果对象不一样,则会在原有的哈希值上边顺延,将对象存储进来,add方法返回真。
//需求:简单演示一下HashSet集合的存储方法和存储特点
import java.util.*;
public class HashSetDemo {
public static void sop(Object obj)
{
System.out.println(obj);
}
public static void main(String[] args)
{
HashSet hs = new HashSet();
hs.add("java01");
sop(hs.add("java02"));//打印结果为真
sop(hs.add("java02"));//重复元素,打印结果为假
hs.add("java03");
hs.add("java04");
hs.add("java04");//重复元素
//取出Set中的元素的方法,只有一个就是迭代器:
for (Iterator it = hs.iterator(); it.hasNext(); )
{
sop(it.next());
}
}
}
运行结果为:
由上图可以看出,HashSet中存储的元素是无序的,而且可以保证对象的唯一性。
在HashSet中存储自定义对象
还是上边的需求,对象人,有属性name和age,相同name和age的元素视为同一个人。
//往HashSet方法中添加自定义对象,
import java.util.*;
class Person
{
private String name;
private int age;
Person(String name,int age)
{
this.name = name;
this.age = age;
}
public String getName()
{
return name;
}
public int getAge()
{
return age;
}
//在Person类中重写了equals方法。
public boolean equals(Object obj)//这里必须写的是Object类型的参数,如果不是说明复写的不是Object类中的额方法
{
//因为传进来的参数是Object类型的,所以在这里要判断一下
if (!(obj instanceof Person))
return false;
//如果obj属于Person对象的话,强转。
Person p = (Person)obj;
//这里多假一条打印语句,看看equals方法有没有被执行到。
System.out.println("name= "+p.name+"...age= "+p.age);
return this.name.equals(p.name) && this.age == p.age;
}
}
public class HashSetDemo2 {
public static void sop(Object obj)
{
System.out.println(obj);
}
public static void main(String[] args)
{
HashSet hs = new HashSet();
hs.add(new Person("zhangsan01",21));
hs.add(new Person("zhangsan02",22));
hs.add(new Person("zhangsan03",23));
hs.add(new Person("zhangsan04",24));
hs.add(new Person("zhangsan04",24));//重复元素
for (Iterator it = hs.iterator();it.hasNext();)
{
Object obj = it.next();
Person p = (Person)obj;
sop("name= "+p.getName()+"...age= "+p.getAge());
}
}
}
运行结果为:
一看结果,郁闷的要死。equals方法根本就没有执行,而且重复元素也存进来了,
然后一想,HashSet集合中的底层数据是哈希表,按照HashSet中的存储规则,
我们每一次存进去的对象都是新new出来的,哈希值(地址值)肯定不一样了,
要按照我们判断元素唯一的方法去存储数据的话,需要重写hashCode方法。
根据这个思路,我们修改了代码:
//往HashSet方法中添加自定义对象,
import java.util.*;
class Person
{
private String name;
private int age;
Person(String name,int age)
{
this.name = name;
this.age = age;
}
public String getName()
{
return name;
}
public int getAge()
{
return age;
}
//在Person类中重写了equals方法。
public boolean equals(Object obj)//这里必须写的是Object类型的参数,如果不是说明复写的不是Object类中的额方法
{
//因为传进来的参数是Object类型的,所以在这里要判断一下
if (!(obj instanceof Person))
return false;
//如果obj属于Person对象的话,强转。
Person p = (Person)obj;
//这里多假一条打印语句,看看equals方法有没有被执行到。
System.out.println(this.name+"...equals..."+p.name);
return this.name.equals(p.name) && this.age == p.age;
}
//重写hashCode方法。
public int hashCode()
{
System.out.println(this.name+".......hashCode");
return this.name.hashCode() + this.age*7;
}
}
public class HashSetDemo2 {
public static void sop(Object obj)
{
System.out.println(obj);
}
public static void main(String[] args)
{
HashSet hs = new HashSet();
hs.add(new Person("zhangsan01",21));
hs.add(new Person("zhangsan02",22));
hs.add(new Person("zhangsan03",23));
hs.add(new Person("zhangsan04",24));
hs.add(new Person("zhangsan04",24));//重复元素
for (Iterator it = hs.iterator();it.hasNext();)
{
Object obj = it.next();
Person p = (Person)obj;
sop("name= "+p.getName()+"...age= "+p.getAge());
}
}
}
运行结果:
从以上程序中我们可以看出来:HashSet是如何保证元素的唯一性的呢?
是通过元素的两个方法:hashCode和equals来完成的。
如果元素的HashCode值相同,才会判断equals是否为true;
如果元素的HashCode值不同,不会调用equals方法。
不仅是在存储元素上,在判断存在(contains)和删除(remove)元素上,依赖的也是hashCode和equals方法。