黑马程序员__集合知识点

来源:互联网 发布:中文域名有价值吗 编辑:程序博客网 时间:2024/06/05 07:20

---------- android培训java培训、期待与您交流! ----------

 

 

集合是能够存储对象并且可变长度的容器。集合类由于内部数据结构(存储对象的方式)不同,大体可以分为两类:单列集合与双列集合。

因为每个集合中的数据结构不同,所以集合有很多种,但都具有共性功能,就不断向上抽取,形成了集合框架,顶层为Iterator接口。

单列集合顶层接口是Collection,它又有两个常用的子接口List和Set接口。双列集合的顶层接口是Map,它有两个常用的具体实现类HashMap和TreeMap。

集合中都重写了Object的toSting()方法,所以可以通过打印集合名直接显式集合中的内容。

 

Collection接口是单列集合的根接口,通常不能直接使用,但该接口还是提供了添加元素、删除元素、管理数据等抽象方法。由于List与Set接口都继承了Collection接口,因此,这些方法在List与Set接口中都是通用的。方法如下:

添加元素 

add(Object)  添加一个元素
addAll(Collection)  添加一个Collection集合,将这个集合中的所有元素都添加进来


删除元素 

clear() 删除该集合中的所有元素
remove(Object)  删除该集合中指定的元素

 removeAll(Collection) 删除指定集合中的所有元素

 

判断元素

 isEmpty()   返回boolean,判断该集合是否为空
 contains(Object) 返回boolean,判断该集合中是否包含某个元素
 containsAll(Collection)  返回boolean,判断该集合中是否包含直接集合中的所有元素

取出元素 

iterator() 返回在该集合的元素上进行迭代的迭代器(Iterator),用于遍历该集合所有元素
      

其他功能 

size() 返回int值,获取该集合元素个数
toArray()  返回包含此 collection 中所有元素的数组

 

List接口是一个有序的 Collection。List的特有方法:都是围绕的索引展开的。List集合就像数组一样建立了索引与对象的关联。List集合允许存放重复元素,并且元素都是有序的(这里的有序,指存入和取出的顺序一样。ArrayList是连续的空间,而linkedlist是不连续的空间)。List接口在Collection的基础上增加了大量的方法,使得可以在List集合中插入和移除元素。

ArrayList内部维护了一个可扩容的数组,使得它在随机查询元素的速度很快,而在指定位置插入和删除元素的速度稍慢。ArrayList可以通过调用get()方传入想要获得的元素索引,就可以快速的取出这个元素。

LinkedList内部是一个双向链表,每个元素都两个指针previous(上一个)和next(下一个),分别指向下一个和上一个元素。当需要在指定位置插入或删除元素时,只需要在指定位置放置元素,然后其指针分别指向下一个和上一个元素,其他的元素不需要变动位置。LinkedList在需要频繁增删元素时,应用的非常多。在实际程序开发中,也会经常使用LinkedList对数据库进行增删操作。

 

 

Iterator(迭代器)是一个专门用于取出Collection集合中元素的接口,它以内部类的方式封装在Collection接口的实现类中,根据Collection接口各个实现类的数据结构不同,有着不同实现方式。而在Collection接口中定义取出Iterator对象的抽象方法。迭代器的出现统一了取出Collection集合元素的过程。Iterator接口中有hashNext()、next()和remove()方法。

public static void main(String[] args) {

       Collection coll = new ArrayList();     //创建集合对象

       coll.add("demo_1");             //添加字符串对象

       coll.add("demo_2");

       Iterator it = coll.iterator();         //获取迭代器对象

       while(it.hasNext()) {              //判断是否存在下一个元素

           Object obj = it.next();         //取出对象的时候,java虚拟机并不知道Collection集合中存储的对象的具体类型,所以以Object类型取出

           System.out.println(obj);       

       }  

    } 

 迭代器的弊端:用迭代器在迭代过程中对集合的元素进行操作,是不被允许的。因为在迭代过程中,向集合中插入元素,迭代器对象并不知情,就导致了ConcurrentModificationException异常的出现。所以就出现了ListIterator(列表迭代器),在使用这个迭代器迭代元素时,是可以对元素进行增、删、改以及别的操作。ListIterator继承自Iterator接口,它还有有add(),remove(),previous()等方法,不仅能在迭代过程中对集合中的元素进行增删或修改,还能反向迭代。

