你的位置:首页 > Java教程

[Java教程]Java使用实现面向对象编程:第七章集合框架的解读=重中之重


对于集合框架,是非常重要的知识,是程序员必须要知道的知识点。

但是我们为什么要引入集合框架呢?

我们之前用过数组存储数据,但是采用数组存储存在了很多的缺陷。而现在我们引用了集合框架,可以完全弥补了数组的缺陷。它灵活,使用,提高软件的开发效率。并且不同的集合适用于不同的场合。

集合框架的好处:

 1.容量自增长;
  2. 提供有用的数据结构和算法,从而减少编程工作;
  3. 提高了程序速度和质量,因为它提供了高性能的数据结构和算法;
  4. 允许不同 API 之间的互操作,API之间可以来回传递集合;
  5. 可以方便地扩展或改写集合。

集合框架是为表示和操作集合而规定的一种统一的标准的体系结构。
任何集合框架都包含三大块内容:对外的接口、接口的实现和对集合运算的算法。
 接口:即表示集合的抽象数据类型。接口提供了让我们对集合中所表示的内容进行单独操作的可能。
  实现:也就是集合框架中接口的具体实现。实际它们就是那些可复用的数据结构。
  算法:在一个实现了某个集合框架中的接口的对象身上完成某种有用的计算的方法,例如查找、排序等。这些算法通常是多态的,因为相同的方法可以在同一个接口被多个类实现时有不同的表现。
事实上,算法是可复用的函数。

它减少了程序设计的辛劳。
集合框架通过提供有用的数据结构和算法使你能集中注意力于你的程序的重要部分上,而不是为了让程序能正常运转而将注意力于低层设计上。
通过这些在无关API之间的简易的互用性,使你免除了为改编对象或转换代码以便联合这些API而去写大量的代码。 它提高了程序速度和质量。
集合框架通过提供对有用的数据结构和算法的高性能和高质量的实现使你的程序速度和质量得到提高。因为每个接口的实现是可互换的,所以你的程序可以很容易的通过改变一个实现而进行调整。
另外,你将可以从写你自己的数据结构的苦差事中解脱出来,从而有更多时间关注于程序其它部分的质量和性能。
减少去学习和使用新的API 的辛劳。 许多API天生的有对集合的存储和获取。
在过去,这样的API都有一些子API帮助操纵它的集合内容,因此在那些特殊的子API之间就会缺乏一致性,你也不得不从零开始学习,并且在使用时也很容易犯错。而标准集合框架接口的出现使这个问题迎刃而解。
减少了设计新API的努力。 设计者和实现者不用再在每次创建一种依赖于集合内容的API时重新设计,他们只要使用标准集合框架的接口即可。 集合框架鼓励软件的复用。 对于遵照标准集合框架接口的新的数据结构天生即是可复用的。
同样对于操作一个实现了这些接口的对象的算法也是如此。 有了这些优点,并通过合理的使用,它就会成为程序员的一种强大的工具。不过,从历史上来看,集合大多其结构相当复杂,也就给它们一个造成极不合理的学习曲线的坏名声。
但是,希望Java2的集合框架能缩短你的学习曲线,从而快速掌握它

现在我们了解集合框架的内容简图:

从该简图可以看出:
java集合接口


Java中集合类定义主要是java.util.*包下面,常用的集合在系统中定义了三大接口,这三类的区别是:
java.util.Set接口及其子类,set提供的是一个无序的集合;
java.util.List接口及其子类,List提供的是一个有序的集合;
java.util.Map接口及其子类,Map提供了一个映射(对应)关系的集合数据结构;
另外,在JDK5中新增了Queue(队列)接口及其子类,提供了基于队列的集合体系。每种集合,都可以理解为用来在内存中存放一组对象的某种”容器“---就像数组,就像前面我们自己定义的队列。

Collection接口

Collection接口是所有集合接口的基类,提供了集合接口的通用操作

