java容器/集合框架

来源:互联网 发布:bi开发工程师转行java 编辑:程序博客网 时间:2024/04/30 15:51

1、容器的概念

容器:Java API 所提供的一系列类的实例,用于在程序中存放对象。

 2、容器API

JDK所提供的容器API位于java.util(java工具包)包内。
容器API的类图结构如下图:
这里写图片描述

上图左边的Set和List容器是一个对象一个对象的往里装,而右边的Map容器是一对一对的往里装。
Collection接口定义了存取一组对象的方法,其子接口Set和List分别定义了存储方式。
Set集合:没有顺序并且不可以重复。
List列表:有顺序并且可以重复。
如果两个对象互相equals就是重复。

Map接口定义了存储“键(Key)--值(value)映射对”的方法。

Collection接口:
接口中所定义的方法:
int size();装了多少个元素
boolean isEmpty();是否为空
void clear();清空
boolean contains(Object element);是否包含某一个对象
.......

3、ArrayList常用方法:

<1> add 增加
add 有两种用法
第一种是直接add对象,把对象加在最后面
heros.add(new Hero("hero " + i));

第二种是在指定位置加对象
heros.add(3, specialHero);

 步骤 2 : 

判断是否存在

 
通过方法contains 判断一个对象是否在容器中
判断标准: 是否是同一个对象,而不是name是否相同
 步骤 3 : 

获取指定位置的对象

 
通过get获取指定位置的对象,如果输入的下标越界,一样会报错
 步骤 4 : 

获取对象所处的位置

indexOf用于判断一个对象在ArrayList中所处的位置
与contains一样,判断标准是对象是否相同,而非对象的name值是否相等

 步骤 5 : 

删除


remove用于把对象从ArrayList中删除
remove可以根据下标删除ArrayList的元素
heros.remove(2);

也可以根据对象删除
heros.remove(specialHero);


 步骤 6 : 

替换


set用于替换指定位置的元素

 步骤 7 : 

获取大小

 
size 用于获取ArrayList的大小

 步骤 8 : 

转换为数组

toArray可以把一个ArrayList对象转换为数组。
需要注意的是,如果要转换为一个Hero数组,那么需要传递一个Hero数组类型的对象给toArray(),这样toArray方法才知道,你希望转换为哪种类型的数组,否则只能转换为Object数组
 步骤 9 : 

把另一个容器所有对象都加进来


addAll 把另一个容器所有对象都加进来

clear 清空一个ArrayList


4、Iterator:迭代器遍历
Iterator对象的remove方法是在迭代过程中删除元素的唯一的安全方法。
使用迭代器Iterator遍历集合中的元素
迭代器遍历
5、增强的for循环
使用增强型for循环可以非常方便的遍历ArrayList中的元素,这是很多开发人员的首选。

不过增强型for循环也有不足:
无法用来进行ArrayList的初始化
无法得知当前是第几个元素了,当需要只打印单数元素的时候,就做不到了。 必须再自定下标变量
packagecollection;
 
importjava.util.ArrayList;
importjava.util.Iterator;
importjava.util.List;
 
importcharactor.Hero;
 
publicclassTestCollection {
 
    publicstaticvoidmain(String[] args) {
        List<Hero> heros = newArrayList<Hero>();
 
        // 放5个Hero进入容器
        for(inti = 0; i < 5; i++) {
            heros.add(newHero("hero name " + i));
        }
 
        // 第三种,增强型for循环
        System.out.println("--------增强型for循环-------");
        for(Hero h : heros) {
            System.out.println(h);
        }
 
    }
 
}

缺陷:
 *数组:不能翻边的访问下标只
 *集合:与使用Iterator相比,不能方便的删除集合中的内容,在内部也是调用Iterator.
总结:除了简单遍历并读出其中的内容外,不建议使用增强for循环。

6、Comparable接口
所有可以“排序”的类都实现了Comparable接口,Comparable接口中只有一个方法
public int compareTo(object obj);该方法:
返回 0 表示this == obj
返回正数表示this >obj
返回附属表示this < obj
实现了Comparable接口的类通过实现 compareTo方法从而确定该类对象的排序方式

7、如何选择数据结构呢?
衡量标准:按照读的效率和改的效率
Array读快改慢
Linked改快读慢
Hash两者之间

8、Map接口
实现Map接口的类用来储存键--值对。
Map接口的实现类有HashMap和TreeMap等。
Map类中储存的键--值对通过键来标识,所以键值不能重复。

9、Auto-boxing/unboxing
再合适的时候自动打包,解包。
(自动将基础类型转换为对象,
自动将对象转换为基础类型)
Boxing Unboxing(装箱 拆箱 有人译作打包解包)还有Generic(泛型)

