重学Java - 进阶(2/2)

引言

【集合】【Collection】【Map】【泛型】【IO】【反射】【Java 8 新特性】【Stream】【Optional】

【因内容较多,已拆分为两篇博文,此为篇二】
本篇博文为 Java 的一些高级特性的常见概念及相关细节梳理,意在重学 Java 查漏补缺。
博文随时会进行更新,补充新的内容并修正错漏,该系列博文旨在帮助自己巩固扎实 Java 基础。
毕竟万丈高楼,基础为重,借此督促自己时常温习回顾。

一、集合

集合、数组都是对多个数据进行存储(内存层面)操作的结构,即容器

数组在内存存储方面的特点:

  • 数组初始化后长度确定
  • 数组声明的类型就决定了进行元素初始化时的类型

数组在存储数据方面的弊端:

  • 数组初始化后长度不可变,不便于拓展
  • 数组中提供的属性和方法不便于进行添加、删除、插入等操作,且效率不高
    无法直接获取已存储元素的个数
  • 数组存储的数据是有序(存入顺序)的、可重复的 —— 存储数据的特点单一

集合类可用于存储数量不等的多个对象,还可用于保存具有映射关系的关联数组

Java 集合可分为 Collection 和 Map 两种体系:

  • Collection 接口:单列数据,定义了存取一组对象的方法的集合
    • List:元素有序、可重复的集合
      • ArrayList、LinkedList、Vector
    • Set:元素无序、不可重复的集合
      • HashSet、LinkedHashSet、TreeSet
  • Map 接口:双列数据,保存具有映射关系 “key-value” 对 的集合
    • HashMap、LinkedHashMap、TreeMap、Hashtable、Properties

二、Collection 接口

常用方法:(例:Collection collectionVar = new ArrayList(); )

  • add(Object e):将元素 e 添加到集合(collectionVar)中
  • size():获取集合(collectionVar)中的元素个数
  • addAll(Collection collection):将集合(collection)中的元素添加到当前集合(collectionVar)中
  • isEmpty():判断当前集合(collectionVar)是否为空
  • clear():清空集合(collectionVar)中的元素
  • contains(Object obj):判断当前集合(collectionVar)中是否包含 obj
    • 向 Collection 接口实现类的对象中添加数据 obj 时,要求 obj 所在类要重写 equals() 方法
    • 判断时会调用 obj 对象所在类的 equals() 方法
  • containsAll(Collection collection):判断集合(collection)中的所有元素是否都存在于当前集合(collectionVar)中
  • remove(Object obj):从当前集合(collectionVar)中移除 obj 元素
  • removeAll(Collection collection):从当前集合(collectionVar)中移除指定集合(collection)中所有的元素(求差集)
  • retainAll(Colllection collection):保留当前集合(collectionVar)与指定集合(collection)中所有相同的元素(求交集)
  • equals(Object obj):判断当前集合(collectionVar)与指定 obj 中元素是否相同
    • 针对有序集合与元素顺序也相关
    • 针对无序集合元素顺序无关
  • hashCode():返回当前对象的哈希值
  • 集合 -> 数组:toArray()
    • 数组 -> 集合:调用 Arrays 类的静态方法 asList()
      • 注意:Arrays.asList(new int[]{1, 2, 3}) 与 Arrays.asList(new Integer[]{1, 2, 3}) 不同,前者结果为包含 1 个元素(int 数组对象)的集合

2.1、Iterator 迭代器接口