public interface Collection<E> extends Iterable<E> {  // Basic operations  int size();  boolean isEmpty();  boolean contains(Object element);  boolean add(E element);     //optional  boolean remove(Object element); //optional  Iterator<E> iterator();  // Bulk operations  boolean containsAll(Collection<?> c);  boolean addAll(Collection<? extends E> c); //optional  boolean removeAll(Collection<?> c);    //optional  boolean retainAll(Collection<?> c);    //optional  void clear();               //optional  // Array operations  Object[] toArray();  <T> T[] toArray(T[] a);}

通过这些操作函数,我们可以进行获取集合中元素的个数 (sizeisEmpty),判断集合中是否包含某个元素(contains),在集合中增加或删除元素(addremove),获取访问迭代器(iterator)等操作。

迭代器

迭代器(Iterator)可以用来遍历集合并对集合中的元素进行删操作。 可以通过集合的iterator 函数获取该集合的迭代器。 Iterator接口如下所示:

public interface Iterator<E> {

    boolean hasNext();

    E next();

    void remove(); //optional

}

当集合中还有元素供迭代器访问时,hasNext函数返回true。此时,可以通过next函数返回集合中的下一个元素。 函数remove删除next()最后一次从集合中访问的元素。

注意:Iterator.remove是在迭代过程中修改collection的唯一安全的方法,在迭代期间不允许使用其它的方法对collection进行操作。

Collection的批量操作

集合的批量操作接口函数如下:

  • containsAll — 检查集合中是否包含指定集合
  • addAll — 在集合中加入指定集合
  • removeAll — 在集合中删除包含于指定集合的元素
  • retainAll —删除集合中不包含于指定集合的元素
  • clear — 删除集合中所有元素

addAllremoveAll retainAll 操作中,如果集合的元素被更改,则返回true。

Set接口

Set 是一个不包含重复元素的集合(Collection)。Set接口中的函数都是从Collection继承而来。 但限制了add 的使用,需要其不能添加重复元素。

Set接口声明如下:

public interface Set<E> extends Collection<E> {  // Basic operations  int size();  boolean isEmpty();  boolean contains(Object element);  boolean add(E element);     //optional  boolean remove(Object element); //optional  Iterator<E> iterator();  // Bulk operations  boolean containsAll(Collection<?> c);  boolean addAll(Collection<? extends E> c); //optional  boolean removeAll(Collection<?> c);    //optional  boolean retainAll(Collection<?> c);    //optional  void clear();               //optional  // Array Operations  Object[] toArray();  <T> T[] toArray(T[] a);}

Set接口批量操作

Set的批量操作和离散数学的集合操作的概论结合的非常好,假如s1和s2是两个Set,它们间的批量操作如下:

s1.containsAll(s2) — 判断s2是否是s1的子集

s1.addAll(s2) —将s1转化为s1和s2的并集

s1.retainAll(s2)将s1转化为s1和s2的交集

s1.removeAll(s2) —将s1转化为s1和s2的差集

List接口

List是一个顺序的Collection(通常被称作序列)。List可以包含重复元素。List接口基本功能如下:

按位置访问 — 通过元素在list中的位置索引访问元素。

查询 — 获取某元素在list中的位置

迭代 — 扩展了Iterator的接口能实现更多功能

List子集合 — 获取List某个位置范围的子集合

List接口如下:

List 接口继承了 Collection 接口以定义一个允许重复项的有序集合。该接口不但能够对列表的一部分进行处理,还添加了面向位置的操作。
 (1) 面向位置的操作包括插入某个元素或 Collection 的功能,还包括获取、除去或更改元素的功能。在 List 中搜索元素可以从列表的头部或尾部开始,如果找到元素,还将报告元素所在的位置 :
  void add(int index, Object element): 在指定位置index上添加元素element
  boolean addAll(int index, Collection c): 将集合c的所有元素添加到指定位置index
  Object get(int index): 返回List中指定位置的元素
  int indexOf(Object o): 返回第一个出现元素o的位置,否则返回-1
  int lastIndexOf(Object o) :返回最后一个出现元素o的位置,否则返回-1
  Object remove(int index) :删除指定位置上的元素
  Object set(int index, Object element) :用元素element取代位置index上的元素,并且返回旧的元素
  (2) List 接口不但以位置序列迭代的遍历整个列表,还能处理集合的子集:
   ListIterator listIterator() : 返回一个列表迭代器,用来访问列表中的元素
   ListIterator listIterator(int index) : 返回一个列表迭代器,用来从指定位置index开始访问列表中的元素
  List subList(int fromIndex, int toIndex) :返回从指定位置fromIndex(包含)到toIndex(不包含)范围中各个元素的列表视图
  “对子列表的更改(如 add()、remove() 和 set() 调用)对底层 List 也有影响。”
  2.1.ListIterator接口
  ListIterator 接口继承 Iterator 接口以支持添加或更改底层集合中的元素,还支持双向访问。ListIterator没有当前位置,光标位于调用previous和next方法返回的值之间。一个长度为n的列表,有n+1个有效索引值:

