Java集合,泛型,枚举详解

Java集合详解

为了保存数量不确定的数据,以及保存具有映射关系的数据(也被称为关联数组),Java 提供了集合类。集合类主要负责保存、盛装其他数据,因此集合类也被称为容器类。Java 所有的集合类都位于 java.util 包下,提供了一个表示和操作对象集合的统一构架,包含大量集合接口,以及这些接口的实现类和操作它们的算法。

集合类和数组不一样,数组元素既可以是基本类型的值,也可以是对象(实际上保存的是对象的引用变量),而集合里只能保存对象(实际上只是保存对象的引用变量,但通常习惯上认为集合里保存的是对象)。

Java 集合类型分为 Collection 和 Map,它们是 Java 集合的根接口,这两个接口又包含了一些子接口或实现类。图 1 和图 2 分别为 Collection 和 Map 的子接口及其实现类。
在这里插入图片描述
在 图 1 和图 2 中,黄色块为集合的接口,蓝色块为集合的实现类。表 1 介绍了这些接口的作用。
在这里插入图片描述
对于 Set、List、Queue 和 Map 这 4 种集合,Java 最常用的实现类分别是 HashSet、TreeSet、ArrayList、ArrayDueue、LinkedList 和 HashMap、TreeMap 等。表 2 介绍了集合中这些常用的实现类。
在这里插入图片描述

Collection接口详解

Collection 接口是 List、Set 和 Queue 接口的父接口,通常情况下不被直接使用。Collection 接口定义了一些通用的方法,通过这些方法可以实现对集合的基本操作。定义的方法既可用于操作 Set 集合,也可用于操作 List 和 Queue 集合。

本节将介绍 Collection 接口中常用的方法,如表 1 所示。
在这里插入图片描述
注意:以上方法完全来自于 Java API 文档,读者可自行参考 API 文档来查阅这些方法的详细信息。读者无需硬性记忆这些方法,可以和实际生活结合记忆。集合类就像容器,现实生活中容器的功能,就是添加对象、删除对象、清空容器和判断容器是否为空等,集合类为这些功能都提供了对应的方法。

在传统模式下,把一个对象“丢进”集合中后,集合会忘记这个对象的类型。也就是说,系统把所有的集合元素都当成 Object 类型。从 Java 5 以后,可以使用泛型来限制集合里元素的类型,并让集合记住所有集合元素的类型。

List集合:ArrayList和LinkedList类的用法及区别

List 是一个 有序、可重复的集合 ,集合中每个元素都有其对应的顺序索引。List 集合允许使用重复元素,可以通过索引来访问指定位置的集合元素。List 集合默认按元素的添加顺序设置元素的索引,第一个添加到 List 集合中的元素的索引为 0,第二个为 1,依此类推。

List 实现了 Collection 接口,它主要有两个常用的实现类:ArrayList 类和 LinkedList 类。

ArrayList 类

ArrayList 类实现了可变数组的大小,存储在内的数据称为元素。它还提供了快速 基于索引访问元素的方式,对尾部成员的增加和删除支持较好。使用 ArrayList 创建的集合,允许对集合中的元素进行快速的随机访问,不过,向 ArrayList 中插入与删除元素的速度相对较慢。

ArrayList 类的常用构造方法有如下两种重载形式:

ArrayList():构造一个初始容量为 10 的空列表。
ArrayList(Collection<?extends E>c):构造一个包含指定 

Collection 元素的列表,这些元素是按照该 Collection 的迭代器返回它们的顺序排列的。

LinkedList类
LinkedList 类采用链表结构保存对象,这种结构的优点是便于向集合中插入或者删除元素。需要频繁向集合中插入和删除元素时,使用 LinkedList 类比 ArrayList 类效果高,但是 LinkedList 类随机访问元素的速度则相对较慢。这里的随机访问是指检索集合中特定索引位置的元素。

Set集合:HashSet和TreeSet类

Set 集合类似于一个罐子,程序可以依次把多个对象“丢进”Set 集合,而 Set 集合通常不能记住元素的添加顺序。也就是说 Set 集合中的对象不按特定的方式排序,只是简单地把对象加入集合。Set 集合中不能包含重复的对象,并且最多只允许包含一个 null 元素。

Set 实现了 Collection 接口,它主要有两个常用的实现类:HashSet 类和 TreeSet类。
HashSet 是 Set 接口的典型实现,大多数时候使用 Set 集合时就是使用这个实现类。HashSet 是按照 Hash 算法来存储集合中的元素。因此具有很好的存取和查找性能。

HashSet 具有以下特点:

不能保证元素的排列顺序,顺序可能与添加顺序不同,顺序也有可能发生变化。
HashSet 不是同步的,如果多个线程同时访问或修改一个 HashSet,则必须通过代码来保证其同步。
集合元素值可以是 null。