Iterator 对象称为迭代器(设计模式的一种),主要用于遍历 Collection 集合中的元素

  • GOF 给迭代器模式的定义为:提供一种方法访问一个容器(container)对象中各个元素,而又不需要暴露该对象的内部细节。迭代器模式就是为容器而生
  • Iterator 仅用于遍历集合,Iterator 本身并不提供承载对象的能力。如果需要创建 Iterator 对象,则必须有一个被迭代的集合
  • 集合对象每次调用 iterator() 方法都得到一个全新的迭代器对象,默认游标都在集合的第一个元素之前
  • Iterator 内部定义了 remove() 方法,可以在遍历的时候删除集合中的元素
    • 此方法不同于集合直接调用 remove()
    • 如果还未调用 next() 或在上一次调用 next() 之后已经调用了 remove(),再次调用 remove() 将会抛出异常:IllegalStateException
  • 常用方法:
    • hasNext():判断是否还有下一个元素
    • next():指针下移并将下移后集合位置上的元素返回
    • 建议在调用 next() 方法之间必须要调用 hasNext() 进行检测。若不进行检测,且下一条记录无效,直接调用 next() 会抛出异常:NoSuchElementException

2.2、foreach 循环遍历

Java5.0 提供了 foreach 循环迭代访问 Collection 和数组

  • 遍历操作不需要获取 Collection 或数组的长度,无需使用索引访问元素
  • 遍历结合的底层调用 Iterator 完成操作
  • 使用方法:
    • for(集合元素的类型 局部变量 : 集合对象){}
    • for(数组元素的类型 局部变量 : 数组对象){}

2.3、List 接口

List 接口的常用实现类: ArrayList、LinkedList 和 Vector

ArrayList、LinkedList 和 Vector 的对比:

  • 相同点:三个类都实现了 List 接口,存储数据的特点相同:存储有序的、可重复的数据

  • 不同点:

    • ArrayList:线程不安全,效率高;底层使用 Object[] elementData 存储
    • LinkedList:对于频繁的插入、删除操作使用此类效率比 ArrayList 高:底层使用双线链表存储
    • Vector:线程安全,效率低;底层使用 Object[] elementData 存储
    • Vector 在扩容方面默认扩容为原来数组长度的 2 倍

2.3.1、ArrayList

ArrayList 源码分析(JDK7):

  • ArrayList list = new ArrayList();
    • 底层创建长度为 10 的 Object[] 数组 elementData
  • list.add(1);
    • elementData[0] = new Integer(1);
  • list.add(2);list.add(3); 以此类推 list.add(11);
    • list.add(11); 导致底层 elementData 数组容量不足,则扩容
    • 默认情况下,扩容为原容量 1.5 倍,同时将原数组中元素复制到新数组中
  • 注:建议开发中使用带参的构造器:ArrayList list = new ArrayList(int capacity);

ArrayList 源码分析(JDK8):

  • ArrayList list = new ArrayList();
    • 底层 Object[] elementData 初始化为 {},并没有创建长度为 10 的数组
  • list.add(1);
    • 第一次调用 add() 时,底层才创建了长度为 10 的数组,并将数据添加到 elementData[0]
  • 后续添加与扩容操作与 JDK7 一致

注: JDK7 中 ArrayList 的对象创建类似于单例模式中的饿汉式;JDK8 中 ArrayList 的对象创建类似于单例模式的懒汉式,延迟了数组的创建,节省内存

2.3.2、LinkedList

LinkedList:双向链表,内部没有声明数组,而是定义了 Node 类型的 first 和 last 用于记录首、尾元素

  • 内部类 Node 作为 LinkedList 中保存数据的基本结构
  • Node 除保存数据,还定义了两个变量:
    • prev 变量记录前一个元素的位置
    • next 变量记录后一个元素的位置
private static class Node<E> {
    E item;
    Node<E> next;
    Node<E> prev;
    
    Node(Node<E> prev, E element, Node<E> next) {
        this.item = element;
        this.next = next;
        this.prev = prev;
    }
}

LinkedList 源码分析:

  • LinkedList list = new LinkedList();
    • 内部声明了 Node 类型的 first 和 last 属性,默认值为 null
  • list.add(1);
    • 创建 Node 对象,将数据封装到 Node 中

2.3.3、List 接口方法