这里的装箱应该理解为 封装对象 ,即把基础数据类型(如 int)转换成基础类型封装类的对象(如 new Integer())
拆箱就是装箱的反过程,即把基础类型封装类的对象(如 new Integer())转换为基础数据类型(如 int)。


Java代码  收藏代码
  1. 装箱: Integer a = new Integer() ;                a = 100 ;  
  2. 拆箱: int b = new Integer(100) ;  
10、泛型

泛型的使用

Java泛型编程是JDK1.5版本后引入的。泛型让编程人员能够使用类型抽象,通常用于集合里面。

通过List<String>,直接限定了list集合中只能含有String类型的元素,从而在上例中的第6行中,无须进行强制类型转换,因为集合能够记住其中元素的类型信息,泛型只在编译阶段有效编译器已经能够确认它是String类型了。

泛型只在编译阶段有效

看下面的代码:

 

  1. AyyayList<String> a = new ArrayList<String>();  
  2. ArrayList b = new ArrayList();  
  3. Class c1 = a.getClass();  
  4. Class c2 = b.getClass();  
  5. System.out.println(a == b); //true  

 

上面程序的输出结果为true。所有反射的操作都是在运行时的,既然为true,就证明了编译之后,程序会采取去泛型化的措施,也就是说Java中的泛型,只在编译阶段有效。在编译过程中,正确检验泛型结果后,会将泛型的相关信息擦出,并且在对象进入和离开方法的边界处添加类型检查和类型转换的方法。也就是说,成功编译过后的class文件中是不包含任何泛型信息的。泛型信息不会进入到运行时阶段。

 

上述结论可通过下面反射的例子来印证:

 

  1. ArrayList<String> a = new ArrayList<String>();  
  2. a.add("CSDN_SEU_Cavin");  
  3. Class c = a.getClass();  
  4. try{  
  5.     Method method = c.getMethod("add",Object.class);  
  6.     method.invoke(a,100);  
  7.     System.out.println(a);  
  8. }catch(Exception e){  
  9.     e.printStackTrace();  
  10. }  

 

因为绕过了编译阶段也就绕过了泛型,输出结果为:

  1. [CSDN_SEU_Cavin, 100]  

 

 

泛型类和泛型方法

如下,我们看一个泛型类和方法的使用例子,和未使用泛型的使用方法进行了对比,两者输出结果相同,在这里贴出来方便读者体会两者的差异。泛型接口的例子有兴趣可以去找一些资料,这里就不赘述了。

(1)使用泛型的情况

  1. public static class FX<T> {  
  2.     private T ob; // 定义泛型成员变量  
  3.   
  4.     public FX(T ob) {  
  5.         this.ob = ob;  
  6.     }  
  7.   
  8.     public T getOb() {  
  9.         return ob;  
  10.     }  
  11.   
  12.     public void showTyep() {  
  13.         System.out.println("T的实际类型是: " + ob.getClass().getName());  
  14.     }  
  15. }  
  16.     public static void main(String[] args) {  
  17.         FX<Integer> intOb = new FX<Integer>(100);  
  18.         intOb.showTyep();  
  19.         System.out.println("value= " + intOb.getOb());  
  20.         System.out.println("----------------------------------");  
  21.   
  22.         FX<String> strOb = new FX<String>("CSDN_SEU_Calvin");  
  23.         strOb.showTyep();  
  24.         System.out.println("value= " + strOb.getOb());  
  25. }  

(2)不使用泛型的情况

 

  1. public static class FX {  
  2.     private Object ob; // 定义泛型成员变量  
  3.   
  4.     public FX(Object ob) {  
  5.         this.ob = ob;  
  6.     }  
  7.   
  8.     public Object getOb() {  
  9.         return ob;  
  10.     }  
  11.   
  12.     public void showTyep() {  
  13.         System.out.println("T的实际类型是: " + ob.getClass().getName());  
  14.     }  
  15. }  
  16.   
  17.     public static void main(String[] args) {  
  18.         FX intOb = new FX(new Integer(100));  
  19.         intOb.showTyep();  
  20.         System.out.println("value= " + intOb.getOb());  
  21.         System.out.println("----------------------------------");  
  22.   
  23.         FX strOb = new FX("CSDN_SEU_Calvin");  
  24.         strOb.showTyep();  
  25.         System.out.println("value= " + strOb.getOb());  
  26.     }  

 

输出结果为:

  1. T的实际类型是: java.lang.Integer  
  2. value= 100  
  3. ----------------------------------  
  4. T的实际类型是: java.lang.String  
  5. value= CSDN_SEU_Calvin  

 

通配符

为了引出通配符的概念,先看如下代码:

 

  1. List<Integer> ex_int= new ArrayList<Integer>();    
  2. List<Number> ex_num = ex_int; //非法的  

 