当向 HashSet 集合中存入一个元素时,HashSet 会调用该对象的 hashCode() 方法来得到该对象的 hashCode 值,然后根据该 hashCode 值决定该对象在 HashSet 中的存储位置。如果有两个元素通过 equals() 方法比较返回的结果为 true,但它们的 hashCode 不相等,HashSet 将会把它们存储在不同的位置,依然可以添加成功。

也就是说,两个对象的 hashCode 值相等且通过 equals() 方法比较返回结果为 true,则 HashSet 集合认为两个元素相等。

在 HashSet 类中实现了 Collection 接口中的所有方法。HashSet 类的常用构造方法重载形式如下。

HashSet():
	构造一个新的空的 Set 集合。
HashSet(Collection<? extends E>c):
	构造一个包含指定 Collection 集合元素的新 Set 集合。其中,“< >”中的 extends 表示 HashSet 的父类,
	即指明该 Set 集合中存放的集合元素类型。c 表示其中的元素将被存放在此 Set 集合中。

下面的代码演示了创建两种不同形式的 HashSet 对象。

HashSet hs = new HashSet();    // 调用无参的构造函数创建HashSet对象
HashSet<String> hss = new HashSet<String>();    // 创建泛型的 HashSet 集合对象

如果向 Set 集合中添加两个相同的元素,则后添加的会覆盖前面添加的元素,即在 Set 集合中不会出现相同的元素。

TreeSet 类
TreeSet 类同时实现了 Set 接口和 SortedSet 接口。SortedSet 接口是 Set 接口的子接口,可以实现对集合进行自然排序,因此使用 TreeSet 类实现的 Set 接口默认情况下是自然排序的,这里的自然排序指的是升序排序。

TreeSet 只能对实现了 Comparable 接口的类对象进行排序,因为 Comparable 接口中有一个 compareTo(Object o) 方法用于比较两个对象的大小。例如 a.compareTo(b),如果 a 和 b 相等,则该方法返回 0;如果 a 大于 b,则该方法返回大于 0 的值;如果 a 小于 b,则该方法返回小于 0 的值。

在使用自然排序时只能向 TreeSet 集合中添加相同数据类型的对象,否则会抛出 ClassCastException 异常。如果向 TreeSet 集合中添加了一个 Double 类型的对象,则后面只能添加 Double 对象,不能再添加其他类型的对象,例如 String 对象等。

Map集合详解

Map 是一种键-值对(key-value)集合,Map 集合中的每一个元素都包含一个键(key)对象和一个值(value)对象。用于保存具有映射关系的数据。

Map 集合里保存着两组值,一组值用于保存 Map 里的 key,另外一组值用于保存 Map 里的 value,key 和 value 都可以是任何引用类型的数据。Map 的 key 不允许重复,value 可以重复,即同一个 Map 对象的任何两个 key 通过 equals 方法比较总是返回 false。

Map 中的 key 和 value 之间存在单向一对一关系,即通过指定的 key,总能找到唯一的、确定的 value。从 Map 中取出数据时,只要给出指定的 key,就可以取出对应的 value。

Map 接口主要有两个实现类:HashMap 类和 TreeMap 类。其中,HashMap 类按哈希算法来存取键对象,而 TreeMap 类可以对键对象进行排序。

TreeMap 类的使用方法与 HashMap 类相同,唯一不同的是 TreeMap 类可以对键对象进行排序

遍历Map集合的四种方式

Map 集合的遍历与 List 和 Set 集合不同。Map 有两组值,因此遍历时可以只遍历值的集合,也可以只遍历键的集合,也可以同时遍历。Map 以及实现 Map 的接口类(如 HashMap、TreeMap、LinkedHashMap、Hashtable 等)都可以用以下几种方式遍历。

1)在 for 循环中使用 entries 实现 Map 的遍历(最常见和最常用的)。

public static void main(String[] args) {
    Map<String, String> map = new HashMap<String, String>();
    map.put("Java入门教程", "http://c.biancheng.net/java/");
    map.put("C语言入门教程", "http://c.biancheng.net/c/");
    for (Map.Entry<String, String> entry : map.entrySet()) {
        String mapKey = entry.getKey();
        String mapValue = entry.getValue();
        System.out.println(mapKey + ":" + mapValue);
    }
}

2)使用 for-each 循环遍历 key 或者 values,一般适用于只需要 Map 中的 key 或者 value 时使用。性能上比 entrySet 较好。

Map<String, String> map = new HashMap<String, String>();
map.put("Java入门教程", "http://c.biancheng.net/java/");
map.put("C语言入门教程", "http://c.biancheng.net/c/");
// 打印键集合
for (String key : map.keySet()) {
    System.out.println(key);
}
// 打印值集合
for (String value : map.values()) {
    System.out.println(value);
}

3)使用迭代器(Iterator)遍历

