在java中,用来存放内容的容器一般为数组,但是,数组有其局限性,比如,数组的长度是不变的,存放内容的类型是固定的,因此java提供了新的容器,也就是集合
集合的概念:类似于数组,但是集合的长度不是固定的,存放的内容也不是固定的。
集合实际上是一个借口,根据底层的数据结构划分出很多子类,具体如下:
Collection就是集合类
=============================================================================================================================
首先介绍Collection中定义的方法,也就是子类中的一些共有方法:
无论是什么容器,都应该包含增,删,改,查这四个功能。
增:
add(Object obj)//在末尾加入输入的对象
addAll(Collection c)//在末尾加入一个集合
删:
boolean remove(Object obj)//删除集合中第一个obj对象,如果集合中不存在obj对象,返回false
boolean removeAll(Collection c)//把集合中与集合c内容相同的部分去掉,即使多处重复也全部去掉,没有重复返回false
clear()//把集合中所有内容都清空
改:
retainAll(Collection c)//把集合的内容变为该集合与集合c内容相同的部分
查(使用迭代器):
Iterator iterator()//用于获取迭代器
迭代器相当于一个初始指向-1位置的指针,通过移动迭代器的指向来遍历集合内容
对于迭代器对象有三个方法:hasNext()//判断是否具有下一个元素,迭代器并有因此指向下一个元素
next()//迭代器指向下个元素,并返回下一个元素
remove()//把迭代器当前指向的元素删除
具体使用代码如下:
import java.util.*;
class CollectionDemo
{
public static void main(String[] args)
{
Collection c=new ArrayList();
c.add("01");
c.add("02");
c.add("03");
c.add("04");
c.add("05");
Iterator it=c.iterator();
while(it.hasNext())
{
System.out.println(it.next());
}
}
}
结果:
还有一些其他的方法:
isEmpty()//判断集合是否为空,如果为空返回true,否则返回false
boolean contains(Object obj)//判断集合中是否具有指定对象
boolean containsAll(Collection c)//判断集合中是否包含指定集合
int size()//返回集合长度
==============================================================================================================================
List接口(List集合中元素是有序的,元素可以重复,因为该集合体系有索引):
在List接口下还有具体实现,当然在List也定义有其子类的共有方法:
在Collection接口的基础下,List定义了一些特有的功能,比较重要的几个如下:
get(int index)//获得指定索引位置的元素
set(int index,Object obj)//将集合中指定索引位置的内容改为obj
add(int index,Object element)//在指定索引位置处加入指定元素
addAll(int index,Collection c)//在指定索引位置处加入指定集合
remove(int index)//把集合中指定索引位置的内容删除
List subList(int begin,int end)//获取子集合,不含end位置上的内容
ListIterator listIterator()//获取列表迭代器
列表迭代器ListIterator是迭代器Iterator的子类
无论是什么迭代器,只要迭代器在作用时,就无法同时使用集合的方法去操作集合,否则会抛出ConcurrentModificationException
那么原本的迭代器Iterator只有三个基本的方法,无法实现在遍历的同时修改集合,因此java提供了ListIterator,相比于Iterator,ListIterator定义了更多的方法,如下是关键的几个方法:
set(Object obj)//把当前内容用指定对象去替代
add(Object obj)//在当前元素的后面添加指定对象内容(注意此时列表迭代器指向加入的元素位置)
ListIterator还提供了逆向遍历的功能
hasPrevious()//判断是否具有前一个元素
previous()//获得前一个元素
具体代码实现:
import java.util.*;
class CollectionDemo
{
public static void main(String[] args)
{
List c=new ArrayList();
ListIterator it=c.listIterator();
it.add("01");
it.add("02");
System.out.println(c);
while(it.hasPrevious())
{
System.out.println(it.previous());
}
it.set("03");
System.out.println(c);
}
}
结果:
除了这些方法外还有其他的方法,因为List集合具有索引,所以字符串的功能List集合基本都有,比如indexOf,有的名称不同,具体查阅API文档
在List接口下还分为三类:ArrayList,LinkedList,Vector
这三类是根据底层数据结构划分的
ArrayList采用数组形式存储,查询速度快,增减元素速度慢
LinkedList采用链表形式存储,查阅速度慢,但增减元素速度快
Vector也采用数组形式存储
ArrayList与Vector区别在于ArrayList不是同步的,Vector是同步的,因此Vector效率极低,因此基本不用
==============================================================================================================================
Set接口(数据的存储是无序的,所谓无序就是存的顺序和取出的顺序是没有必然联系的,而且存储的内容不能重复):
在Set接口下最重要的实现就是HashSet
HashSet的底层结构是哈希表
HashSet集合没有自己特有的功能,全部继承于Collection
HashSet集合再进行增加,修改等操作时底层都要先进行hash值的判断,如果hash值相同时,还要使用equals方法去判断内容是否也相同,从而保证集合中不会出现重复的元素,顺便说一下,如果内容相同,hash值不同并不算相同元素
以增加元素为例:执行add方法时,底层会先判断hash值,如果和集合中其他元素的hash值不同,直接就存入集合中,如果hash值和集合中的某个元素的hash值相同,那么再调用equals方法判断内容是否相同,如果不同就存入集合中,否则,存入失败,add方法返回false
由上面的解释不难看出在HashSet集合内hash值和equals方法是关键,在实际使用中,除了字符串,其他类和自定义类的equals方法都是比较地址值,因此,更多情况下,我们应该根据需要的内容来重写equals方法,比如对于Person类,实际中,可以把同名同年龄的看成同一个人,如果定义两个Person对象,而且年龄和名字一样,但是它们自动生成的hash值不一样,那么直接看成是不同的两个人,因此,应该重写equals方法和hashCode方法
注意:地址值并不是hash值,因此,hash值相同,equals(未重写)的结果也会是false
代码解释:
import java.util.*;
class Person
{
public int age;
public String name;
public Person(String name,int age)
{
this.name=name;
this.age=age;
}
public int hashCode()//重写hashCode方法,使得所有元素hash值相同
{
return 15;
}
/*这里重写了equals方法*/
public boolean equals(Object obj)
{
if(!(obj instanceof Person))
return false;
Person p=(Person)obj;//不能少,因为传参使用了多态
System.out.println(this.name+"..equals.."+p.name);
if(this.name==p.name&&this.age==p.age)
{
System.out.println(this.name+" "+this.age+" Cannot add!!");
return true;
}
else
{
System.out.println(this.name+" "+this.age+" Success!!");
return false;
}
}
}
class HashSetDemo
{
public static void main(String[] args)
{
HashSet hs = new HashSet();
hs.add(new Person("lisi",10));
hs.add(new Person("lisi",10));
hs.add(new Person("zhangsan",10));
hs.add(new Person("zhangsan",55));
Iterator it=hs.iterator();
while(it.hasNext())
{
Person p=(Person)it.next();
System.out.println(p.name+"...."+p.age);
}
}
}
结果如下:
输出并不是倒序,而是乱序,只是碰巧。。
这种方法把所有hash值都统一了,即使有的元素的内容不同,也要进行equals,效率不高,如果能让hash值与内容相关,那么效率将提高
import java.util.*;
class Person
{
public int age;
public String name;
public Person(String name,int age)
{
this.name=name;
this.age=age;
}
public int hashCode()
{
return this.name.hashCode()+this.age;
}
public boolean equals(Object obj)
{
if(!(obj instanceof Person))
return false;
Person p=(Person)obj;//不能少,因为传参使用了多态
System.out.println(this.name+"..equals.."+p.name);
if(this.name==p.name&&this.age==p.age)
{
System.out.println(this.name+" "+this.age+" Cannot add!!");
return true;
}
else
{
System.out.println(this.name+" "+this.age+" Success!!");
return false;
}
}
}
class HashSetDemo
{
public static void main(String[] args)
{
HashSet hs = new HashSet();
hs.add(new Person("lisi",10));
hs.add(new Person("lisi",10));
hs.add(new Person("zhangsan",10));
hs.add(new Person("zhangsan",55));
Iterator it=hs.iterator();
while(it.hasNext())
{
Person p=(Person)it.next();
System.out.println(p.name+"...."+p.age);
}
}
}
结果:
不难看出调用equals方法的次数大大减少。。
============================================================================================================================
TreeSet也是Set接口下的一个实现
TreeSet集合中的内容虽然是无序的(输入输出顺序不同),但是它是根据元素内容,并按照一定规律排序的
TreeSet底层数据结构是二叉树
TreeSet保证元素不重复有两种办法,但这两种方法的实质是一样的,都是通过自行定义排序规则,当比较时出现相同时就放弃存储
方法一:让需要存储的对象的类实现Comparable接口,并重写compareTo方法,定义自己的比较规则
代码解释:
import java.util.*;
class Person implements Comparable
{
public int age;
public String name;
public Person(String name,int age)
{
this.age=age;
this.name=name;
}
public int compareTo(Object obj)//根据年龄大小升序排序,当返回0时,表示两个人是同一个人
{
Person p=(Person)obj;
return this.age-p.age;
}
}
class TreeSetDemo
{
public static void sop(Object obj)
{
System.out.println(obj);
}
public static void main(String[] args)
{
TreeSet ts=new TreeSet();
ts.add(new Person("01",10));
ts.add(new Person("02",10));
ts.add(new Person("03",20));
ts.add(new Person("04",30));
Iterator it=ts.iterator();
while(it.hasNext())
{
Person p=(Person)it.next();
sop(p.name+"...."+p.age);
}
}
}
结果:
不难看出02并没有传进去,这是因为判断规则只与年龄有关,但实际和名字也有关,因此,在重写规则时应注意当有多个条件时,要先判断主要条件,相同时还要判断次要条件
改进:
import java.util.*;
class Person implements Comparable
{
public int age;
public String name;
public Person(String name,int age)
{
this.age=age;
this.name=name;
}
public int compareTo(Object obj)
{
Person p=(Person)obj;
int num=this.age-p.age;
if(num==0)
return this.name.compareTo(p.name);//字符串的equals方法返回boolean,compareTo方法放回int
return num;
}
}
class TreeSetDemo
{
public static void sop(Object obj)
{
System.out.println(obj);
}
public static void main(String[] args)
{
TreeSet ts=new TreeSet();
ts.add(new Person("01",10));
ts.add(new Person("02",10));
ts.add(new Person("03",20));
ts.add(new Person("04",30));
Iterator it=ts.iterator();
while(it.hasNext())
{
Person p=(Person)it.next();
sop(p.name+"...."+p.age);
}
}
}
结果:
ok!!
解释一下底层二叉树的存储方式:
当比较值为负数时,输入元素会走左边路径继续比较,如果没有元素可比了,就存储在该点,同理,如果为正值,则走右边路径,如果为0则放弃输入元素
给个代码:
import java.util.*;
class Person implements Comparable
{
public int age;
public String name;
public Person(String name,int age)
{
this.age=age;
this.name=name;
}
public int compareTo(Object obj)
{
Person p=(Person)obj;
int num=this.age-p.age;
if(num==0)
{
TreeSetDemo.sop(this.name+"...compare.."+p.name+" == "+this.name.compareTo(p.name));
return this.name.compareTo(p.name);//字符串的equals方法返回boolean,compareTo方法放回int
}
TreeSetDemo.sop(this.name+"...compare.."+p.name+" == "+num);
return num;
}
}
class TreeSetDemo
{
public static void sop(Object obj)
{
System.out.println(obj);
}
public static void main(String[] args)
{
TreeSet ts=new TreeSet();
ts.add(new Person("01",50));
ts.add(new Person("02",30));
ts.add(new Person("03",60));
ts.add(new Person("04",30));
ts.add(new Person("05",20));
ts.add(new Person("06",10));
ts.add(new Person("07",50));
Iterator it=ts.iterator();
while(it.hasNext())
{
Person p=(Person)it.next();
sop(p.name+"...."+p.age);
}
}
}
结果:
二叉树存储如下:
方法二:定义一个比较器对象作为参数用来初始化TreeSet集合
具体步骤:1,自定义一个类使其实现Comparator接口,把这个类称为比较器类
2,重写compare方法(注意不是compareTo)
3,建立一个比较器类对象
4,使用该对象初始化TreeSet集合
代码:
import java.util.*;
class MyComparator implements Comparator
{
public int compare(Object obj1,Object obj2)
{
if(!((obj1 instanceof Person)&&(obj2 instanceof Person)))
throw new ClassCastException();
Person p1=(Person)obj1;
Person p2=(Person)obj2;
int num=p1.age-p2.age;
if(num==0)
{
return p1.name.compareTo(p2.name);
}
return num;
}
}
class Person
{
public int age;
public String name;
public Person(String name,int age)
{
this.age=age;
this.name=name;
}
}
class TreeSetDemo
{
public static void sop(Object obj)
{
System.out.println(obj);
}
public static void main(String[] args)
{
TreeSet ts=new TreeSet(new MyComparator());//用比较器初始化TreeSet
ts.add(new Person("01",50));
ts.add(new Person("02",30));
ts.add(new Person("03",60));
ts.add(new Person("04",30));
ts.add(new Person("05",20));
ts.add(new Person("06",10));
ts.add(new Person("07",50));
Iterator it=ts.iterator();
while(it.hasNext())
{
Person p=(Person)it.next();
sop(p.name+"...."+p.age);
}
}
}
结果:
ok~