在List接口中有一个实现类Vector,它在JDK1.2以前是非常常用的集合类,由于Vector的线程是同步的,而导致效率低下,在JDK1.2之后被ArrayList(ArrayList线程不同步)替代。在Vector中取出元素可以用枚举(Enumeration)。

  

Set接口是一个不包含重复元素并且无序(存储和取出的顺序)的Collection。Set接口中并没有额外增加方法(它的所有方法都是从Collection接口继承而来),Set接口也有两个常用实现类:HashSet和TreeSet。

 

将对象存入HashSet集合时,会计算对象的哈希值来找到对象应该放置的位置,如果该位置已有对象存在(它们的哈希值相同),那么会继续使用equals()方法来判断这两个对象是否相同,如果相同,则不会存入集合,如果不同,则会在已有对象的位置上顺延一位存储该对象。

在实际需求中,经常会从数据库中查询数据,当查询出的数据,只要它们某些相同,就认为它们是相同元素。这就需要重写hashCode()和equals()方法了。

 

TreeSet是一个内部为二叉树结构的Set接口实现类。当元素存入TreeSet时,会按照自然顺序进行排序。

如果在TreeSet集合中存入了两个自定义Student对象,就会出现ClassCastException(类型转换异常),Java虚拟机说:Student不能转换成Comparable。是因为系统不知道该怎么对Student进行自然排序。

Java中提供了两种方式解决:让类实现Comparable接口,重写compareTo方法,或者在创建TreeSet对象时指定Comparator比较器。

在TreeSet中,是以compareTo()方法的返回值,来判断重复元素的。这与HashSet以HashCode()和equals()方法来判断重复元素是有所区别的。

通常,将要存入TreeSet集合的对象的类都是定义好的,我们也不能去修改其中的代码,所以自定义比较器这种方式,使得对象具有比较性,更加的方便、实用。通常,在实际开发中也是常用这种方式。

 

Map集合

Map集合是双列集合,它存储着一系列键值的映射关系。在Map集合中可以通过键来获得值,反之则不行,所以必须保证键的唯一性。在存入过程中,键相同,值覆盖。

map虽然没有迭代器。但是map可以转成set,在使用迭代器。这就是map集合取出元素的原理。

取出map集合所有元素的第一种方式:keySet()

map集合取出的第二种方式:entrySet():取出的是键和值的映射关系对象。

map集合取出的第三种方式:只获取所有值:values();

 

Map接口是Map集合的顶层父接口,它有三个常用的实现类HaspMap、TreeMap和Properties。

 

public class TestMap {

       public static void main(String[] args) {

              Map map = new HashMap();                  //建map

              map.put("Jack", "19");                      //存储键

              map.put("Rose", "18");

              Set keySet = map.keySet();                     //的集合

              Iterator it = keySet.iterator();                   //迭代

              while(it.hasNext()) {                        

                     Object key = it.next();              

                     Object value = map.get(key);            //

                     System.out.println(key + ":" + value);

              }

       }

}Map集合获取元素的第一种方式。调用put()方法存储键和值,然后调用keySet()方法,获取键的Set集合,迭代键的集合,然后使用get()方法,根据键获取值。

 

   public class TestMap {

     public static void main(String[] args) {

         Map map = new HashMap();        //创建map对象

         map.put("Jack", "19");             //存储键和值

         map.put("Rose", "18");

           Set entrySet = map.entrySet();     //获取映射关系对象集合

           Iterator it = entrySet.iterator();

           while (it.hasNext()) {

              Map.Entry entry = (Entry) it.next();

              Object key = entry.getKey();

              Object value = entry.getValue();

              System.out.println(key + ":" + value);

           }

       } 

 }

Map集合获取元素的第二种方式。在Java中,将键和值的映射关系以内部类(Map.Entry)的形式封装到了Map集合中。调用entrySet()方法获取映射关系对象组成的Set集合,然后迭代此集合,获得每一个关系对象,调用getKey()getValue()方法获取键和值。

 

 

 