List 除了从 Collection 继承的方法外还增加了一些根据索引来操作集合元素的方法

  • void add(int index, Object obj):在 index 位置插入 obj 元素
  • boolean addAll(int index, Collection eles):从 index 位置开始将 eles 中的所有元素添加进来
  • Object get(int index):获取指定 index 位置的元素
  • int indexOf(Object obj):返回 obj 在集合中首次出现的位置
  • int lastIndexOf(Object obj):返回 obj 在集合中最后一次出现的位置
  • Object remove(int index):移除指定 index 位置的元素,并返回此元素
    • 注意区分 remove(int index) 和 remove(Object obj)
  • Object set(int index, Object obj):设置指定 index 位置的元素 为 obj
  • List subList(int fromIndex, int toIndex):返回从 fromIndex 到 toIndex 位置的子集合

2.4、Set 接口

Set 接口的常用实现类: HashSet、LinkedSet、TreeSet

HashSet、LinkedSet 和 TreeSet的对比:

  • 相同点:三个类都实现了 Set 接口,存储数据的特点相同:存储无序的、不可重复的数据

  • 不同点:

    • HashSet:线程不安全;可以存储 null
    • LinkedSet:作为 HashSet 的子类:遍历其内部数据时,可以按照添加顺序遍历
      • 对于频繁的遍历操作,LinkedHashSet 效率高于 HashSet
    • TreeSet:可以按照添加对象的指定属性进行排序

Set 接口中没有额外定义新的方法,使用的都是 Collection 中声明过的方法

无序性: 不等于随机性

  • 以 HashSet 为例:存储的数据在底层数组中并非按照数组索引的顺序添加,而是根据 hash 值决定的

不可重复性: 相同的元素只能添加一个(保证添加的元素按照 equal() 方法判断时返回结果为 false)

2.4.1、HashSet

HashSet 是 Set 接口的典型实现,大多数时候使用 Set 集合时都使用该实现类

  • HashSet 按 Hash 算法来存储集合中的元素,因此具有较好的存取、查找、删除性能

HashSet 底层结构: 数组 + 链表

  • 数组初始容量为 16,当使用率超过其 0.75 倍,则扩容为原来的 2 倍

HashSet 的特点:

  • 不能保证元素的排列顺序
  • HashSet 不是线程安全的
  • 集合元素可以是 null

对于存放在 Set 容器中的对象,对应的类必须重写 equals() 方法和 hashCode(Object obj) 方法,以实现对象的相等判断规则(相等的对象必须具有相等的散列码)

  • 重写的原则:
    • 通常参与计算 hashCode 的对象的属性也应该参与到 equals() 中进行计算
    • 在程序运行时,同一个对象多次调用 hashCode() 方法应该返回相同的值
    • 当两个对象的 equals() 方法比较返回 true 时,这两个对象的 hashCode() 方法的返回值也应相等
    • 对象中用作 equals() 方法比较的 Field 都应该用来计算 hashCode 值

Eclipse 与 IntelliJ IDEA 工具中 hashCode() 的重写,可以调用工具自动完成,这里有个数字 31

  • 选择系数的时候要选择尽量大的系数。因为如果计算出来的 hash 地址越大,所谓的 “冲突” 就越少,查找起来效率也会提高(减少冲突)
  • 并且 31 只占用 5bits,相乘造成的数据溢出的概率较小
  • 31 可以由 i*31==(i<<5)-1 来表示(提高算法效率)
  • 31 是一个素数,素数作用就是如果用一个数字乘这个素数,那么结果只能被这个素数本身和被乘数以及 1 整除(减少冲突)

向 HashSet 中添加元素的过程:

  • 向 HashSet 中添加新元素,首先调用新元素所在类的 hashCode() 方法,计算新元素的 hash 值,此 hash 值接着通过某种散列函数计算出在 HashSet 底层数组中的存储位置(索引位置),接着判断数组此位置上是否已经有元素:
    • 若此位置没有其他元素,则新元素添加成功(情况 A)
    • 若此位置已有元素(或以链表形式存在的多个元素),则比较新元素与已有元素的 hash 值:
      • 若 hash 值不相同,则新元素添加成功(情况 B)
      • 若 hash 值相同,进而需要调用新元素所在类的 equals() 方法:
        • equals() 返回 false,则新元素添加成功(情况 C)
        • equals() 返回 true,则新元素添加失败

