天天看点

Java(泛型和集合)泛型集合

文章目录

  • 泛型
    • 1.泛型思想
    • 2.泛型类
    • 3.泛型方法
    • 4.泛型高级用法
    • 5.类型擦除
  • 集合
    • 1.集合框架
    • 2.Iterator和Collection
      • 2.1 Iterator(迭代器接口)
      • 2.2 Collection接口
    • 3.List集合
      • 3.1 ArrayList
      • 3.2 LinkedList
    • 4.Set集合
      • 4.1 HashSet
      • 4.2 TreeSet
    • 5.Queue集合和Deque接口
      • 5.1 Queue(队列)
      • 5.3 Deque接口
    • 6.Map集合

泛型

1.泛型思想

泛型的本质是参数化类型,也就是说可以为类、接口或方法指定一个类型参数(注意类型是对象,不能是基本类型如int、double等),这意味着编写的代码可以对多种不同类型的对象重用。泛型提供了编译时类型安全检测机制,可防止插入错误的对象。因此泛型可以使程序更易读,也更安全。由于把对象“丢进”集合时,集合丢失了对象的状态信息,集合只知道它盛装的是 Object,因此取出集合元素后通常还需要进行强制类型转换。这种强制类型转换既增加了编程的复杂度,也可能引发 ClassCastException 异常。

先看一个简单的例子

List list = new ArrayList();
List<String> list = new ArrayList<String>(); // 之后的集合初始化基本都会使用泛型
List<String> list = new ArrayList<>(); // 这样写更加简洁
           

可以很明显的发现使用泛型进行初始化可以很容易的知到list中存储的是String对象并且只能存储String对象,显然第一行代码做不到这一点,这便是泛型的易读性和安全性。

2.泛型类

按照约定,类型参数名使用单个大写字母表示,常用的有E(表示元素)、K(表示键)、N(表示数字)、T(表示类型)、V(表示值)。不过下面的表示不太标准。

//定义一个泛型类的标准格式
public class Stu<N, A, S> {
    private N name; // 姓名
    private A age; // 年龄
    // 创建类的构造函数
    public Stu(N name, A age, S sex) {
        this.name = name;
        this.age = age;
    }
    // 下面是上面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;
    }
}
Stu<String, Integer> stu = new Stu<String, Integer>("kk", 3); // 实例化对象
String name = stu.getName();
Integer age = stu.getAge();
// 获取学生姓名、年龄时,不需要类型转换,程序隐式地将 Object 类型的数据转换为相应的数据类型。
           

3.泛型方法

// 泛型方法 注意定义方法时的格式。 泛型方法所在的类可以是泛型类,也可以不是泛型类                          
public static <E> void printArray( E[] inputArray )// 可以有多个参数
 {
    	// 输出数组元素            
       for ( E element : inputArray ){        
          System.out.printf( "%s ", element );
       }
       System.out.println();
  }
class.<E>.printArray(Obj);// 调用。也可以这样写class.printArray(Obj);
//========有返回值的方法定义格式========//
public static <E> E printArray( E[] inputArray )// 可以有多个参数
 {
 }
           

4.泛型高级用法

// 1.限制泛型可用类型
// 限制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>();
    }
}

// 2.使用类型通配符
A<? extends List>a = null;
a = new A<ArrayList>();    // 正确
b = new A<LinkedList>();    // 正确
c = new A<HashMap>();    // 错误
T<?> d = null; //改写为这种形式,则上面的都正确
           

5.类型擦除

当实例化泛型类型时,编译器使用一种叫类型擦除(type erasure)的技术转换这些类型。在编译时,编译器将清除类和方法中所有与类型参数有关的信息。类型擦除可让使用泛型的Java应用程序与之前不使用泛型类型的Java类库和应用程序兼容。

例如,Node被转换成 Node,它称为源类型(raw type)。源类型是不带任何类型参数的泛型类或接口名。这说明在运行时找不到泛型类使用的是什么类型。因此下面的操作都会出现错误:

public class A<E>{
	if(item instance E){...} 
	E item1 = new E();
	E[] item2 = new E[10];
	E obj = (E) new Object();
}
           