Map<String, String> map = new HashMap<String, String>();
map.put("Java入门教程", "http://c.biancheng.net/java/");
map.put("C语言入门教程", "http://c.biancheng.net/c/");
Iterator<Entry<String, String>> entries = map.entrySet().iterator();
while (entries.hasNext()) {
    Entry<String, String> entry = entries.next();
    String key = entry.getKey();
    String value = entry.getValue();
    System.out.println(key + ":" + value);
}

4)通过键找值遍历,这种方式的效率比较低,因为本身从键取值是耗时的操作。

for(String key : map.keySet()){
    String value = map.get(key);
    System.out.println(key+":"+value);
}

Collections类操作集合详解

Collections 类是 Java 提供的一个操作 Set、List 和 Map 等集合的工具类。Collections 类提供了许多操作集合的静态方法,借助这些静态方法可以实现集合元素的排序、查找替换和复制等操作。下面介绍 Collections 类中操作集合的常用方法。

排序(正向和逆向)
Collections 提供了如下方法用于对 List 集合元素进行排序。

  • void reverse(List list):对指定 List 集合元素进行逆向排序。
  • void shuffle(List list):对 List 集合元素进行随机排序(shuffle 方法模拟了“洗牌”动作)。
  • void sort(List list):根据元素的自然顺序对指定 List 集合的元素按升序进行排序。
  • void sort(List list, Comparator c):根据指定 Comparator 产生的顺序对 List 集合元素进行排序。
  • void swap(List list, int i, int j):将指定 List 集合中的 i 处元素和 j 处元素进行交换。
  • void rotate(List list, int distance):当 distance 为正数时,将 list 集合的后 distance 个元素“整体”移到前面;当 distance 为负数时,将 list 集合的前 distance
    个元素“整体”移到后面。该方法不会改变集合的长度。

查找、替换操作
Collections 还提供了如下常用的用于查找、替换集合元素的方法。

  • int binarySearch(List list,Object key):使用二分搜索法搜索指定的 List 集合,以获得指定对象在 List集合中的索引。如果要使该方法可以正常工作,则必须保证 List 中的元素已经处于有序状态。
  • Object max(Collection coll):根据元素的自然顺序,返回给定集合中的最大元素。
  • Object max(Collection coll, Comparator
    comp):根据 Comparator 指定的顺序,返回给定集合中的最大元素。
  • Object min(Collection coll):根据元素的自然顺序,返回给定集合中的最小元素。
  • Object min(Collection coll, Comparator
    comp):根据 Comparator 指定的顺序,返回给定集合中的最小元素。
  • void fill(List list, Object obj):使用指定元素 obj 替换指定 List 集合中的所有元素。
  • int frequency(Collection c, Object o):返回指定集合中指定元素的出现次数。
  • int indexOfSubList(List source, List target):返回子 List 对象在父 List 对象中第一次出现的位置索引;如果父 List 中没有出现这样的子 List,则返回-1。
  • int lastIndexOfSubList(List source, List target):返回子 List 对象在父 List 对象中最后一次出现的位置索引;如果父 List 中没有岀现这样的子 List,则返回 -1。
  • boolean replaceAll(List list, Object oldVal, Object newVal):使用一个新值 newVal 替换 List 对象的所有旧值 oldVal。

复制

Collections 类的 copy() 静态方法用于将指定集合中的所有元素复制到另一个集合中。执行 copy() 方法后,目标集合中每个已复制元素的索引将等同于源集合中该元素的索引。

copy() 方法的语法格式如下:

void copy(List <? super T> dest,List<? extends T> src)

其中,dest 表示目标集合对象,src 表示源集合对象。
将 srcList 集合中的全部元素复制到 destList 集合中
注意:目标集合的长度至少和源集合的长度相同,如果目标集合的长度更长,则不影响目标集合中的其余元素。如果目标集合长度不够而无法包含整个源集合元素,程序将抛出 IndexOutOfBoundsException 异常。

例子:在一个集合中保存了 5 个商品名称,现在要使用 Collections 类中的 copy() 方法将其中的 3 个替换掉。具体实现的代码如下:

public class Test5 {
    public static void main(String[] args) {
        Scanner input = new Scanner(System.in);
        List srcList = new ArrayList();
        List destList = new ArrayList();
        destList.add("苏打水");
        destList.add("木糖醇");
        destList.add("方便面");
        destList.add("火腿肠");
        destList.add("冰红茶");
        System.out.println("原有商品如下:");
        for (int i = 0; i < destList.size(); i++) {
            System.out.println(destList.get(i));
        }
        System.out.println("输入替换的商品名称:");
        for (int i = 0; i < 3; i++) {
            System.out.println("第 " + (i + 1) + " 个商品:");
            String name = input.next();
            srcList.add(name);
        }
        // 调用copy()方法将当前商品信息复制到原有商品信息集合中
        Collections.copy(destList, srcList);
        System.out.println("当前商品有:");
        for (int i = 0; i < destList.size(); i++) {
            System.out.print(destList.get(i) + "\t");
        }
    }
}