  (1) void add(Object o): 将对象o添加到当前位置的前面
   void set(Object o): 用对象o替代next或previous方法访问的上一个元素。如果上次调用后列表结构被修改了,那么将抛出IllegalStateException异常。
  (2) boolean hasPrevious(): 判断向后迭代时是否有元素可访问
   Object previous():返回上一个对象
   int nextIndex(): 返回下次调用next方法时将返回的元素的索引
   int previousIndex(): 返回下次调用previous方法时将返回的元素的索引
  “正常情况下,不用ListIterator改变某次遍历集合元素的方向 — 向前或者向后。虽然在技术上可以实现,但previous() 后立刻调用next(),返回的是同一个元素。把调用 next()和previous()的顺序颠倒一下,结果相同。”
  “我们还需要稍微再解释一下 add() 操作。添加一个元素会导致新元素立刻被添加到隐式光标的前面。因此,添加元素后调用 previous() 会返回新元素,而调用 next() 则不起作用,返回添加操作之前的下一个元素。”
  2.2.AbstractList和AbstractSequentialList抽象类
  有两个抽象的 List 实现类:AbstractList 和 AbstractSequentialList。像 AbstractSet 类一样,它们覆盖了 equals() 和 hashCode() 方法以确保两个相等的集合返回相同的哈希码。若两个列表大小相等且包含顺序相同的相同元素,则这两个列表相等。这里的 hashCode() 实现在 List 接口定义中指定,而在这里实现。
  除了equals()和hashCode(),AbstractList和AbstractSequentialList实现了其余 List 方法的一部分。因为数据的随机访问和顺序访问是分别实现的,使得具体列表实现的创建更为容易。需要定义的一套方法取决于您希望支持的行为。您永远不必亲自提供的是 iterator方法的实现。
  2.3. LinkedList类和ArrayList类
  在“集合框架”中有两种常规的 List 实现:ArrayList 和 LinkedList。使用两种 List 实现的哪一种取决于您特定的需要。如果要支持随机访问,而不必在除尾部的任何位置插入或除去元素,那么,ArrayList 提供了可选的集合。但如果,您要频繁的从列表的中间位置添加和除去元素,而只要顺序的访问列表元素,那么,LinkedList 实现更好。
  “ArrayList 和 LinkedList 都实现 Cloneable 接口,都提供了两个构造函数,一个无参的,一个接受另一个Collection”
  2.3.1. LinkedList类
  LinkedList类添加了一些处理列表两端元素的方法。