Map接口有三个常用的实现类HaspMapTreeMapProperties

HashMap是哈希表结构,线程不安全,存取速度快,允许存放nullnull值。通过HashSet中的算法保证键的唯一性。

Hashtable现在已被HashMap取代,线程安全,存取速度都很慢,且不允许存放nullnull值。但是Hashtable却有一个重要的子类PropertiesProperties用于持久化存储数据。通常,Properties集合中存储键和值都是字符串。这是一个非常重要的集合类。

 TreeMap是二叉树结构,通过TreeSet中的算法保证键的唯一性。并且对键进行排序,因此不允许null键。

 

通过示例了解TreeMap的具体用法。

 

class Student {                                                //定义Student

       String id;                                           //定义id属性

       String name;                                     //定义name属性

       public Student(String id, String name) {

              this.id = id;

              this.name = name;

       }

}

public class TestTreeMap {                      //定义TreeMap测试类

       public static void main(String[] args) {

              TreeMap tm = new TreeMap(); //创建TreeMap对象

              Student stu1 = new Student("003", "Jack");     //创建Student对象

              Student stu2 = new Student("001", "Rose");

              Student stu3 = new Student("002", "Tom");

              tm.put(stu1.id, stu1.name);    //Student对象的属性以键和值存入集合

              tm.put(stu2.id, stu2.name);

              tm.put(stu3.id, stu3.name);

              Set keySet = tm.keySet();          //获取键的Set集合

              Iterator it = keySet.iterator();            //获取键的Set集合的迭代器对象

              while(it.hasNext()) {

                     Object key = it.next();

                     Object value = tm.get(key);

                     System.out.println(key + ":" + value);

              }

       }

}

示例中,将Student对象的id属性作为键,name属性作为值存入TreeSet集合中,然后取出。通过运行结果发现id属性以自然顺序排序了。如果欲对班级学生以学号排序,TreeSet集合将是很好的选择。当然,在TreeMap中也是可以像TreeSet集合那样定义Comparator比较器或者让存入集合对象的类实现Comparable接口进行排序的。

 

 

泛型是在定义类或者接口时指定类型形参,这个形参在声明变量或创建对象时确定。泛型其实是应用在编译时期的技术,用于检测所操作的引用数据的类型,提高了程序的安全性。如果我们需要在类的方法中,接收任意且相同类型的两个参数,利用前边所学的知识,我们只能将参数类型定义为Object。当传入参数时,虽然可以传入任意类型,但是也可以是两个不同类型。显然这样满足不了需求,这时我们便需要用到泛型技术。泛型的定义格式:<参数类型>。通过示例来了解定义泛型的方式。

示例

public class GenericDemo<T>{           //定义带有泛型的类

       public void test(T t1,T t2) {         //定义方法,接收两个相同类型的参数

              System.out.println("我叫"+t1+",今年"+t2+"");

       }

       public static void main(String[] args) {

        //创建该类对象,并指定具体类型

GenericDemo<String> gen = new GenericDemo<String>();                                                   gen.test("王彬", "20");          //调用方法

       }

}

示例演示了泛型的定义和使用方式。成功的满足了能够接收任意且相同类型的两个参数的需求。当创建对象并指定具体类型为String后,就只能在test方法中传入String类型的参数,否则编译出错。

当方法为静态时,泛型必须定义在方法上,因为静态方法是随着类的加载而加载,不需要创建对象即可直接由类调用。在许多工具类中,需要用到的泛型都是定义在方法上的,例如Collections。格式如下:

public static <T> void test(T t1,T t2) {}

       当然泛型不仅可以定义在类和方法上,也可以定义在接口上。当泛型定义在接口上时,这个接口的实现类有两种定义方式,如下所示。

 

         interface Inter<T> {}   //定义接口Inter指定泛型为T

  

         class InterImp_1 implements Inter<String> {} //定义实现类指定具体类型为String

  

         class InterImp_2<T> implements Inter<T>{} //定义实现类指定泛型为T

如示例所示,方式一:在定义接口实现类时就定义好具体的类型。