这篇文章的末尾详细的解释了类型擦除的机制【转载】Java泛型详解

集合

1.集合框架

先上图,集合框架中的接口和实现类。

Java(泛型和集合)泛型集合
Java(泛型和集合)泛型集合

从两张集合框架图可以看到,Java集合框架主要包括两种类型的容器,一种是集合(Collection),存储一种元素的集合;另一种是映射(Map),存储键/值对映射。

集合接口

接口 描述
Iterable和Iterator的区别和联系 https://blog.csdn.net/qq_32224047/article/details/106314992 注意Collection继承的是Iterable接口
Collection Collection是最基本的集合接口,一个 Collection 代表一组 Object,即 Collection 的元素, Java不提供直接继承自Collection的类,只提供继承于的子接口(如List和set)。Collection 接口存储一组不唯一,无序的对象。
List 是最常用的接口。是有序集合,允许有相同的元素。使用 List 能够精确地控制每个元素插入的位置,用户能够使用索引(元素在 List 中的位置,类似于数组下标)来访问 List 中的元素,与数组类似。
Set Set 具有与 Collection 完全一样的接口,只是行为上不同,Set 不保存重复的元素。Set 接口存储一组唯一,无序的对象。
SortedSet 继承于Set保存有序的集合。
Queue Queue 是 Java 提供的队列实现,有点类似于 List。
Deque 是 Queue 的一个子接口,为双向队列。
Map Map 接口存储一组键值对象,提供key(键)到value(值)的映射。
SortedMap 继承于 Map,使 Key 保持在升序排列。
RandomAccess 《Java集合系列-RandomAccess》 写的很详细

集合实现类

类名称 描述
AbstractCollection 实现了大部分的集合接口
AbstractList 继承于AbstractCollection 并且实现了大部分List接口
ArrayList 该类也是实现了List的接口,实现了可变大小的数组,随机访问和遍历元素时,提供更好的性能,但插入删除效率低
AbstractSequentialList 继承于 AbstractList ,提供了对数据元素的链式访问而不是随机访问
LinkedList 该类实现了List接口,允许有null(空)元素,主要用于创建链表数据结构。LinkedList 查找效率低
AbstractSet 继承于AbstractCollection 并且实现了大部分Set接口
HashSet 该类实现了Set接口,不允许出现重复元素,不保证集合中元素的顺序,允许包含值为null的元素,但最多只能一个。
LinkedHashSet 具有可预知迭代顺序的 Set 接口的哈希表和链接列表实现
TreeSet TreeSet 类同时实现了 Set 接口和 SortedSet 接口。SortedSet 接口是 Set 接口的子接口,可以实现对集合进行自然排序,因此使用 TreeSet 类实现的 Set 接口默认情况下是升序排序
AbstractMap 实现了大部分的Map接口
HashMap HashMap 是一个散列表,它存储的内容是键值对(key-value)映射。该类实现了Map接口,根据键的HashCode值存储数据,具有很快的访问速度,最多允许一条记录的键为null,不支持线程同步。
TreeMap 继承AbstractMap,并且使用一颗树
WeakHashMap 继承AbstractMap类,使用弱密钥的哈希表
LinkedHashMap 继承于HashMap,使用元素的自然顺序对元素进行排序
IdentityHashMap 继承AbstractMap类,比较文档时使用引用相等

2.Iterator和Collection

首先从上面的框架图可以看出Iterable(Iterator)和Collection两个类里的方法都可以被List、Set和Queue集合调用(相同的方法就不重复总结了)。

2.1 Iterator(迭代器接口)

方法 作用
boolean hasNext() 如果存在另一个可访问的元素,返回true
E next() 返回将要访问的下一个对象。如果已经到达了集合的末尾,将抛出一个NoSuchElementException异常
void remove() 删除上次访问的对象。这个方法必须紧跟在访问一个元素(next())后执行。如果上次访问之后集合已经发生了变化,将抛出IllegalStateException异常
default void forEachRemaining(Consumer<? super E> action) 访问元素,并传递到指定的动作(如lambda表达式),直到再没有更多元素,或者这个动作抛出一个异常

遍历集合:

// 简洁的for循环 for each
ArrayList<String> list = new ArrayList<>();
for(String element : list){} // "for each" 可以处理任何实现了Iterable接口的对象

//而调用上面的forEachRemaining方法可以不写循环
Iterator<String> iterator = list.iterator(); // 初始化一个迭代器对象
iterator.forEachRemaining(element -> do something with element);
           

Java集合类库中的迭代器与C++(根据数组索引建模)等的有所不同。Java迭代器查找操作与位置变更紧密耦合,查找一个元素的唯一方法是调用next,在执行查找操作的同时,迭代器的位置会随之向前移动。因此,可以认为Java迭代器位于两个元素之间,当调用next时,迭代器就越过下一个元素。并返回刚刚越过的元素的引用。

2.2 Collection接口

方法 作用
Iterator< E> iterator() 返回一个 Iterator 对象,用于遍历集合中的元素
int size() 返回集合中元素的个数
boolean isEmpty() 集合为空则返回true
boolean add(E e) & boolean addAll(Collection c) 向集合中添加一个元素或c中的所有元素。如果集合对象被添加操作改变了,则返回 true
Object[] toArray() 把集合转换为一个数组,所有的集合元素变成对应的数组元素
void clear() 清除集合中的所有元素,将集合长度变为 0
boolean contains(Object o) & boolean containsAll(Collection c) 判断集合中是否存在指定元素或c中所有元素
boolean remove(Object o) 从集合中删除一个指定元素,当集合中包含了一个或多个元素 o 时,该方法只删除第一个符合条件的元素,删除成功将返回 true
boolean removeAll(Collection c) 从集合中删除所有在集合 c 中出现的元素。如果该操作改变了调用该方法的集合,则该方法返回 true
boolean retainAll(Collection c) 从集合中删除集合 c 里不包含的元素。如果该操作改变了调用该方法的集合,则该方法返回 true

3.List集合

可以发现Iterator没有add方法,但集合类库提供了一个继承自Iterator的子接口ListIterator,其中包含add方法,同时还可以反向遍历链表。

java.util.List

  • ListIterator< E> listIterator() 返回一个列表迭代器,用来访问列表中的元素
  • ListIterator< E> listIterator(int index) 返回一个列表迭代器,用来访问列表中的元素,第一次调用这个迭代器的next会返回给定索引的元素

java.util.ListIterator(初始化及使用与Iterator相同)

  • void add(E element) 在当前位置添加一个元素
  • E Previous() 返回前一个对象。如果已经到达了列表的头部,将抛出一个NoSuchElementException异常
  • boolean hasPrevious() 反向迭代链表时。如果还有可以访问的元素,返回true
  • void set(E element) 用新元素替换next或previous访问的上一个元素。如果上一个next或previous调用之后链表结构发生了变化,将抛出IllegalStateException异常

3.1 ArrayList

ArrayList 类是一个可以动态修改的数组,与普通数组的区别就是它是没有固定大小的限制。查找效率高,但添加或删除元素。

方法 作用
ArrayList() & ArrayList(Collection<? extends E> elements) 构造方法
E get(int index) 获取此集合中指定索引位置的元素
E set(int index, Eelement) 将此集合中指定索引位置的元素修改为 element 参数指定的对象,并返回此集合中指定索引位置的原元素。指定的索引必须是 List 集合的有效索引
int index(Object o) 返回此集合中第一次出现指定元素的索引,如果此集合不包含该元素则返回 -1
int lastIndexOf(Object o) 返回此集合中最后一次出现指定元素的索引,如果此集合不包含该元素,则返回 -1
List< E> subList(int fromlndex, int tolndex) 返回一个新的集合,新集合中包含 [fromlndex,tolndex) 索引之间

3.2 LinkedList

LinkedList 类采用双向链表结构保存对象。需要频繁向集合中插入和删除元素时,使用 LinkedList 类比 ArrayList 类效果高,但是 LinkedList 类随机访问元素的速度则相对较慢。

方法 作用
LinkedList() & LinkedList(Collection<? extends E> elements) 构造方法
void addFirst(E e) & void addLast(E e) 将指定元素添加到此集合的开头或末尾
E getFirst() & E getLast() 返回此集合的第一个或最后一个元素
E removeFirst() & E removeLast() 删除此集合的第一个或最后一个元素

