集合(java.util包中)又称为容器,数据在内存中存储方式称为数据结构,各自自身的特点不同,每一个容器对数据的存储方式不同,所以就出现了多钟容器,对多种进行共性抽取(不断向上抽取的往往是抽象的),即产生体系,称为集合框架。集合框架是用来表现和操纵集合的一个统一的体系结构。
抽取到顶层即collection,定义体系的最基本、最共性的功能。而使用一般是选择底层,因为1、顶层的抽象不能创建对象。2、创建子类对象的方法更多。看顶层基本共性,参阅底层创建对象。
共性方法:
1、add方法的参数是object,以便于接受任意类型的对象
2、集合中存储的都是对象的引用(地址)
迭代器:
迭代器就是集合的取出元素的方式,对于取出这个动作不足以用一个函数描述,需要用多个功能来体现,一般情况下,把多个功能封装到对象中去。每一个容器(如Set、List)都存在一个取出的对象(内部类操作集合中的元素更加方便),数据结构不同,每一个对象中取出的实现不用,但都有共性的内容,即判断和取出,将共性抽取形成了接口Iterator。
通过对外提供的方法iterator()获取集合的取出对象。
常见子接口
List集合:元素是有序的,元素可以重复。该集合体系有索引。
特有方法:凡是可以操作角标的方法
指定位置插入元素,可以通过索引获取,可以判断元素的位置,根据位置获取某一个元素,list集合特有的迭代器。可以改变一个位置的元素。
增
add(index,element);
addAll(index,Collection);
删
remove(index);
修改元素
set(index,element);
通过角标获取元素
get(index);
subList(from,to);包含from,不包括to
listIterator();
List集合特有的迭代器,ListIterator是Iterator的子接口。
在迭代时,不可以通过集合对象的方法操作集合中的元素,会发生并发修改异常,迭代过程中只能使用迭代器方法操作元素,可是Iterator方法是有限的,如果想要其他操作如添加,修改,就需要使用子接口,ListIterator。该接口只能通过List集合的listIterator方法获取。 List集合判断元素是否相同时(contains和remove),依据的是元素的equals方法(比较的都是地址)。
List中常见三个子对象(底层数据结构不一样)
1、ArrayList:底层的数据结构使用的是数组结构。特点:查询速度很快,增加删除比较麻烦。线程不同步。不断new数组产生可变长度数组。超过其默认创建数组长度(10)就开始百分之五十延长。
举例:去除ArrayListTest集合中的重复元素。
import java.util.*;
class ArrayListTest
{
public static void main(String[] args)
{
ArrayList al = new ArrayList();
al.add("java01");
al.add("java02");
al.add("java01");
al.add("java02");
al.add("java01");
al.add("java02");
sop(al);
al = singleElement(al);
sop(al);
}
public static ArrayList singleElement(ArrayList al)
{
//定义一个临时容器
ArrayList newAl = new ArrayList();
Iterator it = al.iterator();
while(it.hasNext())
{
Object obj = it.next();
if(!newAl.contains(obj))
newAl.add(obj);
}
return newAl;
}
public static void sop(Object obj)
{
System.out.println(obj);
}
}
举例2:将自定义对象作为元素存到ArrayList集合中,并且去除重复元素
比如:存人对象,同姓名,同年龄的视为同一个人,为重复元素。
思路:
1、对人描述,将数据封装进人对象
2、定义容器,将人存入
3、取出
class Person
{
private String name;
private int age;
Person(String name, int age)
{
this.name = name;
this.age = age;
}
public boolean equals(Object obj)
{
if(!(obj instanceof Person))
return false;
Person p = (Person)obj;
return this.name.equals(p.name)&&this.age ==p.age; //这里的equals是字符串(name)的equals方法
}
public String getName()
{
return name;
}
public int getAge()
{
return age;
}
}
class ArrayListTest2
{
public static void sop(Object obj)
{
System.out.println(obj);
}
public static void main(String[] args)
{
ArrayList al = new ArrayList();
/*ArrayList容器中判断相同的条件是使用了equals,只判断存入的对象(Person)是否相同,new Person中默认来自Object,而Object中equals判断对象相同不相同使用的是地址,所以这里的对象的地址都不同,即无法删除相同元素,按照我们的方法比较,重写equals*/
al.add(new Person("lisi01",30)); //存对象 al.add(object obj)
al.add(new Person("lisi02",34));
al.add(new Person("lisi03",33));
al.add(new Person("lisi02",34));
al.add(new Person("lisi03",33));
al = singleElement(al);
Iterator it = al.iterator();
while(it.hasNext())
{
//sop(it.next().getName()+"::"+it.next().getAge());
/*getName不是Object(存对象时存入Object,迭代器不知道里面装的是什么元素,所以往回返的都是Object,it.next()往回返的时候返回的是Object)的方法,更改如下,用子对象特有方法时,向下转型*/
Person p = (Person)it.next();
sop(p.getName()+"::"+p.getAge());
}
}
public static ArrayList singleElement(ArrayList al)
{
//定义一个临时容器
ArrayList newAl = new ArrayList();
Iterator it = al.iterator();
while(it.hasNext())
{
Object obj = it.next();
if(!newAl.contains(obj))
//contains调用equals方法,remove方法的底层调用的也是equals,所以重写equals时,对比的还是地址。
newAl.add(obj);
}
}
2、LinkedList:底层使用的是链表数据结构。查询非常慢,但是增删方便
LinkedList特有方法:
addFirst();(1.6版本中变成了offerFirst())
addLast();(1.6版本中变成了offerLast())
getFirst();(1.6版本中变成了peekFirst())
getLast();(1.6版本中变成peekLast();在这个函数中,如果列表为空,返回null,但是getLast()中如果列表为空则返回没有这个元素的异常。即NoSuchElementException)
get方法,获取元素但是不删除元素,remove也可以获取元素但是元素被删除。
removeFirst();(1.6版本中变成pollFirst();集合中没有元素返回null)
removeLast();(1.6版本中变成pollLast();)
举例:使用LinkedList模拟一个堆栈或者队列数据结构
堆栈:先进后出,如同一个杯子
队列:先进先出,如同一个水管
/*为什么要封装?直接用LinkedList是可以完成的,但是它只有自身含义,我们想做成与项目相关的容器,起一些特定的名称来用,更加方便。这个时候把原有的集合封装进我们的描述当中,并对外提供一个自己更加清楚、更能识别的名称。*/
import java.util.*;
class Queue
{
private LinkedList link;
Queue()
{
link = new LinkedList();
}
public void myAdd(Object obj)
{
link.addFirst(obj);
}
public Object myGet()
{
return link.removeLast();
}
public boolean isNull()
{
return link.isEmpty();
}
}
class LinkedListTest
{
public static void main(String[] args)
{
Queue d1 = new Queue();
d1.myAdd("java01");
d1.myAdd("java02");
d1.myAdd("java03");
while(!d1.isNull())
{
System.out.println(d1.myGet());
}
}
}
//更改myGet中的removeLast为removeFirst则为堆栈,即先进后出
3、 Vector:底层是书序数据结构,Vector比ArrayList出现的早,线程同步,被ArrayList替代了,增删与查询都慢。不断new数组产生可变长度数组。超过其默认创建数组长度(10)就开始百分之百延长。枚举是Vector特有的取出方式,发现枚举和迭代器很像,其实是一样的,由于枚举的名称以及方法的名称都过长,所以被迭代器取代了,枚举郁郁而终了。
假使元素中涉及到挺多的增删,那么就考虑使用LinkedList,如果涉及增删不频繁,那么可以使用ArrayList和LinkedList,如果涉及增删也涉及查询,那么选用ArrayList,频繁的增删不多见。
Set集合:元素是无序的(存入和取出的顺序不一定一致,有序是怎么存入,怎么取出),元素不可重复。
Set集合的功能呢和Collection是一致的。
重点关注子类对象,常见的子类:HashSet(线程非同步的。底层数据结构是哈希表:保证集合不重复的依据是HashCode值是否相同,相同则继续判断equals方法是否为真)和TreeSet(可以对Set集合中的元素进行排序)
Hash表:Hash值一样再比较对象是否一样,不是同一个对象
,这个时候会在该地址下顺延。同一个地址,同一个对象时,则不存,即元素不可重复。Hash值不同,直接另存。
往hashset集合中存入自定义对象,姓名和年龄相同都为同一个人,重复元素。
import java.util.*;
class HashSetTest
{
public static void sop(Object obj)
{
System.out.println(obj);
}
public static void main(String[] args)
{
HashSet hs = new HashSet();
hs.add(new Person("a1",11));
hs.add(new Person("a2",4));
hs.add(new Person("a3",21));
hs.add(new Person("a4",13));
/*四个独立的new建立了四个新的地址,对应为其hash值,重新建立Person对象自己的hash值(复写hashCode),依据判断条件生成hash值*/
Iterator it = hs.iterator();
sop("a1:"+hs.contains(new Person("a2",4)));
hs.remove(new Person("a3",21))
while(it.hasNext())
{
Person p = (Person)it.next();
sop(p.getName()+"::"+p.getAge());
}
}
}
class Person
{
private String name;
private int age;
Person(String name, int age)
{
this.name = name;
this.age = age;
}
public int hashCode()
{
System.out.println(this.name+"……hashcode");
//帮助查看hash值什么时候调用
//return 60;
/*生成Hash值,equals运行,所有hash值都是60,所以在地址上依次比较对象内容,不相同的时候,在原位置下,顺延地址往下存。每一个hash值一样,这样是不合适的,按照条件设定hash值*/
return name.hashCode()+age;
//字符串本身有hashCode的方法,为保证Hash值唯一,可以将年龄乘上某一个数据
}
public String getName()
{
return name;
}
public int getAge()
{
return age;
}
public boolean equals(Object obj)
{
if(!(obj isntanceof Person))
return false;
Person p = (Person)obj;
System.out.println(this.name+"……equals……"+p.name);
return this.name.equals(p.name)&&this.age ==p.age; //这里的equals是字符串(name)的equals方法
}
}
HashSet如何保证元素的唯一性的?是通过元素的两个方法,hashcode和equals来完成的。如果元素的hashCode值相同,才会判断equals是否为true,如果元素的hashcode不同,才会调用equals
当自已定义对象时,要复写hashCode和equals,注意复写函数的时候不要把函数名和参数写错。对于判断元素是否存在,添加或者删除等操作,依赖的方法是元素的hashCode和equals方法,先判断hashCode,如果有,则判断equals。有可能对象要存放到HashSet中,如果不复写hashCode,那么存放的hashCode都依据地址来判断,如果都是new出来的对象,地址自然不同,那么就不会判断对象内容。hashCode和equals不是自己调用的,是底层调用。查看调用过程可以通过输出语句来看看。
TreeSet:TreeSet集合可以排序,但是你没有指定按照什么方式排序的时候,元素具备比较性才能排序,TreeSet的要求是存的对象必须具备比较性,如何具备比较性?实现接口Comparable(预先使用接口中的方法),只要实现接口就具备了比较性。Comparable接口强行对实现它的类的对象整体排序,这种排序称为类的自然排序。类的comparaTo方法被称为它的自然比较方法。复写compareTo函数,此函数判断比较的主要条件和次要条件记住,排序时,当主要条件相同时,一定要判断一下次要条件。
TreeSet底层的数据结构:二叉树,也叫红黑树,元素多的时候取中间值,方便比较大小。保证元素唯一性的依据是compareTo方法return 0.
TreeSet排序的第一种方式:让元素自身具备比较性,元素需要实现Comparable接口,覆盖compareTo方法,也称为元素的自然顺序,或者叫做默认顺序。元素一定义完,自身具有比较性。
TreeSet的第二种排序方式,当元素自身不具备比较性时,或者具备比较性不是所需要的,这时就需要让集合自身具备比较性。在集合初始化时,就有了比较方式。即参与构造方法,定义比较器,将比较器对象作为参数传递给TreeSet集合的构造函数。
当两种排序都存在时,以比较器为主。定义一个类,实现Comparator接口,覆盖compare方法。