上述第2行会出现编译错误,因为Integer虽然是Number的子类,但List<Integer>不是List<Number>的子类型。

假定第2行代码没有问题,那么我们可以使用语句ex_num.add(newDouble())在一个List中装入了各种不同类型的子类,这显然是不可以的,因为我们在取出List中的对象时,就分不清楚到底该转型为Integer还是Double了。

 因此,我们需要一个在逻辑上可以用来同时表示为List<Integer>和List<Number>的父类的一个引用类型,类型通配符应运而生。在本例中表示为List<?>即可。下面这个例子也说明了这一点,注释已经写的很清楚了。

 

  1. public static void main(String[] args) {  
  2.     FX<Number> ex_num = new FX<Number>(100);  
  3.     FX<Integer> ex_int = new FX<Integer>(200);  
  4.     getData(ex_num);  
  5.     getData(ex_int);//编译错误  
  6. }  
  7.   
  8. public static void getData(FX<Number> temp) { //此行若把Number换为“?”编译通过  
  9.     //do something...  
  10. }  
  11.       
  12. public static class FX<T> {  
  13.     private T ob;   
  14.     public FX(T ob) {  
  15.         this.ob = ob;  
  16.     }  
  17. }  

 

 

 

上下边界

看了下面这个上边界的例子就明白了,下界FX<? supers Number>的形式就不做过多赘述了。

  1. public static void main(String[] args) {  
  2.     FX<Number> ex_num = new FX<Number>(100);  
  3.     FX<Integer> ex_int = new FX<Integer>(200);  
  4.     getUpperNumberData(ex_num);  
  5.     getUpperNumberData(ex_int);  
  6. }  
  7.   
  8. public static void getUpperNumberData(FX<? extends Number> temp){  
  9.       System.out.println("class type :" + temp.getClass());  
  10. }  
  11.       
  12. public static class FX<T> {  
  13.     private T ob;   
  14.     public FX(T ob) {  
  15.     this.ob = ob;  
  16.     }  
  17. }  

 

 

泛型的好处

(1)类型安全。 

通过知道使用泛型定义的变量的类型限制,编译器可以更有效地提高Java程序的类型安全。 

(2)消除强制类型转换。 

消除源代码中的许多强制类型转换。这使得代码更加可读,并且减少了出错机会。所有的强制转换都是自动和隐式的。

(3)提高性能。 

  1. Lits list1 = new ArrayList();  
  2. list1.add("CSDN_SEU_Cavin ");  
  3. String str1 = (String)list1.get(0);  
  1. List<String> list2 = new ArrayList<String>();  
  2. list2.add("CSDN_SEU_Cavin ");  
  3. String str2 = list2.get(0);  

对于上面的两段程序,由于泛型所有工作都在编译器中完成,javac编译出来的字节码是一样的(只是更能确保类型安全),那么何谈性能提升呢?是因为在泛型的实现中,编译器将强制类型转换插入生成的字节码中,但是更多类型信息可用于编译器这一事实,为未来版本的 JVM 的优化带来了可能。

 

泛型使用的注意事项

 

(1)泛型的类型参数只能是类类型(包括自定义类),不能是简单类型。

(2)泛型的类型参数可以有多个。

(3)不能对确切的泛型类型使用instanceof操作。如下面的操作是非法的,编译时会出错。

 

  1. if(ex_num instanceof FX<Number>){   
  2. }  

 

(4)不能创建一个确切的泛型类型的数组。下面使用Sun的一篇文档的一个例子来说明这个问题:

 

  1. List<String>[] lsa = new List<String>[10]; // Not really allowed.    
  2. Object o = lsa;    
  3. Object[] oa = (Object[]) o;    
  4. List<Integer> li = new ArrayList<Integer>();    
  5. li.add(new Integer(3));    
  6. oa[1] = li; // Unsound, but passes run time store check    
  7. String s = lsa[1].get(0); // Run-time error: ClassCastException.    

这种情况下,由于JVM泛型的擦除机制,在运行时JVM是不知道泛型信息的,所以可以给oa[1]赋上一个ArrayList<Integer>而不会出现异常,但是在取出数据的时候却要做一次类型转换,所以就会出现ClassCastException,如果可以进行泛型数组的声明,上面说的这种情况在编译期将不会出现任何的警告和错误,只有在运行时才会出错。

下面采用通配符的方式是被允许的:

 

    1. List<?>[] lsa = new List<?>[10]; // OK, array of unbounded wildcard type.    
    2. Object o = lsa;    
    3. Object[] oa = (Object[]) o;    
    4. List<Integer> li = new ArrayList<Integer>();    
    5. li.add(new Integer(3));    
    6. oa[1] = li; // Correct.    
    7. Integer i = (Integer) lsa[1].get(0); // OK


原创粉丝点击