方式二:在定义接口实现类时并不指定具体类型,在创建对象时再指定具体类型。

 

 

Java中,集合是一个非常强大的容器,它能存储任何对象,这也就导致了集合的一个缺点的出现。当把一个对象存入集合后,再取出,这个对象的编译类型就变成了Object(其运行时类型没有改变)Java中的集合之所以设计成这样,是因为集合并不知道将要存储什么类型的对象,为了具备更好的通用性,所以都以Object类型取出对象。但是,在取出对象后,通常要进行强制类型转换,来使用对象中的特有方法。然而,这种强制类型转换既会增加程序的复杂性,也可能发生ClsaaCastException(类型转换异常)

通过如下示例,来观察这种缺点造成的问题。

示例:

 

public class CheckType {

       public static void main(String[] args) {

              ArrayList list = new ArrayList();          //创建集合对象

              list.add("String");                                //添加字符串对象

              list.add("Collection");                         

              list.add(1);                                         //添加Integer对象

              for (Object obj : list) {                        //遍历集合

                     String str = (String) obj;               //强制转换成String类型

                     System.out.println(str + ":"+str.length());//打印字符串长度

              }

       }

}

                                                

       示例中,欲对单词的字母个数进行统计,但是"不小心"在集合中存入了一个Integer对象(自动装箱机制),然后在强制类型转换时就发生了ClassCastException异常。这个异常时在程序编译通过后,运行时才发生的。这样是非常危险的。但是通过泛型机制就可以很好的避免这类问题的发生。下面通过如下示例来了解集合中使用泛型的具体方式。

示例:

 public class CheckType {

       public static void main(String[] args) {

              //创建集合对象并指定泛型为String

         ArrayList<String> list = new ArrayList<String> ();

         list.add("String");                                       //添加字符串对象

         list.add("Collection");                         

         list.add(1);                                                //添加Integer对象

         for (Object obj : list) {                               //遍历集合

                System.out.println(str + ":"+str.length());//打印字符串长度

         }

       }

  }

           

                                         

           通过运行结果可以看出,在编译时期就发生了错误,编译器检查出Integer对象与String类型不匹配,编译不通过。在遍历集合时,也没有进行强制类型转换,简化了书写。

           简单地说,泛型的出现将运行时可能出现的异常转化成了编译时的错误,也省去了强制类型转换这一步骤。定义了泛型之后,一个容器只能存储一种类型的元素。

           在前边使用集合时,我们都没有定义泛型,编译时虽然出现了警告提示,但也是通过的。这是因为,Java版本的向下兼容,前边介绍过,泛型时JDK1.5之后才出现的新技术,为了让用旧的版本JDK创建的文档或程序仍能被正常操作或使用。但是现在我们使用集合时,应当定义出泛型。

知识拓展:

           在程序编译时期,编译器检测完具体类型后,如果编译通过,会生成class文件,而这个class文件中是没有泛型的。这称为泛型的擦除。在运行时期,获取存储元素的类型并以该类型对所取出的元素进行具体类型的指定。这称为泛型的补偿

 

泛型的通配符和限定

        Java中提供了通配符(?)来标示泛型,提高了程序的扩展性。通过如下示例来了解通配符的使用。

示例

class Student{}                           //定义Student

 

            public class TestGeneric2{              //定义测试类

              public static void method_1(Collection<Student> coll) {}          //定义方法1

              public static void method_2(Collection<?> coll) {}                    //定义方法2

            }

           定义一个Student类,然后在测试类中定义method_1method_2方法,method_1方法接收的参数为一个Collection<Student>类型,该方法只能接收集合中的元素是StudentCollection,而method_2方法却可以接收集合中元素为任意类型的Collection

?就代表任意类型,根据集合中元素的具体类型来确定。

           然而,通配符的范围太广,需要对其限定,也就是说现在集合中接收的是一种类型体系,这就需要对泛型的限定。泛型的限定有两种方式:? extends E,只能接收E类型或者E子类型。? super E,只能接收E类型或者E的父类型。

 

 

---------- android培训java培训、期待与您交流! ----------

 

原创粉丝点击