集合类

为什么会出现集合类?、

     面向对象语言对事物的体现都是以对象的形式,所以为了方便对多个对象的操作,

     就对对象进行存储,集合就是存储对象最常用一种方式。

数组和集合类同时容器,有何不同?

     数组虽然可以存储对象,但长度是固定的。集合长度是可变的。

     数组中可以存储基本类型数据,集合只能存储对象。

集合特点:

    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

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());

}

}

}