对于新元素添加成功的情况而言:B 与 C 的情况下,新元素与已经存在的指定索引位置上的数据以链表的方式存储

  • JDK7:新元素存放在数组中,指向原来的元素
  • JDK8:原来的元素在数组中,指向新元素

向 HashSet 中添加元素的过程:

  • 当向 HashSet 集合中存入一个元素时,HashSet 会调用该对象的 hashCode() 方法来得到该对象的 hashCode 值,然后根据 hashCode 值,通过某种散列函数决定该对象在 HashSet 底层数组中的存储位置。(这个散列函数会与底层数组的长度相计算得到在数组中的下标,并且这种散列函数计算还尽可能保证能均匀存储元素,越是散列分布,该散列函数设计得越好)
  • 如果两个元素的 hashCode 值相等,会再继续调用 equals() 方法,如果 equals() 方法结果为 true,添加失败;如果为 false,那么会保存该元素,但是该数组的位置已经有元素,因此会通过链表的方式继续链接
  • 如果两个元素的 equals() 方法返回 true,但它们的 hashCode 值不相等,HashSet 将会把它们存储在不同的位置,依然可以添加成功

2.4.2、LinkedHashSet

LinkedHashSet 是 HashSet 的子类

  • LinkedHashSet 根据元素的 hashCode 值来决定元素的存储位置,但它同时使用双向链表维护元素的次序,这使得元素看起来是以插入顺序保存的
    • 在添加数据的同时,每个数据还维护了两个引用,记录此数据前一个数据和后一个数据
  • LinkedHashSet 的插入性能略低于 HashSet,但在迭代访问 Set 集合中全部元素时有很好的性能

2.4.3、TreeSet

TreeSet 是 SortedSet 接口的实现类,TreeSet 可以确保集合元素处于排序状态

  • TreeSet 底层使用红黑树结构存储数据
  • TreeSet 两种排序方法
    • (默认采用)自然排序 Comparable
      • 比较两个对象是否相同的标准为:compareTo() 返回 0。(不再是 equals())
    • (传入参数)定制排序 Comparator
      • 比较两个对象是否相同的标准为:compare() 返回 0。(不再是 equals())
  • 向 TreeSet 中添加的数据要求是相同类的对象

三、Map 接口

Map:双列数据,存储 key-value对 数据

  • HashMap:线程不安全,效率高;key 和 value 可以为 null
    • LinkedHashMap:保证在遍历时可以按照添加的顺序
      在原有的 HashMap 底层结构基础上,添加了一对指针,指向前一个和后一个元素
      对于频繁的遍历操作,此类执行效率高于 HashMap
  • TreeMap:保证按照添加的 key-value对 进行排序,实现排序遍历。(考虑 key 的自然排序或定制排序)
  • Hashtable:线程安全,效率低;key 和 value 不能为 null
    • Properties:常用来处理配置文件。key 和 value 都是 String 类型

3.1、Map 结构

  • Map 中的 key:无序、不可重复,使用 Set 存储(HashMap:key 所在的类要重写 equals() 方法和 hashCode() 方法)
  • Map 中的 value:无序、可重复,使用 Collection 存储(value 所在的类要重写 equals() 方法)
  • 一个键值对:key-value 构成一个 Entry 对象
  • Map 中的 Entry 的对象(entry):无序、不可重复,使用 Set 存储