   (1) void addFirst(Object o): 将对象o添加到列表的开头
    void addLast(Object o):将对象o添加到列表的结尾
  (2) Object getFirst(): 返回列表开头的元素
    Object getLast(): 返回列表结尾的元素
  (3) Object removeFirst(): 删除并且返回列表开头的元素
    Object removeLast():删除并且返回列表结尾的元素
  (4) LinkedList(): 构建一个空的链接列表
    LinkedList(Collection c): 构建一个链接列表,并且添加集合c的所有元素
  “使用这些新方法,您就可以轻松的把 LinkedList 当作一个堆栈、队列或其它面向端点的数据结构。”
  LinkedList<Dog> list=new LinkedList<Dog>();//LinkedList list=new LinkedList()是一样的    //============用add(object o)方法,总是在尾部添加    list.add(dog);    list.add(dog1);    list.addLast(dog2);//最后一个位置    list.addFirst(dog3);//第一个位置        //查看集合中第一条狗狗的信息==========getFirst()方法   Dog dgfirst=  (Dog) list.getFirst();   System.out.println("第一条狗狗的大名是"+dgfirst.getName());    //查看集合中最后一条狗狗的信息===getLast()方法   Dog dglast= (Dog) list.getLast();   System.out.println("最后一条狗狗的大名是"+dglast.getName());   //删除第一条狗狗的信息  Dog refirst= (Dog) list.removeFirst();  System.out.println("删除第一条"+refirst.getName());Dog relast=  (Dog) list.removeLast();System.out.println("删除最后一条"+relast.getName());    System.out.println("\n删除之后还有"+list.size()+"条狗狗");    for (int i = 0; i <list.size(); i++) {      Dog dogall=(Dog) list.get(i);      System.out.println(dogall.getName()+"\t\t"+dogall.getStrain());          }    //判断集合中是否包含集合中某指定的信息=============用contains(object o)方法    if (list.contains(dog3)) {      System.out.println("集合中包含"+dog3.getName()+"的信息");    } else {      System.out.println("集合中不包含"+dog3.getName()+"的信息");    }

  2.3.2. ArrayList类
  ArrayList类封装了一个动态再分配的Object[]数组。每个ArrayList对象有一个capacity。这个capacity表示存储列表中元素的数组的容量。当元素添加到ArrayList时,它的capacity在常量时间内自动增加。
  在向一个ArrayList对象添加大量元素的程序中,可使用ensureCapacity方法增加capacity。这可以减少增加重分配的数量。
  (1) void ensureCapacity(int minCapacity): 将ArrayList对象容量增加minCapacity
  (2) void trimToSize(): 整理ArrayList对象容量为列表当前大小。程序可使用这个操作减少ArrayList对象存储空间。
  2.3.2.1. RandomAccess接口
  一个特征接口。该接口没有任何方法,不过你可以使用该接口来测试某个集合是否支持有效的随机访问。ArrayList和Vector类用于实现该接口。
  List<Dog> list=new ArrayList<Dog>();// List list=new ArrayList()是一样的    //============用add(object o)方法,总是在尾部添加    list.add(dog);    list.add(dog1);    list.add(dog2);    list.add(dog3);    /**     * 添加某元素到某指定任何位置的做法     *     * list.add(int index,Object o)     *  list.add(2,dog3);//添加dog3到指定位置     */    //获取集合中狗狗的数量===============用size()方法    System.out.println("共计有"+list.size()+"条狗狗");    //通过遍历集合显示狗狗的信息===============用get(int index)方法    System.out.println("分别是:");    for (int i = 0; i <list.size(); i++) {      Dog dogall=(Dog) list.get(i);      System.out.println(dogall.getName()+"\t\t"+dogall.getStrain());          }    System.out.println("删除之前共计有"+list.size()+"条狗狗");    //删除集合中某指定位置的狗狗==================用remove(int index) 或 remove(Object o)方法    //方法中写索引或对象都可以    list.remove(0);    list.remove(dog2);    System.out.println("\n删除之后还有"+list.size()+"条狗狗");    for (int i = 0; i <list.size(); i++) {      Dog dogall=(Dog) list.get(i);      System.out.println(dogall.getName()+"\t\t"+dogall.getStrain());          }

Map接口,HashMap类的常用方法
void clear() 
从此映射中移除所有映射关系(可选操作)。
boolean containsKey(Object key)
如果此映射包含指定键的映射关系,则返回 true。
boolean containsValue(Object value)
如果此映射为指定值映射一个或多个键,则返回 true。
Set<Map.Entry<K,V>> entrySet()
返回此映射中包含的映射关系的 set 视图。
boolean equals(Object o)
比较指定的对象与此映射是否相等。
V get(Object key)
返回此映射中映射到指定键的值。
int hashCode()
返回此映射的哈希码值。
boolean isEmpty()
如果此映射未包含键-值映射关系,则返回 true。
Set<K> keySet()
返回此映射中包含的键的 set 视图。
V put(K key, V value)
将指定的值与此映射中的指定键相关联(可选操作)。
void putAll(Map<? extends K,? extends V> t)
从指定映射中将所有映射关系复制到此映射中(可选操作)。
V remove(Object key)
如果存在此键的映射关系,则将其从映射中移除(可选操作)。
int size()
返回此映射中的键-值映射关系数。
Collection<V> values()
返回此映射中包含的值的 collection 视图。
Iterator迭代器