如上述代码,首先创建了两个 List 对象 srcList 和 destList,并向 destList 集合中添加了 5 个元素,向 srcList 集合中添加了 3 个元素,然后调用 Collections 类中 copy() 方法将 srcList 集合中的全部元素复制到 destList 集合中。由于 destList 集合中含有 5 个元素,故最后两个元素不会被覆盖。

运行该程序,具体的执行结果如下所示。

		原有商品如下:
		苏打水
		木糖醇
		方便面
		火腿肠
		冰红茶
		输入替换的商品名称:
		第 1 个商品:
		燕麦片
		第 2 个商品:
		八宝粥
		第 3 个商品:
		软面包
		当前商品有:
		燕麦片    八宝粥    软面包    火腿肠    冰红茶

Iterator(迭代器)遍历Collection集合元素

Iterator(迭代器)是一个接口,它的作用就是遍历容器的所有元素也是 Java 集合框架的成员,但它与 Collection 和 Map 系列的集合不一样,Collection 和 Map 系列集合主要用于盛装其他对象,而 Iterator 则主要用于遍历(即迭代访问)Collection 集合中的元素。

Iterator 接口隐藏了各种 Collection 实现类的底层细节,向应用程序提供了遍历 Collection 集合元素的统一编程接口。Iterator 接口里定义了如下 4 个方法。

boolean hasNext():如果被迭代的集合元素还没有被遍历完,则返回 true。
Object next():返回集合里的下一个元素。
void remove():删除集合里上一次 next 方法返回的元素。
void forEachRemaining(Consumer action):这是 Java 8 为 Iterator 新增的默认方法,该方法可使用 Lambda 表达式来遍历集合元素。

Iterator 必须依附于 Collection 对象,若有一个 Iterator 对象,则必然有一个与之关联的 Collection 对象

所以当使用 Iterator 对集合元素进行迭代时,Iterator 并不是把集合元素本身传给了迭代变量,而是把集合元素的值传给了迭代变量,所以修改迭代变量的值对集合元素本身没有任何影响。

当使用 Iterator 迭代访问 Collection 集合元素时,Collection 集合里的元素不能被改变,只有通过 Iterator 的 remove() 方法删除上一次 next() 方法返回的集合元素才可以,否则将会引发“java.util.ConcurrentModificationException”异常。

Iterator 迭代器采用的是快速失败(fail-fast)机制,一旦在迭代过程中检测到该集合已经被修改(通常是程序中的其他线程修改),程序立即引发 ConcurrentModificationException 异常,而不是显示修改后的结果,这样可以避免共享资源而引发的潜在问题。
快速失败(fail-fast)机制,是 Java Collection 集合中的一种错误检测机制。

Java泛型

由于把对象“丢进”集合时,集合丢失了对象的状态信息,集合只知道它盛装的是 Object,因此取出集合元素后通常还需要进行强制类型转换。这种强制类型转换既增加了编程的复杂度,也可能引发 ClassCastException 异常。

所以为了解决上述问题,从 Java 1.5 开始提供了泛型。泛型可以在编译的时候检查类型安全,并且所有的强制转换都是自动和隐式的,提高了代码的重用率。

泛型集合
泛型本质上是提供类型的“类型参数”,也就是参数化类型。我们可以为类、接口或方法指定一个类型参数,通过这个参数限制操作的数据类型,从而保证类型转换的绝对安全。
例 1
下面将结合泛型与集合编写一个案例实现图书信息输出。

1)首先需要创建一个表示图书的实体类 Book,其中包括的图书信息有图书编号、图书名称和价格。Book 类的具体代码如下:

public class Book {
    private int Id; // 图书编号
    private String Name; // 图书名称
    private int Price; // 图书价格
    public Book(int id, String name, int price) { // 构造方法
        this.Id = id;
        this.Name = name;
        this.Price = price;
    }
    public String toString() { // 重写 toString()方法
        return this.Id + ", " + this.Name + "," + this.Price;
    }
}

2)使用 Book 作为类型创建 Map 和 List 两个泛型集合,然后向集合中添加图书元素,最后输出集合中的内容。具体代码如下:

public class Test14 {
    public static void main(String[] args) {
        // 创建3个Book对象
        Book book1 = new Book(1, "唐诗三百首", 8);
        Book book2 = new Book(2, "小星星", 12);
        Book book3 = new Book(3, "成语大全", 22);
        Map<Integer, Book> books = new HashMap<Integer, Book>(); // 定义泛型 Map 集合
        books.put(1001, book1); // 将第一个 Book 对象存储到 Map 中
        books.put(1002, book2); // 将第二个 Book 对象存储到 Map 中
        books.put(1003, book3); // 将第三个 Book 对象存储到 Map 中
        System.out.println("泛型Map存储的图书信息如下:");
        for (Integer id : books.keySet()) {
            // 遍历键
            System.out.print(id + "——");
            System.out.println(books.get(id)); // 不需要类型转换
        }
        List<Book> bookList = new ArrayList<Book>(); // 定义泛型的 List 集合
        bookList.add(book1);
        bookList.add(book2);
        bookList.add(book3);
        System.out.println("泛型List存储的图书信息如下:");
        for (int i = 0; i < bookList.size(); i++) {
            System.out.println(bookList.get(i)); // 这里不需要类型转换
        }
    }
}

在该示例中,第 7 行代码创建了一个键类型为 Integer、值类型为 Book 的泛型集合,即指明了该 Map 集合中存放的键必须是 Integer 类型、值必须为 Book 类型,否则编译出错。在获取 Map 集合中的元素时,不需要将books.get(id);获取的值强制转换为 Book 类型,程序会隐式转换。在创建 List 集合时,同样使用了泛型,因此在获取集合中的元素时也不需要将bookList.get(i)代码强制转换为 Book 类型,程序会隐式转换。

执行结果如下:

泛型Map存储的图书信息如下:
1001——1, 唐诗三百首,8
1003——3, 成语大全,22
1002——2, 小星星,12
泛型List存储的图书信息如下:
1, 唐诗三百首,8
2, 小星星,12
3, 成语大全,22

泛型类

除了可以定义泛型集合之外,还可以直接限定泛型类的类型参数。语法格式如下:

public class class_name<data_type1,data_type2,>{}

其中,class_name 表示类的名称,data_ type1 等表示类型参数。Java 泛型支持声明一个以上的类型参数,只需要将类型用逗号隔开即可。

泛型类一般用于类中的属性类型不确定的情况下。在声明属性时,使用下面的语句:

private data_type1 property_name1;
private data_type2 property_name2;

该语句中的 data_type1 与类声明中的 data_type1 表示的是同一种数据类型。
例 2
在实例化泛型类时,需要指明泛型类中的类型参数,并赋予泛型类属性相应类型的值。例如,下面的示例代码创建了一个表示学生的泛型类,该类中包括 3 个属性,分别是姓名、年龄和性别。

public class Stu<N, A, S> {
    private N name; // 姓名
    private A age; // 年龄
    private S sex; // 性别
    // 创建类的构造函数
    public Stu(N name, A age, S sex) {
        this.name = name;
        this.age = age;
        this.sex = sex;
    }
    // 下面是上面3个属性的setter/getter方法
    public N getName() {
        return name;
    }
    public void setName(N name) {
        this.name = name;
    }
    public A getAge() {
        return age;
    }
    public void setAge(A age) {
        this.age = age;
    }
    public S getSex() {
        return sex;
    }
    public void setSex(S sex) {
        this.sex = sex;
    }
}

接着创建测试类。在测试类中调用 Stu 类的构造方法实例化 Stu 对象,并给该类中的 3 个属性赋予初始值,最终需要输出学生信息。测试类的代码实现如下:

public class Test14 {
    public static void main(String[] args) {
        Stu<String, Integer, Character> stu = new Stu<String, Integer, Character>("张晓玲", 28, '女');
        String name = stu.getName();
        Integer age = stu.getAge();
        Character sex = stu.getSex();
        System.out.println("学生信息如下:");
        System.out.println("学生姓名:" + name + ",年龄:" + age + ",性别:" + sex);
    }
}

该程序的运行结果如下:

学生信息如下:
学生姓名:张晓玲,年龄:28,性别:女

在该程序的 Stu 类中,定义了 3 个类型参数,分别使用 N、A 和 S 来代替,同时实现了这 3 个属性的 setter/getter 方法。在主类中,调用 Stu 类的构造函数创建了 Stu 类的对象,同时指定 3 个类型参数,分别为 String、Integer 和 Character。在获取学生姓名、年龄和性别时,不需要类型转换,程序隐式地将 Object 类型的数据转换为相应的数据类型。

泛型方法
到目前为止,我们所使用的泛型都是应用于整个类上。泛型同样可以在类中包含参数化的方法,而方法所在的类可以是泛型类,也可以不是泛型类。也就是说,是否拥有泛型方法,与其所在的类是不是泛型没有关系。

泛型方法使得该方法能够独立于类而产生变化。如果使用泛型方法可以取代类泛型化,那么就应该只使用泛型方法。另外,对一个 static 的方法而言,无法访问泛型类的类型参数。因此,如果 static 方法需要使用泛型能力,就必须使其成为泛型方法。

定义泛型方法的语法格式如下:

[访问权限修饰符][static][final]<类型参数列表>返回值类型方法名([形式参数列表])
例如:
public static List<T> find(Class<T>class,int userId){}

一般来说编写 Java 泛型方法,其返回值类型至少有一个参数类型应该是泛型,而且类型应该是一致的,如果只有返回值类型或参数类型之一使用了泛型,那么这个泛型方法的使用就被限制了。下面就来定义一个泛型方法,具体介绍泛型方法的创建和使用。
例 3
使用泛型方法打印图书信息。定义泛型方法,参数类型使用“T”来代替。在方法的主体中打印出图书信息。代码的实现如下:

public class Test16 {
    public static <T> void List(T book) { // 定义泛型方法
        if (book != null) {
            System.out.println(book);
        }
    }
    public static void main(String[] args) {
        Book stu = new Book(1, "细学 Java 编程", 28);
        List(stu); // 调用泛型方法
    }
}

该程序中的 Book 类为前面示例中使用到的 Book 类。在该程序中定义了一个名称为 List 的方法,该方法的返回值类型为 void,类型参数使用“T”来代替。在调用该泛型方法时,将一个 Book 对象作为参数传递到该方法中,相当于指明了该泛型方法的参数类型为 Book。

该程序的运行结果如下:

1, 细学 Java 编程,28

泛型的高级用法
泛型的用法非常灵活,除在集合、类和方法中使用外,本节将从三个方面介绍泛型的高级用法,包括限制泛型可用类型、使用类型通配符、继承泛型类和实现泛型接口。

  1. 限制泛型可用类型
    在 Java 中默认可以使用任何类型来实例化一个泛型类对象。当然也可以对泛型类实例的类型进行限制,语法格式如下:
class 类名称<T extends anyClass>

其中,anyClass 指某个接口或类。使用泛型限制后,泛型类的类型必须实现或继承 anyClass 这个接口或类。无论 anyClass 是接口还是类,在进行泛型限制时都必须使用 extends 关键字。

例如,在下面的示例代码中创建了一个 ListClass 类,并对该类的类型限制为只能是实现 List 接口的类。
// 限制ListClass的泛型类型必须实现List接口

public class ListClass<T extends List> {
    public static void main(String[] args) {
        // 实例化使用ArrayList的泛型类ListClass,正确
        ListClass<ArrayList> lc1 = new ListClass<ArrayList>();
        // 实例化使用LinkedList的泛型类LlstClass,正确
        ListClass<LinkedList> lc2 = new ListClass<LinkedList>();
        // 实例化使用HashMap的泛型类ListClass,错误,因为HasMap没有实现List接口
        // ListClass<HashMap> lc3=new ListClass<HashMap>();
    }
}

在上述代码中,定义 ListClass 类时设置泛型类型必须实现 List 接口。例如,ArrayList 和 LinkedList 都实现了 List 接口,所以可以实例化 ListClass 类。而 HashMap 没有实现 List 接口,所以在实例化 ListClass 类时会报错。

当没有使用 extends 关键字限制泛型类型时,其实是默认使用 Object 类作为泛型类型。因此,Object 类下的所有子类都可以实例化泛型类对象,如图 1 所示的这两种情况。
在这里插入图片描述

  1. 使用类型通配符
    Java 中的泛型还支持使用类型通配符,它的作用是在创建一个泛型类对象时限制这个泛型类的类型必须实现或继承某个接口或类。

使用泛型类型通配符的语法格式如下:

泛型类名称<? extends List>a = null;

其中,“<? extends List>”作为一个整体表示类型未知,当需要使用泛型对象时,可以单独实例化。

例如,下面的示例代码演示了类型通配符的使用。

A<? extends List>a = null;
a = new A<ArrayList> ();    // 正确
b = new A<LinkedList> ();    // 正确
c = new A<HashMap> ();    // 错误

在上述代码中,同样由于 HashMap 类没有实现 List 接口,所以在编译时会报错。

  1. 继承泛型类和实现泛型接口
    定义为泛型的类和接口也可以被继承和实现。例如下面的示例代码演示了如何继承泛型类。
public class FatherClass<T1>{}
public class SonClass<T1,T2,T3> extents FatherClass<T1>{}

如果要在 SonClass 类继承 FatherClass 类时保留父类的泛型类型,需要在继承时指定,否则直接使用 extends FatherClass 语句进行继承操作,此时 T1、T2 和 T3 都会自动变为 Object,所以一般情况下都将父类的泛型类型保留。

下面的示例代码演示了如何在泛型中实现接口。

interface interface1<T1>{}
interface SubClass<T1,T2,T3> implements
Interface1<T2>{}

集合实现图书信息查询

在图书管理系统中为了方便管理图书,将图书划分为几个类别。每个类别下有很多图书,每本图书都有相对应的类别,这就具备了一对多的关系映射,即一个类别对应多本图书。