3.2、Map 中的常用方法

  • 添加、删除、修改操作:
    • Object put(Object key, Object value):将指定 key-value 添加到(或修改 value)当前 map 对象中
    • void putAll(Map m):将 m 中的所有 key-value对 存放到当前 map 中
    • Object remove(Object key):移除指定 key 的 key-value对,并返回 value
    • void clear():清空当前 map 中的所有数据
  • 查询操作:
    • Objec get(Object key):获取指定 key 对应的 value
    • boolean containsKey(Object key):判断是否包含指定的 key
    • boolean containsValue(Object value):判断是否包含指定的 value
    • int size():返回 map 中 key-value对 的个数
    • boolean isEmpty():判断当前 map 是否为空
    • boolean equals(Object obj):判断当前 map 和 obj 是否相等
  • 元视图操作:
    • Set keySet():返回所有 key 构成的 Set 集合
    • Collection values():返回所有 value 构成的 Collection 集合
    • Set entrySet():返回所有 key-value对 构成的 Set 集合

3.2.1、HashMap

HashMap 的底层实现原理(JDK7):

  • HashMap map = new HashMap(); :底层创建长度为 16 的一维数组 Entry[] table
  • map.put(key, value); :首先调用 key 所在类的 hashCode() 方法计算 key 的 hash 值,此 hash 值经过某种算法计算得到 Entry 数组中的存放位置
    • 若此位置数据为空,此时 key-value 添加成功(情况 A)
    • 若此位置数据不为空(存在一个或多个数据(以链表形式存在)),比较 key 与已存在的一个或多个数据的 hash 值
      • 若 key 的 hash 值与已存在的数据的 hash 值都不相同,此时 key-value 添加成功(情况 B)
      • 若 key 的 hash 值与已存在的某一个数据的 hash 值相同,则调用 key 所在类的 equals() 与这个原数据的 key 做比较
        • 若 equals() 返回 false,此时 key-value 添加成功(情况 C)
        • 若 equals() 返回 true,则使用 value 替换原数据的 value

对于 key-value 添加成功的情况而言:B 与 C 的情况下,key-value 与原数据以链表的方式存储

在添加过程中涉及到扩容问题,当超出临界值时(且要存放的位置非空)默认扩容为 2 倍

HashMap 在 JDK8 与 JDK7 底层实现方面的区别(针对 JDK8 而言):

  • new HashMap():底层没有直接创建一个长度为 16 的数组
  • JDK8 的底层数组是 Node[],不再是 Entry[]
  • 首次调用 put() 方法时,底层创建长度为 16 的数组
  • JDK7 底层实现结构:数组 + 链表;JDK8 底层实现结构:数组 + 链表 + 红黑树
    • 当数组的某一索引位置上的元素以链表形式存在的数据个数大于 8 且当前数组的长度大于 64 则此时索引位置上的所有数据改为使用红黑树存储

HashMap 源码中的主要常量:

  • DEFAULT_INITAL_CAPACITY:HashMap 的默认容量
  • MAXIMUM_CAPACITY:HashMap 的最大支持容量 2^30
  • DEFAULT_LOAD_FACTOR:HashMap 的默认负载因子
  • TREEIFY_THRESHOLD:Bucket 中链表长度大于该默认值,转化为红黑树
  • UNTREEIFY_THRESHOLD:Bucket 中红黑树存储的 Node 小于该默认值,转化为链表
  • MIN_TREEIFY_CAPACITY:Bucket 中的 Node 被树化时最小的 hash 表容量
    当 Bucket 中 Node 的数量大到需要转红黑树时,若 hash 表容量小于 MIN_TREEIFY_CAPACITY,此时应执行 resize 扩容这个 MIN_TREEIFY_CAPACITY 的值至少是 TREEIFY_THRESHOLD 的 4 倍
  • table:存储元素的数组,总是 2 的 n 次幂
  • entrySet:存储具体元素的集合
  • size:HashMap 中存储的键值对的数量
  • modeCount:HashMap 扩容和结构改变的次数
  • threshold:扩容的临界值(容量 * 负载因子)
  • loadFactor:负载因子