 (1) 使用方法iterator()要求容器返回一个Iterator。第一次调用Iterator的next()方法时,它返回序列的第一个元素。注意:iterator()方法是java.lang.Iterable接口,被Collection继承。

  (2) 使用next()获得序列中的下一个元素。

  (3) 使用hasNext()检查序列中是否还有元素。

  (4) 使用remove()将迭代器新返回的元素删除。

  Iterator是Java迭代器最简单的实现,为List设计的ListIterator具有更多的功能,它可以从两个方向遍历List,也可以从List中插入和删除元素。

Iterator模式有三个重要的作用:

1)它支持以不同的方式遍历一个聚合 复杂的聚合可用多种方式进行遍历,如二叉树的遍历,可以采用前序、中序或后序遍历。迭代器模式使得改变遍历算法变得很容易: 仅需用一个不同的迭代器的实例代替原先的实例即可,你也可以自己定义迭代器的子类以支持新的遍历,或者可以在遍历中增加一些逻辑,如有条件的遍历等。

2)迭代器简化了聚合的接口 有了迭代器的遍历接口,聚合本身就不再需要类似的遍历接口了,这样就简化了聚合的接口。

3)在同一个聚合上可以有多个遍历 每个迭代器保持它自己的遍历状态,因此你可以同时进行多个遍历。

4)此外,Iterator模式可以为遍历不同的聚合结构(需拥有相同的基类)提供一个统一的接口,即支持多态迭代。

简 单说来,迭代器模式也是Delegate原则的一个应用,它将对集合进行遍历的功能封装成独立的Iterator,不但简化了集合的接口,也使得修改、增 加遍历方式变得简单。从这一点讲,该模式与Bridge模式、Strategy模式有一定的相似性,但Iterator模式所讨论的问题与集合密切相关, 造成在Iterator在实现上具有一定的特殊性,具体将在示例部分进行讨论。

正如前面所说,与集合密切相关,限制了 Iterator模式的广泛使用。在一般的底层集合支持类中,我们往往不愿“避轻就重”将集合设计成集合 + Iterator 的形式,而是将遍历的功能直接交由集合完成,以免犯了“过度设计”的诟病,但是,如果我们的集合类确实需要支持多种遍历方式(仅此一点仍不一定需要考虑 Iterator模式,直接交由集合完成往往更方便),或者,为了与系统提供或使用的其它机制,如STL算法,保持一致时,Iterator模式才值得考 虑。

Iterator遍历

import java.util.ArrayList;import java.util.Iterator;import java.util.List;public class ArrayTest {  public static void main(String[] args) {    List<Integer> list = new ArrayList<Integer>();    list.add(1);    list.add(2);    list.add(3);    System.out.println("用for循环遍历");    for (int i = 0; i < list.size(); i++) {      System.out.println(list.get(i));    }    System.out.println("用增强for循环");    for (Integer i : list) {      System.out.println(i);    }    System.out.println("用iterator+while");    Iterator<Integer> it = list.iterator();    while (it.hasNext()) {      int i = (Integer) it.next();      System.out.println(i);    }    System.out.println("用iterator+for");    for (Iterator<Integer> iter = list.iterator(); iter.hasNext();) {      int i = (Integer) iter.next();      System.out.println(i);    }  }}