在这种情况下就可以使用 Map 映射来存储类别和图书信息,其键为 Category(类别)类型,值为 List 类型(Book 类为图书类),然后使用嵌套循环遍历输出每个类别所对应的多个图书信息。具体的实现步骤如下。

1)创建表示图书类别的 Category 类,在该类中有两个属性:id 和 name,分别表示编号和类别名称,并实现了它们的 setXxx() 和 getXxx() 方法,具体内容如下:

public class Category {
    private int id; // 类别编号
    private String name; // 类别名称
    public Category(int id, String name) {
        this.id = id;
        this.name = name;
    }
    public String toString() {
        return "所属分类:" + this.name;
    }
    // 上面两个属性的setXxx()和getXxx()方法
    public int getId() {
        return id;
    }
    public void setId(int id) {
        this.id = id;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
}

2)创建表示图书明细信息的 BookInfo 类,在该类中包含 5 个属性:id、name、price、author 和 startTime,分别表示图书编号、名称、价格、作者和出版时间,同样实现了它们的 setXxx() 和 getXxx() 方法,具体内容如下:

public class BookInfo {
    private int id; // 编号
    private String name; // 名称
    private int price; // 价格
    private String author; // 作者
    private String startTime; // 出版时间
    public BookInfo(int id, String name, int price, String author, String startTime) {
        this.id = id;
        this.name = name;
        this.price = price;
        this.author = author;
        this.startTime = startTime;
    }
    public String toString() {
        return this.id + "\t\t" + this.name + "\t\t" + this.price + "\t\t" + this.author + "\t\t" + this.startTime;
    }
    // 上面5个属性的 setXxx() 和 getXxx() 方法
    public int getId() {
        return id;
    }
    public void setId(int id) {
        this.id = id;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public int getPrice() {
        return price;
    }
    public void setPrice(int price) {
        this.id = price;
    }
    public String getAuthor() {
        return author;
    }
    public void setAuthor(String author) {
        this.author = author;
    }
    public String getStartTime() {
        return startTime;
    }
    public void setStartTime(String startTime) {
        this.startTime = startTime;
    }
}

3)创建 CategoryDao 类,在该类中定义一个泛型的 Map 映射,其键为 Category 类型的对象,值为 List 类型的对象,并定义 printCategoryInfo() 方法,用于打印类别和图书明细信息。具体代码如下:

public class CategoryDao {
    // 定义泛型Map,存储图书信息
    public static Map<Category, List<BookInfo>> categoryMap = new HashMap<Category, List<BookInfo>>();
    public static void printDeptmentInfo() {
        for (Category cate : categoryMap.keySet()) {
            System.out.println("所属类别:" + cate.getName());
            List<BookInfo> books = categoryMap.get(cate);
            System.out.println("图书编号\t\t图书名称\t\t图书价格\t\t图书作者\t\t出版时间");
            for (int i = 0; i < books.size(); i++) {
                BookInfo b = books.get(i); // 获取图书
                System.out.println(b.getId() + "\t\t" + b.getName() + "\t\t" + b.getPrice() + "\t\t" + b.getAuthor()
                        + "\t\t" + b.getStartTime());
            }
            System.out.println();
        }
    }
}

4)创建测试类 Test17,在该类中定义 4 个 Deptment 对象和 8 个 People 对象,并将 8 个 People 对象分成 4 组,存储到 4 个 List 集合中,然后将 4 个 Deptment 对象和 4 个 List 集合按照——对应的关系存储到 DeptmentDao 类中的 peoplesMap 映射中。最后调用 DeptmentDao 类中的 printDeptmentInfo() 方法打印类别及对应的图书信息。具体的代码如下:

public class Test17 {
    public static void main(String[] args) {
        Category category1 = new Category(1, "数据库"); // 创建类别信息
        Category category2 = new Category(2, "程序设计"); // 创建类别信息
        Category category3 = new Category(3, "平面设计"); // 创建类别信息
        BookInfo book1 = new BookInfo(1, "细说 Java 编程", 25, "张晓玲", "2012-01-01"); // 创建图书信息
        BookInfo book2 = new BookInfo(2, "影视后期处理宝典", 78, "刘水波", "2012-10-05"); // 创建图书信息
        BookInfo book3 = new BookInfo(3, "MySQL 从入门到精通", 41, "王志亮", "2012-3-2"); // 创建图书信息
        BookInfo book4 = new BookInfo(4, "Java 从入门到精通", 27, "陈奚静", "2012-11-01"); // 创建图书信息
        BookInfo book5 = new BookInfo(5, "SQL Server 一百例", 68, "张晓玲", "2012-01-01"); // 创建图书信息
        List<BookInfo> pList1 = new ArrayList<BookInfo>(); // 向类别 1 添加图书
        pList1.add(book1);
        pList1.add(book4);
        List<BookInfo> pList2 = new ArrayList<BookInfo>(); // 向类别 2 添加图书
        pList2.add(book3);
        pList2.add(book5);
        List<BookInfo> pList3 = new ArrayList<BookInfo>(); // 向类别 3 添加图书
        pList3.add(book2);
        CategoryDao.categoryMap.put(category1, pList1);
        CategoryDao.categoryMap.put(category2, pList2);
        CategoryDao.categoryMap.put(category3, pList3);
        CategoryDao.printDeptmentInfo();
    }
}