负载因子值的大小,对 HashMap 的影响:

  • 负载因子的大小决定了 HashMap 的数据密度
  • 负载因子越大密度越大,发生碰撞的几率越高,数组中的链表越容易长,造成查询或添加时的比较次数增多,性能会下降
  • 负载因子越小,就越容易触发扩容,数据密度也越小,即发生碰撞的几率越小,数组中的链表也就越短,查询和添加时比较的次数也越少,性能会更高。但是会浪费一定的内存空间,而且经常扩容也会影响性能,因此建议初始化预设大一点的空间
  • 按照其他语言的参考及研究经验,会考虑将负载因子设置为 0.7 ~ 0.75,此时平均检索长度接近于常数

3.2.2、LinkedHashMap

HashMap 中的内部类:Node

static class Node<K,V> implements Map.Entry<K,V> {
    final int hash;
    final K key;
    V value;
    Node<K,V> next;
}

LinkedHashMap 中的内部类:Entry

static class Entry<K,V> extends HashMap.Node<K,V> {
    Entry<K,V> before, after;  // 实现记录添加的元素的先后顺序
    Entry(int hash, K key, V value, Node<K,V> next) {
        super(hash, key, value, next);
    }
}

3.2.3、Properties

Properties 类是 Hashtable 的子类,该对象用于处理配置文件

  • 配置文件中的 key、value 都是字符串类型,所以 Properties 中的 key、value 都是字符串类型
  • 存取数据时,建议使用 setProperty(String key, String value) 方法和 getProperty(string key) 方法
Properties pros = new Properties();
pros.load(new FileInputStream("jdbc.properties"));
String db = pros.getProperty("db");

3.3、Collections 工具类

Collections 是一个操作 List、Set 和 Map 等集合的工具类

  • Collections 中提供了一系列静态方法对集合元素进行排序、查询和修改等操作,还提供了对集合对象设置不可变、对集合对象实现同步控制等方法

3.3.1、Collections 常用方法:同步控制

Collections 类中提供了多个 synchronizedXxx() 方法,该方法可以将指定集合包装为线程同步的集合,从而可以解决多线程并发访问集合时的线程安全问题

  • synchronizedCollection(Collection c)
  • synchronizedList(List list)
  • synchronizedMap(Map m)
  • synchronizedSet(Set, s)
  • synchronizedSortedMap(SortedMap, m)
  • synchronizedSortedSet(Sorted s)

3.3.2、Collections 常用方法

排序操作(static 方法):

  • reverse(List list):反转 list 中元素的顺序
  • shuffle(List list):对 list 集合元素进行随机排序
  • sort(List list):对 list 集合元素进行自然排序(升序)
  • sort(List list, Comparator comparator):根据指定的 comparator 对 list 集合元素进行定制排序
  • swap(List list, int i, int j):将指定 list 集合中 i 处元素和 j 处元素进行交换

查找、替换:

  • Object max(Collection collection):根据元素自然排序结果,返回给定集合中的最大元素
  • Object max(Collection collection, Comparator comparator):根据 comparator 指定的排序规则,返回给定集合中的最大元素
  • Object min(Collection collection):根据元素自然排序结果,返回给定集合中的最小元素
  • Object min(Collection collection, Comparator comparator):根据 comparator 指定的排序规则,返回给定集合中的最小元素
  • int frequency(Collection collection, Object obj):返回指定集合中指定元素的出现次数
  • void copy(List, dest, List src):将 src 中的内容复制到 dest 中
  • boolean raplaceAll(List list, Object oldVal, Object newVal):使用新值替换 List 集合中所有的旧值

四、泛型(Generic)

集合容器类在设计阶段(声明阶段)不能确定这个容器到底实际存的时什么类型的对象,所以在 JDK5 之前只能把元素类型设计为 Object。JDK5 之后使用泛型来解决

  • 因为这个时候除了元素的类型不确定,其他的部分是确定的,如关于这个元素如何保存,如何管理等。
  • 此次此时将元素的类型设计成一个参数,该类型参数即泛型

4.1、泛型的概念

