自学内容网 自学内容网

Javase-集合框架初步(JDK17&21)

1. 集合框架概述

集合概述 :

  1. 集合是一种容器, 用来组织和管理数据的, 非常重要
  2. Java的集合框架对应的这套类库其实是对各种数据结构的实现
  3. 每一个集合类底层采用的数据结构不同, 例如ArrayList集合底层采用了顺序表, LinkedList底层采用了双向链表, HashMap集合底层采用哈希表, TreeMap集合底层采用的红黑树
  4. 我们对于集合框架的使用要求就是, 要知道什么场合下使用什么样的数据结构是最高效, 最合适的
  5. 集合中存储的是引用, 并不是把堆中的对象存储在集合中, 是把对象的内存地址存储到集合中
  6. 默认情况下, 如果不使用泛型的话, 集合中可以存储任何类型的引用, 只要是Object的子类都可以
  7. 集合框架相关的类都存储在 java.util.包下
  8. Java的集合框架分为下面的两部分
    Collection结构 : 元素以单个形式存储
    Map结构 : 元素以键值对的映射关系存储

2. Collection框架的继承结构

我们分别用starUML做出了JDK17和JDK21的继承关系图如下

JDK17版本(更为详细, 我们主学的版本)
在这里插入图片描述
JDK21版本
在这里插入图片描述

Collection继承体系

  1. 我们的图中, 蓝色的是实现类, 其他的都是接口
  2. 6个实现类里面, 只有HashSet是无序集合, 其他都是有序集合
    无序集合跟有序集合的判别法见下
    有序集合 : 集合中的元素有下标或者说元素是可以排序的
    无序集合 : 集合中的元素没有下标并且元素也没有排序
  3. 每一个集合对应的实现类如下
    LinkedList : 双向链表(不是队列和栈, 但是可以模拟队列和栈)
    ArrayDeque : 数组(也可以模拟队列和栈)
    ArrayList : 数组(线程不安全, 但是效率高)
    Vector : 数组(线程安全, 但是效率低)
    HashSet : 哈希表
    LinkedHashSet : 双向链表加哈希表
    TreeSet : 红黑树
  4. Queue和List集合中的元素可以重复, Set集合中的元素不可重复
  5. 关于JDK21新增接口的问题
    SequencedCollection和SequencedSet接口都是新增的

关于迭代器

由于Collection接口继承了Iterable接口, 说明所有的Collection集合体系下的元素都是可以迭代的, 该方法中有一个方法 : Iterator< T > iterator(); 该方法返回一个迭代器接口
这个接口中有下面几个重要的方法, boolean hasNext() 和 T next() 还有 T remove()
迭代器在最开始的时候是指向的第一个元素的前一个位置(可以理解为-1处)
hasNext()方法判断的是迭代器是否有下一个对象, next()的作用是打印迭代器指针指向的下一个元素的值, 并把指针移动到下一个元素的位置上去

3. Collection接口的通用方法

关于这一块我们要重点去记忆, 因为这对我们的集合框架的梳理很有帮助, 防止到后期使用集合框架的时候迷迷糊糊的, 下面是Collection集合中的通用方法

  1. boolean add(E e); 向集合中添加元素
  2. int size(); 获取集合中的元素个数
  3. boolean addAll(Collection c); 将参数集合中的所有元素全部加入到当前集合
  4. boolean contains(Object o); 判断集合中是否包含对象o
  5. boolean remove(Object o); 从集合中删除对象o
  6. void clear(); 清空集合
  7. boolean isEmpty(); 判断集合是不是一个空集合
  8. Object[] toArray(); 把集合转化为一个一维数组

下面我们用ArrayList和LinkedList这两个实现类来验证一下上述Collection接口中的通用方法

  1. add方法测试
public class A {
    //ArrayList的add方法是默认添加到尾部的
    public static void main(String[] args) {
        //add方法测试
        ArrayList<Integer> arrayList = new ArrayList<>();
        arrayList.add(1);
        arrayList.add(2);
        arrayList.add(3);
        arrayList.add(4);
        arrayList.add(5);
        arrayList.add(6);
        //获取迭代器
        Iterator<Integer> it = arrayList.iterator();
        //迭代元素
        while(it.hasNext()){
            System.out.print(it.next() + " ");
        }
    }
}
输出结果 : 1 2 3 4 5 6
  1. size方法测试
public class A {

    public static void main(String[] args) {
        //size()方法测试
        ArrayList<Integer> arrayList = new ArrayList<>();
        arrayList.add(1);
        arrayList.add(2);
        arrayList.add(3);
        arrayList.add(4);
        arrayList.add(5);
        arrayList.add(6);

        //打印值
        System.out.println(arrayList.size());
    }
}
输出结果 : 6
  1. addAll方法测试(只要是Collection集合体系下的都可以使用)
public class A {

