集合框架学习

## Collection体系结构图:

image-20220129234928939

泛型

由JDK1.5新引入的特性,本质是参数化类型,把类型作为参数进行传递,可以理解为,将一个类传递到其他的容器当中

语法:

  • <T , ...> T称为类型占位符,表示一种引用类型

这里有一些注意点:

  1. 泛型只能是引用类型,如String,包装类,自定义类型等等

  2. 不同泛型对象不能相互赋值

    //这里说的不同泛型对象是指创建对象的类相同但泛型不同的对象:
    GenericLearning<String> genericLearning = new GenericLearning<>();
    GenericLearning<Integer> genericLearning1 = genericLearning;//这就不行,会报错
    

泛型的好处:

  1. 提高代码的重用性
  2. 防止类型转换异常,提高代码的安全性

泛型类

我们可以在一般类的基础上创建泛型类,写法如下:

public class GenericLearning<T>{}

可以看到,只需要在类名后面加尖括号里面加类名即可实现

调用也比较简单:

GenericLearning<String> genericLearning = new GenericLearning<>();
//JDK1.7之后,构造器尖括号内可以不用带上类名了

泛型接口

接口与类的定义类似:

public interface GenericInterface<T>

但是注意,在接口内不能创建泛型的对象,可以类比为在接口内创建属性,接口内的属性都是public static final,也就是静态常量。而泛型要是创建为静态常量,就必须要初始化:

T t = new T();

问题来了,引入的泛型我们并不知道它的构造器究竟是什么样子,换个说法来说,我们不知道它有没有无参构造器,就不一定能够这样创建,这样就报错了,这就与之前的说法矛盾了。因此,注意一点,泛型接口内不能创建泛型的对象

然后就是,接口在被类继承的时候,需要在继承时表明泛型的确切内容:

public class GenericLearning implements GenericInterface<String> {}
//若继承时类是泛型类,则可以与泛型类共用泛型而不需指明:
public class GenericLearning<T> implements GenericInterface<T> {}

泛型方法

方法的定义:

public <T> void genericMethod(){}

但是调用方法的时候就不用加<>,按照正常的方法调用即可

所以一般在方法的形参内加入T的对象:

public <T> void genericMethod(T t){}

使得在调用方法时,用实参来确定T的类型:

	test.genericMethod("abc");//T为String
	test.genericMethod(123);//T为Integer

泛型集合

是一种参数化类型,是类型安全的集合,这与之前的不同,泛型能使集合元素的类型一致。

eg.

ArrayList<String> arrayList = new ArrayList<>();

当此时定义String为泛型,此时集合内只能有String类型的集合元素。

Collection父接口

代表一组任意类型的对象,无序,无下标,不能重复

但是:由于Collection为父接口,不能直接创建对应的对象,只能通过继承的子类来创建,所以当使用子类构造方法初始化,且子类具有可重复性质的时候,其内部的元素也是可重复的

操作如下:

添加元素:

//一般添加元素
collection.add("pear");
collection.add("strawberry");
//添加指定集合的元素
collection.addAll(collection_2);

删除元素:

//一般删除元素
collection.remove("pear");
//删完元素
collection.clear();

遍历:

  1. 增强for循环遍历:

    //for循环需要用局部变量衡量遍历者的长度
    //增强for就免除了这一点
    for (Object object: collection) {
        System.out.println(object);
    }
    
  2. 迭代器遍历

    迭代器Iterator,专门面向Collection集合的,但这只是一个接口

    Iterator it = collection.iterator();
    while(it.hasNext()){
        System.out.println(it.next());
    }
    //hasNext();	判断是否有元素,如果有则移动指针到下一个元素区域
    //next();	读取元素内容
    //remove(Object o);	移除Collection集合内的某元素
    /*
    注意,当迭代器使用途中,不可使用Collection接口的remove()以及add()等方法直接改变集合的内容,否则会报错
    只能使用迭代器自带的remove()方法
    */
    

判断:

System.out.println(collection.contains("pear"));//有没有
System.out.println(collection.isEmpty());//是否空

List子接口

特点:有序,有下标,元素可以重复,也就是数据结构中的线性表

操作与Collection集合大致相同,其中有一些不同:

  1. 遍历操作中,由于List集合有下标,所以使用for循环也可以遍历:

    for (int i = 0; i < list.size(); i++) {
        System.out.println(list.get(i));
    }
    
  2. 遍历操作使用迭代器时,可以使用Collection对应的Iterator迭代器,也可以使用列表迭代器ListIterator

    //列表迭代器不像Collection迭代器,它可以从前往后,也可以从后往前迭代
    //使用next()或者previous()进行遍历
    //且可以添加,删除,修改元素
    ListIterator listIterator = list.listIterator();
    while (listIterator.hasNext()){
          System.out.println(listIterator.hasNext());
    }//正向遍历
    //正向遍历后,指针指到最后了,所以可以直接从后往前遍历
    while (listIterator.hasPrevious()){
         System.out.println(listIterator.hasPrevious());
    }//逆向遍历
    
  3. 由于List集合元素有下标,可以获取元素对应的下标位置:

    System.out.println(list.indexOf("apple"));
    