泛型集合

泛型(Generic type 或者 generics)是对 Java 语言的类型系统的一种扩展,以支持创建可以按类型进行参数化的类。可以把类型参数看作是使用参数化类型时指定的类型的一个占位符,就像方法的形式参数是运行时传递的值的占位符一样。 可以在集合框架(Collection framework)中看到泛型的动机。例如,Map 类允许您向一个 Map 添加任意类的对象,即使最常见的情况是在给定映射(map)中保存某个特定类型(比如 String)的对象。 因为 Map.get() 被定义为返回 Object,所以一般必须将 Map.get() 的结果强制类型转换为期望的类型,如下面的代码所示: Map m = new HashMap(); m.put("key", "blarg"); String s = (String) m.get("key"); 要让程序通过编译,必须将 get() 的结果强制类型转换为 String

 

泛型(Generic type 或者 generics)是对 Java 语言的类型系统的一种扩展,以支持创建可以按类型进行参数化的类。可以把类型参数看作是使用参数化类型时指定的类型的一个占位符,就像方法的形式参数是运行时传递的值的占位符一样。

可以在集合框架(Collection framework)中看到泛型的动机。例如,Map 类允许您向一个 Map 添加任意类的对象,即使最常见的情况是在给定映射(map)中保存某个特定类型(比如 String)的对象。

因为 Map.get() 被定义为返回 Object,所以一般必须将 Map.get() 的结果强制类型转换为期望的类型,如下面的代码所示:

Map m = new HashMap();
m.put("key", "blarg");
String s = (String) m.get("key");

要让程序通过编译,必须将 get() 的结果强制类型转换为 String,并且希望结果真的是一个 String。但是有可能某人已经在该映射中保存了不是 String 的东西,这样的话,上面的代码将会抛出 ClassCastException。

理想情况下,您可能会得出这样一个观点,即 m 是一个 Map,它将 String 键映射到 String 值。这可以让您消除代码中的强制类型转换,同时获得一个附加的类型检查层,该检查层可以防止有人将错误类型的键或值保存在集合中。这就是泛型所做的工作。

 

泛型的好处

Java 语言中引入泛型是一个较大的功能增强。不仅语言、类型系统和编译器有了较大的变化,以支持泛型,而且类库也进行了大翻修,所以许多重要的类,比如集合框架,都已经成为泛型化的了。这带来了很多好处:

类型安全。 泛型的主要目标是提高 Java 程序的类型安全。通过知道使用泛型定义的变量的类型限制,编译器可以在一个高得多的程度上验证类型假设。没有泛型,这些假设就只存在于程序员的头脑中(或者如果幸运的话,还存在于代码注释中)。

Java 程序中的一种流行技术是定义这样的集合,即它的元素或键是公共类型的,比如“String 列表”或者“String 到 String 的映射”。通过在变量声明中捕获这一附加的类型信息,泛型允许编译器实施这些附加的类型约束。类型错误现在就可以在编译时被捕获了,而不是在运行时当作 ClassCastException 展示出来。将类型检查从运行时挪到编译时有助于您更容易找到错误,并可提高程序的可靠性。

消除强制类型转换。 泛型的一个附带好处是,消除源代码中的许多强制类型转换。这使得代码更加可读,并且减少了出错机会。

尽管减少强制类型转换可以降低使用泛型类的代码的罗嗦程度,但是声明泛型变量会带来相应的罗嗦。比较下面两个代码例子。

该代码不使用泛型

List li = new ArrayList();
li.put(new Integer(3));
Integer i = (Integer) li.get(0);


该代码使用泛型

List<Integer> li = new ArrayList<Integer>();
li.put(new Integer(3));
Integer i = li.get(0);


在简单的程序中使用一次泛型变量不会降低罗嗦程度。但是对于多次使用泛型变量的大型程序来说,则可以累积起来降低罗嗦程度。