    public static void main(String[] args) {
        //addAll()方法测试
        ArrayList<Integer> arrayList = new ArrayList<>();
        LinkedList<Integer> linkedList = new LinkedList<>();
        arrayList.add(1);
        arrayList.add(2);
        arrayList.add(3);
        linkedList.add(4);
        linkedList.add(5);
        linkedList.add(6);
        arrayList.addAll(linkedList);

        //获取迭代器
        Iterator<Integer> it = arrayList.iterator();

        //遍历集合元素
        while(it.hasNext()){
            System.out.print(it.next() + " ");
        }
    }
}
输出结果 : 1 2 3 4 5 6
  1. contains方法测试(该方法底层调用的equals方法)

public class A {

    public static void main(String[] args) {
        //contains()方法测试(该方法内部其实调用的是equals方法)
        ArrayList<Integer> arrayList = new ArrayList<>();
        arrayList.add(1);
        arrayList.add(2);
        arrayList.add(3);
        arrayList.add(4);

        //输出结果
        System.out.print(arrayList.contains(1) + " ");
        System.out.println(arrayList.contains(5));
    }
}
输出结果 : true false
  1. remove方法测试(底层删除的时候也是调用的equals方法)

从下面的测试结果也可以看出来我们的ArrayList方法remove(Object o)删除其实是删除从前向后找到的第一个o元素进行删除


public class A {

    public static void main(String[] args) {
        //remove()方法测试(该方法底层其实调用的是equals方法)
        ArrayList<Integer> arrayList = new ArrayList<>();
        arrayList.add(1);
        arrayList.add(2);
        arrayList.add(3);
        arrayList.add(4);
        arrayList.add(1);
        arrayList.add(2);
        arrayList.add(1);

        //remove删除(删除的其实是第一个1对象)
        //注意这里不能直接给一个1,因为List接口家族有一个根据下标删除的remove方法(后期说)
        arrayList.remove(Integer.valueOf(1));
        
        //迭代遍历输出
        Iterator<Integer> it = arrayList.iterator();
        while(it.hasNext()){
            System.out.print(it.next() + " ");
        }
    }
}
输出结果 : 2 3 4 1 2 1
  1. clear方法与isEmpty方法测试
//这个方法没什么好说的
public class A {

    public static void main(String[] args) {
        ArrayList<Integer> arrayList = new ArrayList<>();
        arrayList.add(1);
        arrayList.add(2);
        arrayList.add(3);
        arrayList.add(4);
        arrayList.add(1);
        arrayList.add(2);
        arrayList.add(1);
System.out.print(arrayList.isEmpty() + " ");
        arrayList.clear();
        System.out.print(arrayList.size() + " " );
        System.out.print(arrayList.isEmpty() + " ");
    }
}
输出结果 : false 0 true
  1. toArray方法测试
    没什么好说的就是把集合转化为Object数组, 但是值得一提的是, 我们不能够对数组进行强制类型转换, 因为编译器不认为(Integer[] 是 Object[] 的子类)
public class A {

    public static void main(String[] args) {
        ArrayList<Integer> arrayList = new ArrayList<>();
        arrayList.add(1);
        arrayList.add(2);
        arrayList.add(3);
        arrayList.add(4);
        arrayList.add(1);
        arrayList.add(2);
        arrayList.add(1);

        Object[] objects = arrayList.toArray();
        for(Object elem : objects){
            System.out.print(elem + " ");
        }
    }
}

4. JDK21与17的接口演变差异

JDK21新增接口方法

上面我们已经介绍了我们的JDK21添加了SequencedCollection接口
这个接口下有许多方法
ArrayList, LinkedList, LinkedHashSet, TreeSet, Vector, Stack都可以调用下面的方法

  1. void addFirst(); 向头部添加
  2. void addLast(); 向尾部添加
  3. Object removeFirst(); 删除头部
  4. Object removeLast(); 删除尾部
  5. Object getFirst(); 获取头部元素
  6. Object getLast(); 获取尾部元素
  7. SequencedCollection reversed(); 反转集合中的元素

JDK17

那么JDK17对上面的不同方法的处理是怎么样的呢, 我们看下面的测试


public class A {

    public static void main(String[] args) {
        ArrayList<Integer> arrayList = new ArrayList<>();
        arrayList.addFirst(); //error
        arrayList.addLast(); //error
        arrayList.removeFirst(); //error
        arrayList.removeLast(); //error
        arrayList.getFirst(); //error
        arrayList.getLast(); //error
arrayList.reversed(); //error

        LinkedList<Integer> linkedList = new LinkedList<>();
        linkedList.addFirst(1); //ac
        linkedList.addLast(2); //ac
        linkedList.removeFirst(); //ac
        linkedList.removeLast(); //ac
        linkedList.getFirst(); //ac
        linkedList.getLast(); //ac
        linkedList.reversed(); //error

        //Vector的情况同ArrayList
        Vector<Integer> vector = new Vector<>();

        //LinkedHashSet同ArrayList
        LinkedHashSet<Integer> linkedHashSet = new LinkedHashSet<>();

        //TreeSet的情况同ArrayList
        TreeSet<Integer> treeSet = new TreeSet<>();

        //Stack的情况同ArrayList(因为本来就是Vector的子类)
        Stack<Integer> stack = new Stack<>();
    }
}