由结构图可知,List下实现类有三个:

ArrayList

特点:

  1. 数组结构实现,查询快,增删慢,需要开辟连续的空间
  2. 运行效率快,线程不安全

与上面说的相同,它与List的常用实现方法基本一致

深入分析一下,从源码入手,注意到一些重要的参量:

  • elementData 集合容量
  • DEFAULT_CAPACITY = 10 默认容量
  • size 实际元素个数

注:容量和实际元素个数不一样,容量指集合能装多少元素,size指实际装多少;而对于容量,当ArrayList集合内没有任何元素的时候,容量为0,一旦有添加元素,容量则为默认容量10

因此,这里提一下add()方法,这个是添加元素的方法,查看源码:

public boolean add(E e) {
    ensureCapacityInternal(size + 1);  // Increments modCount!!
    elementData[size++] = e;
    return true;
}

简单而言,就是容量变化,size加一,将元素加入集合,完毕

那么容量变化是怎么做到的?点进ensureCapacityInternal(size + 1):

private static int calculateCapacity(Object[] elementData, int minCapacity) {
    if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
        return Math.max(DEFAULT_CAPACITY, minCapacity);
    }
    return minCapacity;
}//3

private void ensureCapacityInternal(int minCapacity) {
    ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}//1

private void ensureExplicitCapacity(int minCapacity) {
    modCount++;

    // overflow-conscious code
    if (minCapacity - elementData.length > 0)
        grow(minCapacity);
}//2

呃啊,三个方法,看起来有点晕,我标了一下记号,简单来说,1调用了2,而2的参数是3的计算结果。

3方法的意思是,如果容量为空容量,那么那么取该参数为(默认容量)和(实际元素个数+1)两者的最大值,否则就直接取实际元素个数+1。

2方法直接看下面,若实际元素个数+1后大于容量,就执行一个grow()方法,这个方法才是关键,他会改变集合的容量:

private void grow(int minCapacity) {
    // overflow-conscious code
    int oldCapacity = elementData.length;
    int newCapacity = oldCapacity + (oldCapacity >> 1);
    if (newCapacity - minCapacity < 0)
        newCapacity = minCapacity;
    if (newCapacity - MAX_ARRAY_SIZE > 0)
        newCapacity = hugeCapacity(minCapacity);
    // minCapacity is usually close to size, so this is a win:
    elementData = Arrays.copyOf(elementData, newCapacity);
}

简单来说,创建一个新的容量值,这个新的容量值通过一系列计算判断而得到,最终令elementData的大小改变。(计算过程会有很多的情况,这里不过赘述)

Vector

特点:

  1. 数组结构实现,查询快,增删慢
  2. 运行效率慢,线程安全

与上面说的相同,它与List的常用实现方法基本一致

这里演示一下elements()方法以及Enumeration类,这个是枚举器,一般在JDK1.2之前使用这个遍历,写法和迭代器相似:

Vector vector = new Vector();
Enumeration en = vector.elements();//枚举器
while(en.hasMoreElements()){
    String s = (String) en.nextElement();
    System.out.println(s);
}

LinkedList

特点:

  1. 链表结构实现,增删快,查询慢,不需空间连续
  2. 是双向链表结构

Set子接口

特点:无序,无下标,元素不可重复

所谓元素不可重复,是指对象是否一致,包括元素内容和地址等完全一致。

HashSet

  • 基于HashCode计算元素存放的位置

  • 当存入元素HashCode相同时,会调用equals进行确认,如果结果为true,即重复元素,则拒绝后者存入;如果false,则引出链表接上该元素

注:存入过程为:先执行hashCode()函数,再执行equals()方法。这里用到hash查找的技术,根据内容设计hash函数计算元素存放的位置,使用的处理冲突的办法为拉链法,每个位置引出一串链表,用于存储hashcode相同但实际内容不同的对象

hashCode()函数的重写一般会以这个形式:

public int hashCode() {
    final int prime = 31;
    int result = 1;
    result = prime * result + age;
    result = prime * result + ((name == null) ? 0 : name.hashCode());
    return  result;
}

至于prime为什么设计为31,有观点说:

  1. 31为质数,可以减少散列冲突
  2. 31能够提高执行效率——31*i = (i<<5)-i

