Java 基础要点之集合类

287 阅读10分钟

Collection 结合

基本概念

  • java.util.Collection接口是List接口,Queue接口以及Set接口的父接口,因此该接口里定义的方法既可用于操作List集合,也可用于操作Queue集合和Set集合 常用方法 | 方法 | 功能 | | ---------------------------------------------- | ---------------------------------------------------- | | boolean add(E e) | 向集合中添加对象 | | boolean addAll(Collection<? extends E> c) | 用于将指定参数集合c中的所有元素添加到集合中 | | boolean contains(Object o) | 判断是否包含指定对象(自己编写的类需要重写equals方法) | | boolean containsAll(Collection<? extends E> c) | 判断是否包含参数集合C中所有的对象 | | boolean retainAll(Collection<? extends E> c) | 保存当前集合与参数集合有交集的所有对象 | | boolean remove(Object o) | 从集合中删除对象 | | boolean removeAll(Collection<? extends E> c) | 删除包含参数集合C中所有的对象 | | void clear() | 清空集合 | | int size() | 返回包含对象的个数 | | boolean isEmpty() | 判断集合是否为空 | | boolean equals(Object o) | 判断是否相等 | | int hashCode() | 获取哈希码值 | | Object[] toArray() | 将集合转化为数组 | | Iterator iterator() | 获取当前集合的迭代器 |

考点

Collection c1 = new ArrayList();
c1.add(1);
c1.add(2);
Collection c2 = new ArrayList();
c2.add(1);
c2.add(2);
c2.add(3);
boolean contains = c2.contains(c1); 	//false  c2中的元素没有c1这个对象
boolean b = c2.containsAll(c1);		//true	 c2中的元素包含了c1中的元素(1,2)
//======================================================================
boolean remove = c2.remove(c1);    	//false。C2中的元素不包含C1这个对象,所以移除失败
boolean removeAll = c2.removeAll(c1);  	//true,C2中有C1中的元素(1,2),c2把1和2移除

List集合

基本概念

  • java.util.List集合是Collection集合的子集合,该集合总允许有重复的元素并且有先后放入次序
  • 该集合的主要实现类有:ArrayList、LinkedList、Stack、Vector类
  • 其中ArrayList类的底层是采用动态数组进行数据管理,支持下标访问。增删元素不方便
  • 其中LinkedList类的底层是采用双向链表进行数据管理,访问不方便,增删元素方便
  • 可以认为ArrayList和LinkedList的方法在逻辑上完全一样,只是在性能上有一定差距,ArrayList更适合随机访问,而LinkedList更适合插入和删除:在性能要求不是特格苛刻的情形下可以忽略这个差别
  • 其中Stack类的底层是采用动态数组进行数据管理的,该类主要用于描述一种先进后出特征的数据结构。叫做栈(LIFO,last in first out)
  • 其中Vecto类的底层是采用动态数组进行数据管理的,该类与ArrayList类相比属于线程安全的类,效率比较低。 常用方法 | 方法 | 功能 | | --------------------------------------------------- | ------------------------------------------------------------ | | void add(int index,E e) | 添加元素 | | boolean addAll(int index,Collection<? extends E> c) | 添加集合中的所有元素 | | E get(int index) | 获取元素 | | int indexOf(Object o) | 查找指定元素 | | int lastIndexOf(Object o) | 反向查找指定元素 | | E set(int index ,E e) | 修改指定位置的元素 | | E remove(int index) | 删除指定位置的元素 | | List subList(int fromIndex,int toIndex) | 获取子List子集合与当前集合共用同一块内存空间,
    操作子集合也会改变当前集合 |

Map集合