潜在的性能收益。 泛型为较大的优化带来可能。在泛型的初始实现中,编译器将强制类型转换(没有泛型的话,程序员会指定这些强制类型转换)插入生成的字节码中。但是更多类型信息可用于编译器这一事实,为未来版本的 JVM 的优化带来可能。

由于泛型的实现方式,支持泛型(几乎)不需要 JVM 或类文件更改。所有工作都在编译器中完成,编译器生成类似于没有泛型(和强制类型转换)时所写的代码,只是更能确保类型安全而已。


泛型用法的例子

泛型的许多最佳例子都来自集合框架,因为泛型让您在保存在集合中的元素上指定类型约束。考虑这个使用 Map 类的例子,其中涉及一定程度的优化,即 Map.get() 返回的结果将确实是一个 String:


Map m = new HashMap();
m.put("key", "blarg");
String s = (String) m.get("key");


如果有人已经在映射中放置了不是 String 的其他东西,上面的代码将会抛出 ClassCastException。泛型允许您表达这样的类型约束,即 m 是一个将 String 键映射到 String 值的 Map。这可以消除代码中的强制类型转换,同时获得一个附加的类型检查层,这个检查层可以防止有人将错误类型的键或值保存在集合中。

下面的代码示例展示了 JDK 5.0 中集合框架中的 Map 接口的定义的一部分:


public interface Map<K, V> {
public void put(K key, V value);
public V get(K key);
}

注意该接口的两个附加物:

类型参数 K 和 V 在类级别的规格说明,表示在声明一个 Map 类型的变量时指定的类型的占位符。

在 get()、put() 和其他方法的方法签名中使用的 K 和 V。

为了赢得使用泛型的好处,必须在定义或实例化 Map 类型的变量时为 K 和 V 提供具体的值。以一种相对直观的方式做这件事:

Map<String, String> m = new HashMap<String, String>();
m.put("key", "blarg");
String s = m.get("key");

当使用 Map 的泛型化版本时,您不再需要将 Map.get() 的结果强制类型转换为 String,因为编译器知道 get() 将返回一个 String。

在使用泛型的版本中并没有减少键盘录入;实际上,比使用强制类型转换的版本需要做更多键入。使用泛型只是带来了附加的类型安全。因为编译器知道关于您将放进 Map 中的键和值的类型的更多信息,所以类型检查从执行时挪到了编译时,这会提高可靠性并加快开发速度。

 

向后兼容

在 Java 语言中引入泛型的一个重要目标就是维护向后兼容。尽管 JDK 5.0 的标准类库中的许多类,比如集合框架,都已经泛型化了,但是使用集合类(比如 HashMap 和 ArrayList)的现有代码将继续不加修改地在 JDK 5.0 中工作。当然,没有利用泛型的现有代码将不会赢得泛型的类型安全好处。

二 泛型基础

类型参数

在定义泛型类或声明泛型类的变量时,使用尖括号来指定形式类型参数。形式类型参数与实际类型参数之间的关系类似于形式方法参数与实际方法参数之间的关系,只是类型参数表示类型,而不是表示值。

泛型类中的类型参数几乎可以用于任何可以使用类名的地方。例如,下面是 java.util.Map 接口的定义的摘录:

public interface Map<K, V> {
public void put(K key, V value);
public V get(K key);
}

Map 接口是由两个类型参数化的,这两个类型是键类型 K 和值类型 V。(不使用泛型)将会接受或返回 Object 的方法现在在它们的方法签名中使用 K 或 V,指示附加的类型约束位于 Map 的规格说明之下。

当声明或者实例化一个泛型的对象时,必须指定类型参数的值:

Map<String, String> map = new HashMap<String, String>();

注意,在本例中,必须指定两次类型参数。一次是在声明变量 map 的类型时,另一次是在选择 HashMap 类的参数化以便可以实例化正确类型的一个实例时。

编译器在遇到一个 Map<String, String> 类型的变量时,知道 K 和 V 现在被绑定为 String,因此它知道在这样的变量上调用 Map.get() 将会得到 String 类型。