4.Set集合

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

4.1 HashSet

先康康散列集的设计思想 散列集简介

java.util.HashSet< E>

  • HashSet() 构造一个空的散列集
  • HashSet(Collection<? extends E>c) 构造一个包含指定 Collection 集合元素的新 Set 集合
  • HashSet(int initialCapacity) 构造一个空的具有指定容量(桶数)的散列集
  • HashSet(int initialCapacity, float loadFactor) 构造一个有指定容量和装填因子(0.0~1.0之间的一个数,确定散列表的填充百分比,当大于这个百分比时,散列表在进行再散列) 的散列集

Java.lang.Object

  • int hashCode() 返回这个对象的散列码。散列码可以是任何整数,包括正数和负数。equals和hashCode的定义必须兼容,即如果x.equals(y)为true,则x.hashCode必须等于y.hashCode.

4.2 TreeSet

TreeSet 类同时实现了 Set 接口和 SortedSet 接口。SortedSet 接口是 Set 接口的子接口,可以实现对集合进行自然排序(默认为升序排序)。排序是用一个树的数据结构(目前是红黑树)完成的,每次将一个元素添加到树中,都会将其放置在正确的排序位置上。因此,遍历时(for each或迭代器)总是以有序的顺序访问每个元素。

TreeSet 只能对实现了 Comparable 接口的类对象进行排序,这些类有:

比较方式
包装类(BigDecimal、Biglnteger、 Byte、Double、Float、Integer、Long 及 Short) 按数字大小比较
Character 按字符的 Unicode 值的数字大小比较
String 按字符串中字符的 Unicode 值的数字大小比较

java.util.TreeSet< E>

  • TreeSet() & TreeSet(Comparator<? super E> comparator) 构造一个空树集
  • TreeSet(Collection<? extends E> elements) & TreeSet(SortedSet< E> s) 构造一个包含一个集合或有序集中的所有元素。(对于后一种情况,要使用同样的顺序)

java.util.SortedSet< E>

  • Comparator<? super E> comparator() 返回用于对元素进行排序的比较器。如果元素用Comparable接口的compareTo方法进行比较则返回null
  • E first() & E last() 返回有序集中最大或最小元素
  • SortedSet subSet(E fromElement,E toElement) 返回一个新的集合,新集合包含原集合中 fromElement 对象与 toElement对象之间(不包含toElement对象)的所有对象
  • SortedSet headSet(E toElement)返回一个新的集合,新集合包含原集合中 toElement 对象之前的所有对象。不包含 toElement 对象
  • SortedSet tailSet(E fromElement) 返回一个新的集合,新集合包含原集合中 fromElement 对象之后的所有对

    象。包含 fromElement 对象

同时,从Java 6起,TreeSet类实现了NavigableSet接口,增加了几个查找元素以及反向遍历的便利方法

java.util.NavigableSet

  • E higher(E value) & E lower(E value) 返回大于value的最小元素或小于value的最大元素,没有则返回null
  • E ceiling(E value) & E floor(E value) 返回大于等于value的最小元素或小于等于value的最大元素,没有则返回null
  • E pollFirst() & E pollLast() 删除并返回这个集中的最大或最小元素,集为空时返回null
  • Iterator< E> descendingIterator() 返回一个按照递减顺序遍历元素的迭代器

5.Queue集合和Deque接口

5.1 Queue(队列)

队列允许你高效地在尾部添加元素,在头部删除元素。

方法 作用
boolean add(E element) & boolean offer(E element) 如果队列没满,将元素添加到队尾并返回true。如果队列已满,第一个方法会抛出IllegalStateException,而第二个方法返回false
E remove() & E poll() 队列不为空,删除并返回这个队列队头的元素。若队列为空,第一个方法会抛出NoSuchElementException,而第二个方法返回null
E element() & E peek() 队列不为空,返回这个队列队头的元素,但不删除。若队列为空,第一个方法会抛出NoSuchElementException,而第二个方法返回null

