集合类
为什么会出现集合类?、
面向对象语言对事物的体现都是以对象的形式,所以为了方便对多个对象的操作,
就对对象进行存储,集合就是存储对象最常用一种方式。
数组和集合类同时容器,有何不同?
数组虽然可以存储对象,但长度是固定的。集合长度是可变的。
数组中可以存储基本类型数据,集合只能存储对象。
集合特点:
1.集合只用于存储对象
2.集合长度可变
3.集合可以存储不同类型的对象,但不能存储基本类型数据。
集合容器因为内部数据结构不同,有多种具体容器。不断向上抽取,就形成了集合框架。
框架的顶层Collection结构:
Collection的常见方法:
1.添加:
boolean add(Object obj); //添加成功就返回真,否则返回假
boolean addAll(Collection coll);
2.删除:
boolean remove(Object obj);
boolean removeAll(Collection coll); //将两个集合中的相同元素从调用removeAll的集合中删除
void clear(); //全部删除,清空
3.判断
boolean contains(Object obj);
boolean containsAll(Collectin coll);
boolean isEmpty(); //判断集合中是否有元素
4.获取
int size();
Iterator iterator(); //取出元素的方式:迭代器。该对象必须依赖于具体容 器,因为每一个容器的数据结构都不同。所以该迭代器对象是在容器内部进行实现的(内部类)。对于使用容器者而言,具体的实现不重要,只要通过容器获取到该实现迭代器的对象即可,也就是iterator方法。iterator接口是对所有的collection容器进行元素取出的公共接口。
5.其他:
boolean retainAll(Collection coll); //取交集
Object[] toArray(); //将集合转成数组
演示:
package day16;
import java.util.ArrayList;
import java.util.Collection;
public class CollectionDemo {
public static void main(String[] args) {
Collection coll=new ArrayList();
show(coll);
Collection c1=new ArrayList();
Collection c2=new ArrayList();
show_2(c1,c2);
}
public static void show_2(Collection c1,Collection c2){
c1.add("abc1");
c1.add("abc2");
c1.add("abc3");
c1.add("abc4");
c2.add("abc2");
c2.add("abc5");
c2.add("abc6");
System.out.println("c1:"+c1);
System.out.println("c2:"+c2);
//演示addAll
c1.addAll(c2); //将c2的元素添加到c1中
System.out.println("c1:"+c1);
//演示removeAll
boolean b=c1.removeAll(c2); //将两个集合中的相同元素从调用removeAll的集合中删除
System.out.println("removeAll:"+b); //若b为true表明删除成功
System.out.println("c1:"+c1);
//演示containsAll
boolean c=c1.contains(c2);
System.out.println("containsAll:"+c);
//演示retainAll(取交集) 这个和removeAll正好相反,这个是删不同的,留相同的,而removeAll是删相同的,留不同的。
c1.add("abc2"); //上面removeAll把"abc2"删了,补回来。
boolean a=c1.retainAll(c2);
System.out.println("c1:"+c1);
}
public static void show(Collection coll){
//1.添加元素 add
coll.add("nba");
coll.add("cba");
coll.add("NCAA");
System.out.println(coll);
//2.删除元素 remove
coll.remove("cba"); //会改变集合的长度
System.out.println(coll);
coll.clear();
System.out.println(coll);
//3.判断
coll.add("nba");
coll.add("cba");
coll.add("NCAA");
System.out.println(coll.contains("CBA"));
}
}
Iterator演示:
package day16;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
public class IteratorDemo {
/**
* @param args
*/
public static void main(String[] args) {
Collection coll=new ArrayList();
coll.add("abc1");
coll.add("abc2");
coll.add("abc3");
coll.add("abc4");
System.out.println(coll);
//使用了collection中的iterator()方法。调用集合中的迭代器方法,是为了获取集合中的迭代器对象。
// Iterator it=coll.iterator();
// while(it.hasNext()){ //判断是否还有元素可取,若有则true。
// System.out.println(it.next());
// } //注意这个和上面的直接输出的区别,那个是一个整体的字符串,分不开,这个取出来是一个一个的。
//下面这种方法比较好,用完后迭代器it就不在内存里了,while用完后,迭代器it还在内存里
for(Iterator it=coll.iterator();it.hasNext();){
System.out.println(it.next());
}
}
}
集合框架的构成及分类:
collection接口常用的两个子接口:List和Set
List:有序(存入和取出的顺序一致),元素都有索引(角标),元素可以重复。
Set:元素不能重复,无序。
(其实后面可以发现,LinkedHashSet是Set的子类,但却有序,
所以,决定到底选List还是Set最关键的是是否重复)
List特有的常见方法(collection的那些方法都有):(他们有一个共性,可以操作角标)
1.添加
void add(int index,element)
void add(int index,collection)
2.删除
Object remove(int index)
3.修改
Object set(int index,element)
4.获取
Object get(int index) //这个是List特有的取出元素的方法,当然也可以用iterator。
int indexOf(Object)
int lastIndexOf(Object)
List subList(int start,int end)
package day16;
import java.util.ArrayList;
import java.util.List;
import java.util.ListIterator;
public class ListDemo {
/**
* @param args
*/
public static void main(String[] args) {
List list=new ArrayList();
list.add("abc1");
list.add("abc2");
list.add("abc3");
System.out.println(list);
/**
Iterator it=list.iterator();
while(it.hasNext()){
Object obj=it.next(); //java.util.ConcurrentModificationException
//在迭代过程中,不要使用集合操作元素,容易出现异常,就像上面那样。
//可以使用Iterator接口的子接口ListIterator来完成在迭代中对元素进行更多的操作。
//修改后的代码在下面
if(obj.equals("abc2"))
list.add("abc9");
else
System.out.println("next:"+obj);
}
System.out.println("next:"+it.next());
*/
ListIterator it=list.listIterator();
//获取列表迭代器对象,它可以实现在迭代过程中完成对元素的增删改查。
//注意:只有list集合具备该迭代功能。
while(it.hasNext()){
Object obj=it.next();
if(obj.equals("abc2"))
it.add("abc9"); 、
//这里是关键,和上面比对一下。
else if(obj.equals("abc3"))
it.set("abc666");
//list特有的修改方法,collection没有的。
}
while(it.hasPrevious()){
System.out.println(it.previous());
//这是list的另一个方法,和hasnext相反,逆向遍历列表。
}
System.out.println(list);
}
}
List常用的实现类:
1.Vector:内部是数组数据结构,是同步的(换句话说,线程是安全的)。增删,查询都很慢!所以几乎不用了。
2.ArrayList:内部是数组数据结构,不同步(不安全,但效率高)。查找的速度非常快。
ArrayList替代了Vector,若在多线程使用,可自行加锁,Vector几乎不用了。
3.LinkedList:内部是链表数据结构,不同步。增删元素的速度非常快。
Vector的一个特有方法:Enumeration
Enumeration(枚举) 等效于Iterator,但这个名字太长,所以后来不用了
LingkedList特有方法:
addFirst() addLast();
offerLast() offerLast(); 这个是jdk1.6之后添加的,功能和addFirst一致。
getFirst() getLast(); 获取元素且不删除。若链表为空,会抛出异常。
peekFirst() peekLast(); 这个是jdk1.6之后添加的,功能和getFirst一致,但链表为空时,返回NULL,不抛出异常。
remove First() remove Last(); 获取元素但会删除,会改变长度。
pollFirst() pollLast(); 这个是jdk1.6之后添加的,功能和removeFirst一致,但链表为空时,返回NULL,不抛出异常。
用remove其实也可以实现迭代器的功能。但是如何控制循环结束呢?
while(!link.isEmpty()){
System.out.peintln(link.removeFirst());
}
这样就可以实现迭代器的功能了。
面试题:
* 请使用LinkedList来模拟一个堆栈或者队列数据结构。
*
* 堆栈:先进后出(First In Last Out) FILO
*
* 队列:先进先出(First in First out) FIFO
*
* 分析:我们应该描述这样一个容器,给使用者提供一个容器对象完成这两种结构中的一种。
* 下面写出了队列的代码,堆栈只需把myAdd方法中的addfirst改成addLast。
package day16;
import java.util.LinkedList;
public class DuiLie {
private LinkedList link;
public DuiLie(){
link=new LinkedList();
}
/*
* 队列的添加元素功能
* */
public void myAdd(Object obj){
link.addLast(obj);
}
public Object myGet(){
return link.removeFirst();
}
public boolean isNull(){
return link.isEmpty();
}
}
package day16;
import java.util.LinkedList;
public class LinkedTest {
/**
* @param args
*/
public static void main(String[] args) {
DuiLie d=new DuiLie();
d.myAdd("abc1");
d.myAdd("abc2");
d.myAdd("abc3");
d.myAdd("abc4");
while(!d.isNull()){
System.out.println(d.myGet());
}
}
}
ArrayList:
ArrayList的特有方法一般几乎用不到。
ArrayList存自定义对象需要注意:取出时必须要做强制类型转换才可以用。
package Class;
public class Person {
private String name;
private int age;
public Person() {
super();
}
public Person(String name, int age) {
super();
this.name = name;
this.age = age;
}
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;
}
}
package day16;
import java.util.ArrayList;
import java.util.Iterator;
import Class.Person;
public class ArrayListTest {
public static void main(String[] args) {
ArrayList al=new ArrayList();
al.add(new Person("lisi1",21));
al.add(new Person("lisi2",22));
al.add(new Person("lisi3",23));
al.add(new Person("lisi4",24));
Iterator it=al.iterator();
while(it.hasNext()){
// System.out.println(it.next()); 如果这样打,输出就是类名加哈希值,而不是姓名加年龄。
// 因为add接受的对象都会提升为Object,迭代器取得也是Object,就无法调用子类方法,要转成子类类型才可以。
System.out.println(((Person) it.next()).getName());
// System.out.println(((Person) it.next()).getName()+"::"+((Person) it.next()).getAge());
// 若用同样的方法在姓名后加上年龄,输出就会异常,同一个while中不能写两个next。解决办法如下。
Person p=(Person) it.next();
System.out.println(p.getName()+"--"+p.getAge());
}
}
}
集合框架的构成及分类:
collection接口常用的两个子接口:List和Set
List:有序(存入和取出的顺序一致),元素都有索引(角标),元素可以重复。
Set:元素不能重复,无序。
(其实后面可以发现,LinkedHashSet是Set的子类,但却有序,
所以,决定到底选List还是Set最关键的是是否重复)
前面学习完了List,接下来学习Set
Set:set接口中的方法和collection一致。
比较常用的两个子类对象:HashSet和TreeSet
HashSet:内部数据结构是哈希表,是不同步的。
哈希表确定元素是否相同:
1.首先判断的是两个元素的哈希值是否相同。若相同,再判断两个对象的内容是否相同。
2.判断哈希值相同,其实判断的是对象的hashCode的方法,判断内容相同,用的是equals方法。
注意:如果哈希值不同,就不需要判断equals了。
哈希冲突:哈希值相同,但内容不同,不常见。若出现了,会在该位置上新加一个位置。
练习题:
** 往HashSet集合中存储Person对象。如果姓名和年龄相同,视为同一个人。视为相同元素。
package Class;
public class Person {
private String name;
private int age;
public Person() {
super();
}
public Person(String name, int age) {
super();
this.name = name;
this.age = age;
}
@Override
public int hashCode() {
return name.hashCode()+age*27;
//其实在HashSetTest这道题中,这里可以任意返回一个数字都行。
} //若返回100,所有对象都存储在位置100,但内容不同,就在位置100上顺延。返回年龄更方便,避免了过多的哈希冲突。
//为了减少哈西冲突,这里可以直接返回姓名的哈希值加年龄,姓名是字符串,本身就有哈希值,这样哈希冲突就更少了。
@Override //最后age*27也是为了减少哈希冲突,避免了40+30=50+20这样的问题。
public boolean equals(Object obj) {
if(this==obj)
return true; //如果你把同一个对象存两遍,就不用判断姓名和年龄是否相同,直接返回true
if(obj instanceof Person)
return false; //健壮性判断,如果你传个猪,不是人,也不用判断姓名和年龄是否相同,直接返回false。
Person p=(Person) obj;
return this.name.equals(p.name)&&this.age==p.age; //注意年龄是数字,就不用equals,直接用等于号就行。
}
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;
}
}
package day16;
import java.util.HashSet;
import java.util.Iterator;
import Class.Person;
public class HashSetTest {
/**
* HasSet集合数据结构是哈希表,所以存储元素的时候
* 使用元素的hasCode方法来确定位置,若位置相同,再通过元素的equals来确定是否相同。
* Person类继承Object,不同对象地址不同,Object的equals判断的是地址,所以每个对象判断后都是不同的。
* 题目要求同姓名同年龄即为同一人,所以就要在Person类中重写哈希值方法和equals方法了。
*/
public static void main(String[] args) {
HashSet hs=new HashSet();
hs.add(new Person("lisi4",24));
hs.add(new Person("lisi7",27));
hs.add(new Person("lisi1",21));
hs.add(new Person("lisi9",29));
hs.add(new Person("lisi7",27)); //没覆盖hasCode和equals之前,这个人可以存进去,
//这明显不符合hasSet不重复的特点,覆盖之后就解决了。
Iterator it=hs.iterator();
while(it.hasNext()){
Person p=(Person) it.next();
System.out.println(p.getName()+"--"+p.getAge());
}
}
}
ArrayListTest2
/*
* 定义功能去除ArrayList中的重复元素。
*/
package Class;
public class Person {
private String name;
private int age;
public Person() {
super();
}
public Person(String name, int age) {
super();
this.name = name;
this.age = age;
}
@Override
public int hashCode() {
return name.hashCode()+age*27;
//其实在HashSetTest这道题中,这里可以任意返回一个数字都行。
} //若返回100,所有对象都存储在位置100,但内容不同,就在位置100上顺延。返回年龄更方便,避免了过多的哈希冲突。
//为了减少哈西冲突,这里可以直接返回姓名的哈希值加年龄,姓名是字符串,本身就有哈希值,这样哈希冲突就更少了。
@Override //最后age*27也是为了减少哈希冲突,避免了40+30=50+20这样的问题。
public boolean equals(Object obj) {
if(this==obj)
return true; //如果你把同一个对象存两遍,就不用判断姓名和年龄是否相同,直接返回true
if(obj instanceof Person)
return false; //健壮性判断,如果你传个猪,不是人,也不用判断姓名和年龄是否相同,直接返回false。
Person p=(Person) obj;
return this.name.equals(p.name)&&this.age==p.age; //注意年龄是数字,就不用equals,直接用等于号就行。
}
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;
}
}
package day16;
import java.util.ArrayList;
import java.util.Iterator;
public class ArrayListTest2 {
/**
* @param args
*/
public static void main(String[] args) {
ArrayList al=new ArrayList();
al.add("abc1");
al.add("abc2");
al.add("abc2");
al.add("abc1");
al.add("abc");
System.out.println(al);
al=getSingleElement(al);
System.out.println(al);
}
public static ArrayList getSingleElement(ArrayList al) {
//1.定义一个临时容器
ArrayList temp=new ArrayList();
//2.迭代al集合
Iterator it=al.iterator();
while(it.hasNext()){
Object obj=it.next();
//3.判断迭代到的元素是否在临时容器中存在。
if(!temp.contains(obj)){ //注意contains实现原理也是通过equals实现的,所以如果此题存的不是abc,而是Person对象,
//就不能完了重写equals方法,否则无法实现。remove用的也是equals,使用时,也别忘了重写。
temp.add(obj);
}
}
return temp;
}
}
HashSet的子类:LinkedHashSet,它是有序的。
LinkedHashSet:具有可预知迭代顺序的Set接口的哈希表和链表实现。
到这里就学习完了Set的一个常用对象HashSet,接下来学习另一个TreeSet。
TreeSet:可以对Set集合中的元素进行排序。是不同步的。 它是Set的子类,所以元素也必须保证唯一性。
判断元素唯一性的方式:根据比较方法的返回结果是否是0,是0就相同,不存。
TreeSet的底层结构:二叉树结构
TreeSet对元素进行排序的方式一:自然排序(这个是默认排序方式)
让元素自身具备比较功能,也就是实现Comparable接口,覆盖compareTo方法。
如果要求不按照对象中具备的自然顺序进行排序,或者对象不具备自然顺序,怎么办???
TreeSet对元素进行排序的方式二:比较器排序(这个比较常用)
让集合自身具备比较功能,录入元素时就要排序以确定位置,所以比较功能必须定义在集合的构造函数中。
定义一个类实现Comparable接口,覆盖compareTo方法。将该类对象作为参数传递给TreeSet集合的构造函数。
两种排序方式演示:
package Class;
public class Person implements Comparable{ //这里的实现是TreeSet中药用到的
private String name;
private int age;
public Person() {
super();
}
public Person(String name, int age) {
super();
this.name = name;
this.age = age;
}
@Override
public int hashCode() {
return name.hashCode()+age*27;
//其实在HashSetTest这道题中,这里可以任意返回一个数字都行。
} //若返回100,所有对象都存储在位置100,但内容不同,就在位置100上顺延。返回年龄更方便,避免了过多的哈希冲突。
//为了减少哈西冲突,这里可以直接返回姓名的哈希值加年龄,姓名是字符串,本身就有哈希值,这样哈希冲突就更少了。
@Override //最后age*27也是为了减少哈希冲突,避免了40+30=50+20这样的问题。
public boolean equals(Object obj) {
if(this==obj)
return true; //如果你把同一个对象存两遍,就不用判断姓名和年龄是否相同,直接返回true
if(obj instanceof Person)
return false; //健壮性判断,如果你传个猪,不是人,也不用判断姓名和年龄是否相同,直接返回false。
Person p=(Person) obj;
return this.name.equals(p.name)&&this.age==p.age; //注意年龄是数字,就不用equals,直接用等于号就行。
}
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;
}
@Override
public int compareTo(Object o) { //覆盖compareTo方法,按照年龄排序。
Person p=(Person) o; //若年龄相同,则按照姓名排序。
/* if(this.age>p.age) //比较方法的思想就是这样的,但一般不这么写
return 1;
if(this.age<p.age)
return -1;
else
return(this.name.compareTo(p.name));
*/
int temp=this.age-p.age;
return temp==0?this.name.compareTo(p.name):temp;
}
}
package Comparator;
import java.util.Comparator;
import Class.Person;
/**
* 创建一个根据penson类的name进行比较的比较器。
* */
public class ComparatorByName implements Comparator {
@Override
public int compare(Object o1, Object o2) {
Person p1=(Person)o1;
Person p2=(Person)o2;
int temp=p1.getName().compareTo(p2.getName());
//注意这里不能写name,age因为是私有的
return temp==0?p1.getAge()-p2.getAge():temp;
//return 1; //如果比较方法里直接返回1,则按照输入的顺序输出,这涉及到了TreeSet的二叉树结构。
}
}
package day16;
import java.util.Iterator;
import java.util.TreeSet;
import Class.Person;
import Comparator.ComparatorByName;
public class TreeSetDemo {
/**
* @param args
*/
public static void main(String[] args) {
demo1();
demo2();
}
private static void demo2() {
// TreeSet ts=new TreeSet(); //这个是自然排序,下面的是用比较器排序。两个都存在以比较器为主。
TreeSet ts=new TreeSet(new ComparatorByName());
/*
* 以person对象的年龄进行从小到大的排序。
* */
ts.add(new Person("zhangsan",28)); //由于TreeSet会对集合中的元素进行排序,就要比较
ts.add(new Person("wangwu",29)); //但是没有具体的比较方法,所以就会报错。
ts.add(new Person("lisi",21)); //解决办法就是在Person类中覆盖compareTo方法
ts.add(new Person("zhouqi",29)); //demo1中传的是字符串,字符串本身就实现了compareTo方法。
ts.add(new Person("zhaoliu",25));
Iterator it=ts.iterator();
while(it.hasNext()){
Person p=(Person) it.next();
System.out.println(p.getName()+"--"+p.getAge());
}
}
public static void demo1() {
TreeSet ts=new TreeSet();
ts.add("abc");
ts.add("aa");
ts.add("nba");
ts.add("cba");
Iterator it=ts.iterator();
while(it.hasNext()){
System.out.println(it.next());
}
}
}