基本概念

  • java.util.Map<K,V>集合中存储元素的基本单位是:单对元素,其中类型参数如下:K-此映射所维护的键(key)的类型,相当于目录,V-映射值(value)的类型,相当于内容
  • 该集合中 Key 是不允许重复的,而且一个key只能对应一个value
  • 该集合的主要实现类有:HashMap、TreeMap、LinkedHashMap、HashTable
  • 其中HashMap类的底层是采用哈希表进行数据管理
  • TreeMap类的底层是采用红黑树进行数据管理的
  • LinkedHashMap与HsahMap类的不同之处在与内部维护了一个双向链表,链表中记录了元素的迭代顺序,也就是元素插入集合中的先后顺序,因此便于迭代
  • HashTable类是古老的Map实现类,与HashMap类相比属于线程安全的类。且不允许null作为key或者value的数值
  • 其中Properties类是HashTable类的子类,该对象用于处理属性文件,key和value都是String类型的
  • Map集合是面向查询优化的数据结构,在大数据量情况下有着优良的查询性能
  • 经常用于根据key检索value的业务场景 常用方法 | 方法 | 功能 | | ----------------------------------- | ------------------------------------------------------------ | | V put(K key,V value) | 将key-value存入map,若已包含该key,则替换该key对应的
    返回值为该key原来对应的value,若没有则返回null | | V get(Object key) | 返回key对应的value,若不存在则返回null | | boolean containsKey(Object key) | 判断集合中是否包含该key | | boolean containsValue(Object value) | 判断集合中是否包含该value | | V remove(Object key) | 根据指定key进行删除value,返回值为value | | Set keySet() | 返回此映射中包含的键的Set视图 | | Collection values() | 返回此映射中包含的值得Set视图 | | Set<Map.Entry<K,V>> entrySet() | 返回此映射中包含的键值对的Set视图 |

元素放入HashMap集合的原理

  • 使用元素的key调用hashCode方法获取对应的哈希码值,再由某种哈希算法计算在数组中的索引位置
  • 若该位置没有元素,则将该键值对直接放入即可
  • 若该位置有元素,则使用key与已有元素依次比较哈希值,若哈希值不同,则将该元素放入
  • 若该key与已有元素的哈希值相同,则使用key调用equals方法与已有元素依次比较
  • 若相等则将对应的value修改,否则将键值对直接放入即可

Set集合

基本概念

  • java.util.Set 集合是Collection集合的子集合,与List集合平级
  • 该集合中元素没有先后放入次序,且不允许重复
  • 该集合的 主要实现类 是:HashSet 类和 TreeSet 类以及LinkedHashSet 类
  • 其中HashSet的底层是采用哈希表进行数据管理的
  • 其中TreeSet的底层是采用红黑树进行数据管理的
  • 其中LinkedHashSet 与HashSet 类的不同之处在于内部维护了一个双向链表,链表记录了元素的迭代顺序,也就是元素插入集合中的先后顺序,因此便于迭代

元素放入HashSet集合的原理

  • 使用元素调用 hashCode() 方法获取对应的哈希码值,再由某种哈希算法计算出该元素在数组中的索引位置。
  • 若该位置没有元素,则将该元素直接放入即可
  • 若该位置有元素,则使用新元素与已有元素依次比较哈希值,若哈希值不相同,则将该元素直接放入。
  • 若新元素与已有元素的哈希值相同,则使用新元素调用equals方法与已有元素依次比较
  • 若相等则添加元素失败,否则将元素直接放入即可
    • 思考:为什么要求重写equals方法后要重写hashCode方法呢?
    • 解析:当两个元素调用equals方法相等时证明这两个元素相等,重写 hashCode 方法后保证这两个元素得到的哈希码值相同,由同一个哈希算法生成的索引位置相同,此时只需要与该索引位置已有元素比较即可,从而提高效率并且避免重复元素的出现。

Queue集合

基本概念

  • java.util.Queue集合是Collection集合的子集合。与List集合属于平级关系
  • 该集合主要描述具有先进先出特征的数据结构,叫做队列(FIFO,first in first out)
  • 该集合的主要实现类是LinkedList类,因为该类在增删方面比较有优势 常用方法 | 方法 | 功能 | | ------------------ | ------------------------------------ | | boolean offer(E e) | 将一个对象添加到队尾,成功则返回true | | E poll() | 从队首删除并返回一个元素 | | E peek() | 返回队首的元素 |