**优先队列(PriorityQueue类)**继承自AbstractQueue类,其中的元素可以按照任意地顺序插入,但会按照有序的顺序进行检索(即总是从当前队列的最小元素开始操作)。这是因为优先队列使用了一种精巧且高效数据结构——堆(heap)。堆是一个可以自组织的二叉树,其添加(add)和删除(remove)操作可以让最小的元素移动到根,而不必花费时间对元素进行排序。与TreeSet一样,优先队列既可以保存实现了Comparable接口的类对象,也可以保存构造器中提供的Comparator对象。

java.util.PriorityQueue

  • PriorityQueue() & PriorityQueue(int initialCapacity) 构造一个存放Comparable对象的优先队列
  • PriorityQueue(int initialCapacity, Comparator<? super E> c) 构造一个优先队列,并使用指定的比较器对元素进行排序

5.3 Deque接口

Java 6引入了Deque接口,ArrayDeque和LinkedList实现了这个接口,这两个类都可以提供双端队列(deque),允许在头部和尾部都高效地添加或删除元素。

  • void addFirst(E element) & void addLast(E element)
  • boolean offerFirst(E element) & boolean offerLast(E element)

    将给定的对象添加到双端队列地队头或队尾。如果队列已满,第一组方法会抛出IllegalStateException,而第二组方法返回false

java.util.ArrayDeque

  • ArrayDeque() & ArrayDeque(int initialCapacity)

    用初始容量16或给定的初始容量构造一个无限定双端队列

6.Map集合

Java类库为映射(map)提供了两个通用的实现:HashMap和TreeMap类,都实现了Map接口。

散列映射(HashMap)对键进行散射,树映射(TreeMap 实现了SortedMap接口)根据键的顺序将元素组织为一个搜索树(基于红黑树)。散列或比较函数只应用于键。散列效率快一些,如果不需要按照有序的顺序访问键,最好选择散列映射。

HashMap和TreeMap类的方法基本是相同的

方法 作用
V get(Object key) 获取与键关联的值或对象,没有则返回null
V put(K key, V value) 将一键值对放到映射中。如果键已存在,与键关联的旧对象将会被新的值取代,并返回关联的旧值。若之前没有这个键则返回null
void putAll(Map<? extends K, ? extends V> entries) 将指定Map中的 key-value 对复制到本Map中
boolean containsKey(Object key) 查询 Map 中是否包含指定的 key,如果包含则返回 true
boolean containsValue(Object value) boolean containsValue(Object value)
default void forEach(BiConsumer<? super K, ? super V> action) 对键值对作相应的动作
boolean isEmpty() 查询该 Map 是否为(即不包含任何 key-value 对,如果为空则返回 true
int size() 返回该 Map 里 key-value 对的个数
V remove(Object key) 从 Map 集合中删除 key 对应的键-值对,返回 key 对应的 value,如果该 key 不存在,则返回 null
boolean remove(Object key, Object value) 这是 Java 8 新增的方法,删除指定 key、value 所对应的 key-value 对。如果从该 Map 中成功地删除该 key-value 对,该方法返回 true,否则返回 false
void clear() 删除该 Map 对象中的所有 key-value 对

java.util.HashMap<K,V>

  • HashMap()
  • HashMap(int initialCapacity)
  • HashMap(int initialCapacity, float loadFactor)

    构造一个有指定容量和装填因子的空散列映射(0.0~1.0之间的一个数,确定散列表的填充百分比,当大于这个百分比时,散列表在进行再散列) 。默认的装填因子是0.75.

java.util.TreeMap< E>

  • TreeMap() 为实现Comparable接口的键构造一个空的映射
  • TreeMap(Comparator<? super K> c) 构造一个树映射,并使用指定的比较器对键进行排序
  • TreeSet(Map<? extends k, ? extends v> entries) 构造一个包含entries所有映射的树映射
  • TreeSet(SortedMap<? extends k, ? extends v> entries) 构造一个包含entries所有映射的树映射,且使用与给定有序映射相同的比较器

java.util.SortedMap<K, V>

  • Comparator<? super K> comparator() 返回用于对元素进行排序的比较器。如果元素用Comparable接口的compareTo方法进行比较则返回null
  • K firstKey() & E lastKey() 返回映射中最小或最大键

继续阅读