除了异常类型、枚举或匿名内部类以外,任何类都可以具有类型参数。


命名类型参数

推荐的命名约定是使用大写的单个字母名称作为类型参数。这与 C++ 约定有所不同(参阅 附录 A:与 C++ 模板的比较),并反映了大多数泛型类将具有少量类型参数的假定。对于常见的泛型模式,推荐的名称是:

K —— 键,比如映射的键。 
V —— 值,比如 List 和 Set 的内容,或者 Map 中的值。 
E —— 异常类。 
T —— 泛型


泛型不是协变的

关于泛型的混淆,一个常见的来源就是假设它们像数组一样是协变的。其实它们不是协变的。List<Object> 不是 List<String> 的父类型。

如果 A 扩展 B,那么 A 的数组也是 B 的数组,并且完全可以在需要 B[] 的地方使用 A[]:

Integer[] intArray = new Integer[10]; 
Number[] numberArray = intArray;

上面的代码是有效的,因为一个 Integer 是 一个 Number,因而一个 In

简单例子:

public static void main(String[] args) {  Dog dog=new Dog( "欧欧", "雪娜娜");   Dog dog1=new Dog( "亚亚", "拉布拉多");  Dog dog2=new Dog( "美美", "雪娜娜");  Dog dog3=new Dog( "菲菲", "拉布拉多");  Map<String, Dog> map=new HashMap<String, Dog>(); //Map map=new HashMap()一样的  map.put(dog.getName(),dog);  map.put(dog1.getName(),dog1);  map.put(dog2.getName(),dog2);  map.put(dog3.getName(),dog3);  /**   * 通过迭代器依次输出集合中的所有信息   */  System.out.println("使用Iterator遍历,所有狗狗的昵称和品种分别是:");  Set<String> setkeys=map.keySet();//取出所有key的集合    Set setkeys=map.keySet() 是一样的  Iterator<String> it= setkeys.iterator();//获取Iterator对象    Iterator it= setkeys.iterator()是一样的  /**   * foreach循环遍历   */  /*   * for (String key1 : setkeys) {    Dog dogkey=map.get(key1); // Dog dogkey= (Dog)map.get(key)    System.out.println(key1+"\t"+dogkey.getStrain());  }   */    while (it.hasNext()) {    String key = (String) it.next();    Dog dogkey=map.get(key); // Dog dogkey= (Dog)map.get(key)    System.out.println(key+"\t"+dogkey.getStrain());  }  }

Java基本类型与包装类

Java语言提供了八种基本类型。六种数字类型(四个整数型,两个浮点型),一种字符类型,还有一种布尔型。

1、整数:包括int,short,byte,long ,初始值为0

2、浮点型:float,double ,初始值为0.0

3、字符:char ,初始值为空格,即'' ",如果输出,在Console上是看不到效果的。

4、布尔:boolean ,初始值为false

注意:

表格里的^代表的是次方;

java采用unicode,2个字节来表示一个字符。

基本类型的包装类

Integer 、Long、Short、Byte、Character、Double、Float、Boolean、BigInteger、BigDecmail

其中BigInteger、BigDecimal没有相对应的基本类型,主要应用于高精度的运算,BigInteger 支持任意精度的整数,

BigDecimal支持任意精度带小数点的运算。

基本类型与包装类型的异同:

1、在Java中,一切皆对象,但八大基本类型却不是对象。

2、声明方式的不同,基本类型无需通过new关键字来创建,而封装类型需new关键字。

3、存储方式及位置的不同,基本类型是直接存储变量的值保存在堆栈中能高效的存取,封装类型需要通过引用指向实例,具体的实例保存在堆中。

4、初始值的不同,封装类型的初始值为null,基本类型的的初始值视具体的类型而定,比如int类型的初始值为0,boolean类型为false;

5、使用方式的不同,比如与集合类合作使用时只能使用包装类型。

就写到这里啦,有什么不足之处,望大神们多多指点!!!