允许在定义类、接口时通过一个标识表示类中某个属性的类型或者是某个方法的返回值以及参数类型

  • 这个类型参数在使用时(如:继承或实现这个接口,用这个类型声明变量、创建对象时)确定(即传入实际的类型参数,也称为类型实参)

从 JDK5 后,Java 引入了 “参数化类型(Parameterized type)” 的概念,允许在创建集合时再指定集合元素的类型

  • 如:List,表明该 List 只能保存字符串类型的对象

4.1.1、在集合中使用泛型

JDK5 改写了集合框架中的全部接口和类,为这些接口、类增加了泛型支持,从而可以在声明集合变量、创建集合对象时传入类型实参

  • 在实例化集合类时,可以指明具体的泛型类型
  • 在集合类或接口中凡是定义类或接口时,内部结构(如:方法、构造器、属性等)使用到类的泛型的位置,都指定为实例化的泛型类型
    如 add(E e) 实例化后 add(Integer e)
  • 注意:泛型的类型必须是类,不能是基本数据类型。需要用到基本数据类型的位置,可用包装类代替
  • 若实例化时没有指明泛型的类型,默认类型为 java.lang.Object 类型

4.2、自定义泛型结构

4.2.1、自定义泛型类、泛型接口

  • 泛型类可能有多个参数,此时可将多个参数放在一对尖括号内 “<>”,如

  • 泛型类的无参构造器与普通无参构造器一致:public GenericClass(){}

  • 实例化后,操作原来泛型位置的结构必须与指定的泛型类型一致

  • 泛型不同的引用不能相互赋值

    • 尽管在编译时 ArrayList 和 ArrayList 是两种类型,但在运行时只有一个 ArrayList 被加载到 JVM 中
  • 泛型如果不指定将被擦除,泛型对应的类型均按照 Object 处理,但不等价于 Object

    • 建议:泛型一旦使用便要从一而终
  • 若泛型结构是一个接口或抽象类,则不能创建泛型类的对象

  • JDK7 新增类型推断,泛型的简化操作:ArrayList list = new ArrayList<>();

  • 泛型的指定中不能使用基本数据类型,可以使用包装类替换

  • 在类/接口上声明的泛型,在本类或本接口中即代表某种类型,可作为非静态属性的类型、非静态方法的参数类型、非静态方法的返回值类型。但在静态方法中不能使用类的泛型(静态方法的加载要早于类的初始化)

  • 异常类不能是泛型的

  • 不能使用 new E[]

    • 但可使用 E[] elements = (E[])new Object[capacity];
      参考:ArrayList 源码中声明:Object[] elementData,而非泛型参数类型数组
  • 父类有泛型,子类可以选择保留泛型也可以选择指定泛型类型

    • 子类不保留父类的泛型:按需实现
      • 没有类型 -> 擦除
      • 具体类型
    • 子类保留父类的泛型:泛型子类
      • 全部保留
      • 部分保留
    • 子类除了指定或保留父类的泛型,还可以增加自己的泛型

4.2.2、自定义泛型方法

方法也可被泛型化,不管此时定义在其中的类是不是泛型类。在泛型方法中可以定义泛型参数,此时参数的类型就是传入数据的类型

泛型方法的格式: 访问权限 <泛型> 返回类型 方法名(泛型标志 参数名称) 抛出的异常

public class DAO {
    public <E> E get(int id, E e) {
        E result = null;
        return result;
    }
}

泛型方法: 在方法中出现泛型结构,泛型参数与类的泛型参数没有任何关系

  • 泛型方法所属的类是不是泛型类都无关
  • 泛型方法可以声明为静态的
    • 泛型参数是在调用方法时确定的,并非在实例化类时确定

4.3、泛型在继承方面的体现

虽然类 A 是类 B 的父类,但是 E 和 E 二者不具备子、父类关系,二者是并列关系

  • 区别:类 A 是类 B 的父类,A 是 B 的父类

4.4、通配符

泛型可以使用通配符 “?”,比如 List,Map