Iterator

基本概念

  • java.util.Iterator 接口主要用于描述迭代器对象,可以遍历Collection集合中的所有元素
  • java.util.Collection 接口继承 Iterator 接口,因此所有实现Collection接口的实现类都可以使用该迭代器迭代对象 常用方法 | 方法 | 功能 | | ----------------- | ----------------------------------- | | boolean hasNext() | 判断集合中是否有可以迭代/访问的元素 | | E next() | 用于取出一个元素并指向下一个元素 | | void remove() | 用于删除访问到的最后一个元素 |

注意

  • 在迭代过程中,一旦使用了 Collection 的 remove(E e) 方法,运行会发生ConcurrentModificationException 并发修改异常,这是因为 Collection 在迭代过程中时一旦进行修改,会扰乱迭代器的工作。在迭代过程中只能使用迭代器的 remove 方法。

泛型

基本概念

  • 通常情况下集合可以存放不同类型的对象,是因为将所有对象都看作Object类型放入的,因此从集合中取出元素时也是Object类型,为了表达该元素真实的数据类型,则需要强制类型转换,而强制类型装换可能会引发类型转换异常
  • 为了避免上述错误的发生,从 Java 5开始增加泛型机制,也就是在集合名称的右侧使用<数据类型>的方式来明确要求该集合中可以存放的元素类型,若放入其他类型的元素则编译报错。
  • 泛型只在编译时期有效,在运行时期不区分是什么类型。

底层原理

  • 泛型的本质就是参数化类型,也就是让数据类型作为参数传递,其中E相当于形式参数负责占位,而使用集合时 <> 中的数据类型相当于实际参数,用于给形式参数E进行初始化,从而使得集合中所有的E被实际参数替换,由于实际参数可以传递各种各样广泛的数据类型,因此得名为泛型。
  • Java 7 新特性:菱形特性(后面的<>可以不加泛型)

自定义泛型类

//- 泛型类与普通类的区别就是后面添加了类型参数列表,可以有多个类型参数,如:<E,T,...>
//- 实例化泛型类时应该指定具体的数据类型,而且是引用数据类型而不是基本数据类型
//- 父类有泛型,子类可以选择保留泛型也可以选择指定泛型类型
//- 子类必须是“富二代”,子类除了指定或者保留父类的泛型,还可以增加自己的泛型
class SubPerson extends Person{}		//不保留泛型并且没有指定泛型,此时Person类的T默认为Object,擦除
class SubPerson extends Person<String>{}	//不保留泛型,但指定泛型类型。此时Person类的T被指定为String
class SubPerson<E> extends Person<E>{}		//保留父类泛型
class SubPerson<E,T> extends Person<E>{}	//保留父类泛型,子类增加新的泛型

自定义泛型方法

//- 泛型方法就是我们输入参数的时候,输入的是泛型参数。而不是具体的参数。我们在调用这个方法是需要对泛型参数进行实例化
//- 泛型方法的格式:[访问权限] <泛型> 返回值类型 方法名([泛型标识 参数名称]) {}
//- 在静态方法中使用泛型参数的时候,需要我们把静态方法定义为泛型方法
public <E> void test(E e){}

泛型通配符

  • 如果B是A 的一个子类或子接口,而G是具有泛型声明的类或者接口,则G并不是G的子类型
  • 有时候我们希望传入的类型在一个指定的范围内,此时就可以使用泛型通配符了
  • 泛型中有三种通配符形式:
    • 表示我们可以传入任意类型的参数,无限制通配符<?>
      • 不支持元素的添加,支持元素的获取,全部当做Object类
    • 表示类型的上界是E,只能是E或者是E的子类<? extends E>
      • 不支持元素的添加,支持元素的获取,全部是Animal类
    • 表示类型的下界是E,只能是E或者是E的父类<? super E>
      • 支持元素的添加,支持元素的获取,全部当做Object类

注意

List<String> lt1 = new ArrayList<>();
List<Double> lt2 = new ArrayList<>();
lt1 = lt2;//报错:集合支持的类型不同