在该程序中,使用了泛型 List 和泛型 Map 分别存储图书类别和特定类别下的图书明细信息。从中可以看出使用泛型不仅减少了代码的编写量,也提高了类型的安全性。

Java枚举(enum)详解

Java声明枚举类型、枚举(enum)类、EnumMap 与 EnumSet

枚举是一个被命名的整型常数的集合,用于声明一组带标识符的常数。枚举在曰常生活中很常见,例如一个人的性别只能是“男”或者“女”,一周的星期只能是 7 天中的一个等。类似这种当一个变量有几种固定可能的取值时,就可以将它定义为枚举类型。

声明枚举
声明枚举时必须使用 enum 关键字,然后定义枚举的名称、可访问性、基础类型和成员等。枚举声明的语法如下:

enum-modifiers enum enumname:enum-base {
    enum-body,
}

其中,enum-modifiers 表示枚举的修饰符主要包括 public、private 和 internal;enumname 表示声明的枚举名称;enum-base 表示基础类型;enum-body 表示枚举的成员,它是枚举类型的命名常数。

任意两个枚举成员不能具有相同的名称,且它的常数值必须在该枚举的基础类型的范围之内,多个枚举成员之间使用逗号分隔

提示:如果没有显式地声明基础类型的枚举,那么意味着它所对应的基础类型是 int。

例 1
下面代码定义了一个表示性别的枚举类型 SexEnum 和一个表示颜色的枚举类型 Color。

public enum SexEnum {
    male,female;
}
public enum Color {
    RED,BLUE,GREEN,BLACK;
}

枚举类
Java 中的每一个枚举都继承自 java.lang.Enum 类。当定义一个枚举类型时,每一个枚举类型成员都可以看作是 Enum 类的实例,这些枚举成员默认都被 final、public, static 修饰,当使用枚举类型成员时,直接使用枚举名称调用成员即可

所有枚举实例都可以调用 Enum 类的方法,常用方法如表 1 所示。

在这里插入图片描述

例 2
创建一个示例,调用valueOf() 方法获取枚举的一个成员,再调用 compareTo() 方法进行比较,并输出结果。具体实现代码如下:

public class TestEnum {
    public enum Sex {
        // 定义一个枚举
        male,female;
    }
    public static void main(String[] args) {
        compare(Sex.valueOf("male"));    // 比较
    }
    public static void compare(Sex s) {
        for(int i = 0;i < Sex.values().length;i++) {
            System.out.println(s + "与" + Sex.values()[i] + "的比较结果是:" + s.compareTo(Sex.values()[i]));
        }
    }
}

上述代码中使用 Sex.valueOf(“male”) 取出枚举成员 male 对应的值,再将该值与其他枚举成员进行比较。最终输出结果如下:

male与male的比较结果是:0
male与female的比较结果是:-1

为枚举添加方法

Java 为枚举类型提供了一些内置的方法,同时枚举常量也可以有自己的方法。此时要注意必须在枚举实例的最后一个成员后添加分号,而且必须先定义枚举实例。enum 还可以跟 Class 类一样覆盖基类的方法。下面示例代码创建的 Color 枚举类型覆盖了 toString() 方法。

public class Test {
    public enum Color {
        RED("红色",1),GREEN("绿色",2),WHITE("白色",3),YELLOW("黄色",4);
        // 成员变量
        private String name;
        private int index;
        // 构造方法
        private Color(String name,int index) {
            this.name = name;
            this.index = index;
        }
        // 覆盖方法
        @Override
        public String toString() {
            return this.index + "-" + this.name;
        }
    }
    public static void main(String[] args) {
        System.out.println(Color.RED.toString());    // 输出:1-红色
    }
}

EnumMap 与 EnumSet
EnumMap 类
HashMap 只能接收同一枚举类型的实例作为键值,并且由于枚举类型实例的数量相对固定并且有限,所以
EnumMap 使用数组来存放与枚举类型对应的值,使得 EnumMap 的效率非常高。
EnumSet 类
EnumSet 是枚举类型的高性能 Set 实现,它要求放入它的枚举常量必须属于同一枚举类型。

参考内容

http://c.biancheng.net/java/110/

发布了63 篇原创文章 · 获赞 17 · 访问量 4076

猜你喜欢

转载自blog.csdn.net/Bonport/article/details/104947498