TreeSet

基于红黑树来进行元素存储,特征:

  1. 基于排列顺序实现元素不重复
  2. 实现了SortedSet接口,对集合元素自动排序。
  3. 元素对象的类型必须实现Comparable接口,指定排序的规则。
  4. 通过CompareTo方法确定是否为重复元素。

补充:

Comparator接口:比较器,可以实现定制比较

//创建集合后直接指定比较规则
TreeSet<Student> treeSet1 = new TreeSet<>(new Comparator<Student>() {//需要实现compare方法的方法体
    @Override
    public int compare(Student o1, Student o2) {
        int n1 = o1.getAge() - o2.getAge();
        int n2 = o1.getName().compareTo(o2.getName());
        return n1==0?n2:n1;
    }//这里是指比较规则为,先比较年龄,在比较名字
});

Map体系结构图

image-20220223150511071

Map接口

特点:

  • 用于存储键值对(Key - Value)
  • 键:无序无下标,不允许重复
  • 值:无序无下标,允许重复

常用的操作方法:

添加元素

map.put("1","你好");
map.put("2","谢谢");
map.put("3","小笼包");
map.put("4","再见");

删除元素

map.remove("2");

遍历

//1.使用keySet()
Set<String> keySet = map.keySet();
for (String key:
     keySet) {
    System.out.println(key + "  " + map.get(key));
}

//2.entrySet()方法
Set<Map.Entry<String,String>> entries = map.entrySet();
for (Map.Entry<String,String> entry:
     entries) {
    System.out.println(entry.getKey()+"   " + entry.getValue());
}
/*
	下者效率高于上者,entry表示一个映射对,它调取value数值会更快
	这里下面的Set<Map.Entry<String,String>>表示将整个entry对保存在Set集合中
*/

判断

System.out.println(map.containsKey("1"));

HashMap实现类

特点:线程不安全,运行效率快,允许用null作为key或者value

注:使用hashMap()构造方法初始化HashMap对象,构造出的对象默认初始容量为16,默认加载因子0.75。——加载因子就是数量到容量的0.75时就会扩容

源码分析:

static final int DEFAULT_INITIAL_CAPACITY = 1 << 4;//hashMap初始容量大小
static final int MAXIMUM_CAPACITY = 1 << 30;//hashmap的数组最大容量
static final float DEFAULT_LOAD_FACTOR = 0.75f;//默认加载因子
static final int TREEIFY_THRESHOLD = 8;//在链表长度大于8时。调整成红黑树
static final int UNTREEIFY_THRESHOLD = 6;//链表长度小于6,红黑树调整成链表
static final int MIN_TREEIFY_CAPACITY = 64;//链表长度大于8且hash桶元素个数大于64调整成红黑树
transient Node<K,V>[] table;//大小
transient int size;//个数

HashTable实现类(了解)

JDK1.0版本,线程安全,运行效率慢,不许用null作为key或是value,初始容量是11,默认加载因子0.75

它有一个子类Porperties:它要求key,value均为String类型,通常用于配置文件的读取

TreeMap

实现了SortedMap接口,可以对key自动进行排序

TreeMap与TreeSet一样,也可以进行定制比较:

TreeMap<String,String> treeMap = new TreeMap<>(new Comparator<String>() {
    @Override
    public int compare(String o1, String o2) {
        int n1 = o1.compareTo(o2);
        return n1;
    }
});

TreeSet的添加,创建操作,本质上是调用的TreeMap的操作:

private transient NavigableMap<E,Object> m;
TreeSet(NavigableMap<E,Object> m) {
        this.m = m;
    }
public TreeSet() {
        this(new TreeMap<E,Object>());
    }//构造函数

    public boolean add(E e) {
        return m.put(e, PRESENT)==null;
    }//add方法

Collections工具类

  1. Collections.sort(List list)——排序,默认顺序为升序
  2. Collections.binarySearch((List list , Object key)——二分查找
  3. Collections.copy(List dest , List src)——复制,将src的内容粘贴到dest里面
  4. Collections.reverse(List list);——反转
  5. Collections.shuffle();——打乱元素


补充:

  • list转成数组:

    ArrayList<String> arrayList = new ArrayList();
    String[] arr = arrayList.toArray(new String[0]);
    //注,要用泛型规定集合元素的种类,否则数组的类型一定为Object
    
  • 数组转成集合

List list = Arrays.asList(arr);
//这个集合是一个受限static集合,不能添加和删除操作,只能进行修改
//不要用基本类型创建的数组转集合,请使用引用类型,如包装类
posted @ 2022-02-24 00:18  可乐加品客  阅读(70)  评论(0)    收藏  举报