也就是说只有LinkedList可以使用上面的除了reversed以外的方法, 其他实现类一个方法也无法使用

5. 泛型初步

泛型概述

泛型

  1. 泛型是Java5的新特性,属于编译阶段的功能。
  2. 泛型可以让开发者在编写代码时指定集合中存储的数据类型
  3. 泛型作用:
    3.1. 类型安全:指定了集合中元素的类型之后,编译器会在编译时进行类型检查,如果尝试将错误类型的元素添加到集合中,就会在编译时报错,避免了在运行时出现类型错误的问题。
    3.2. 代码简洁:使用泛型可以简化代码,避免了繁琐的类型转换操作。比如,在没有泛型的时候,需要使用 Object 类型来保存集合中的元素,并在使用时强制类型转换成实际类型,而有了泛型之后,只需要在定义集合时指定类型即可。
  4. 在集合中使用泛型
    Collection< String> strs = new ArrayList< String>();这就表示该集合只能存储字符串,存储其它类型时编译器报错。
    并且以上代码使用泛型后,避免了繁琐的类型转换,集合中的元素可以直接调用String类特有的方法。
  5. Java7的新特性:钻石表达式
    Collection< String> strs = new ArrayList<>();

泛型的定义方法

  1. 在类上面定义泛型
    下面就是在类上定义泛型的方法, 直接在类名后面用< E>定义泛型(E不是确定的)
    下面是最简单的一个泛型类

public class A<E> {
    private E name;

    public A(E name) {
        this.name = name;
    }

    public E getName() {
        return name;
    }

    public void setName(E name) {
        this.name = name;
    }
    
    protected void m1(E[] elem){
        //操作...
    }
}

  1. 在静态方法上定义泛型
    由于我们的静态方法不依赖于对象是直接通过类名调用的, 所以我们使用泛型的机制就要不同于类
    直接在静态方法前面声明一个泛型
public static <E> void m2(E[] elem){
        //操作...
    }
  1. 接口定义泛型

public class A<E> implements B<E>{
    @Override
    public void m() {
        //操作...
    }
}

interface B<T>{
    void m();
}

  1. 泛型通配符
  1. 泛型是在限定数据类型,当在集合或者其他地方使用到泛型后,那么这时一旦明确泛型的数据类型,那么在使用的时候只能给其传递和数据类型匹配的类型,否则就会报错。
  2. 有的情况下,我们在定义方法时,根本无法确定集合中存储元素的类型是什么。为了解决这个“无法确定集合中存储元素类型”问题,那么Java语言就提供了泛型的通配符。
  3. 通配符的几种形式:
    3.1. 无限定通配符,<?>,此处“?”可以为任意引用数据类型。
    3.2. 上限通配符,<? extends Number>,此处“?”必须为Number及其子类。
    3.3. 下限通配符,<? super Number>,此处“?”必须为Number及其父类。
  1. 泛型的擦除与补偿

① 泛型的出现提高了编译时的安全性,正因为编译时对添加的数据做了检查,则程序运行时才不会抛出类型转换异常。因此泛型本
质上是编译时期的技术,是专门给编译器用的。加载类的时候,会将泛型擦除掉(擦除之后的类型为Object类型),这个称为泛型擦除。
② 为什么要有泛型擦除呢?其本质是为了让JDK1.4和JDK1.5能够兼容同一个类加载器。在JDK1.5版本中,程序编译时期会对集合添加的元素进行安全检查,如果检查完是安全的、没有错误的,那么就意味着添加的元素都属于同一种数据类型,则加载类时就可以把这个泛型擦除掉,将泛型擦除后的类型就是Object类,这样擦除之后的代码就与JDK1.4的代码一致。
③ 由于加载类的时候,会默认将类中的泛型擦除为Object类型,所以添加的元素就被转化为Object类型,同时取出的元素也默认为
Object类型。而我们获得集合中的元素时,按理说取出的元素应该是Object类型,为什么取出的元素却是实际添加的元素类型呢

④ 这里又做了一个默认的操作,我们称之为泛型的补偿。在程序运行时,通过获取元素的实际类型进行强转,这就叫做泛型补偿(不必手动实现强制转换)。获得集合中的元素时,虚拟机会根据获得元素的实际类型进行向下转型,也就是会恢复获得元素的实际类型,因此我们就无需手动执行向下转型操作,从本质上避免了抛出类型转换异常。


原文地址:https://blog.csdn.net/2301_81486828/article/details/140491725

免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!