Java集合概述

在这里插入图片描述

一、集合概述


1、数组的特点

  • 数组的大小是固定的,一旦初始化之后长度不能改变。
  • 数组只能存储相同类型的数据。
  • 数组查询效率高(有索引,元素内存连续分配),增删效率低(不断的扩容)。

2、数组和集合的区别

相同点:

  • 都是容器,都可以存储多个数据。
  • 都可以存储引用类型的数据。

区别:

  • 数组长度不可变。
  • 集合长度可变。
  • 数组既可以存储基本类型数据,也可以存储引用类型数据。
  • 集合只能存储引用类型数据,如果要存储基本类型,需要存储对应的包装类。

3、为什么要用集合?

数组的缺点: 不灵活,容量需要事先定义好,不能随着需求的变化而扩容。

但是我们的开发又不可能离开数组,所以最初就只能依靠一些数据结构来实现动态的数组处理,其中最为重要的两个数据结构:链表、数组,但是面对这些数据结构的实现又不得不面对如下的问题?

  • 数据结构的代码实现困难,对于一般的开发者是无 法进行使用的。

  • 对于链表或二叉树当进行更新处理的时候维护是非常麻烦的。

  • 对于链表或二叉树还需要尽可能保证其操作的性能。


正是因为这样的原因,所以从JDK1.2开始Java引入了集合的概念,主要就是对常见的数据结构进行完整的实现包装,并且提供了一些列接口和实现类来帮助用户减少数据结构所带来的开发困难。

最初的集合实现由于Java本身的技术所限,所以对数据的控制并不严格,全部采用了Object类型进行数据的接收;在JDK1.5之后由于泛型技术的推广,集合框架也得到了良好的改进,可以直接利用泛型来保存相同类型的数据;随着数据量的不断增加,从JDK1.8开始集合框架中的实现算法也得到了良好的性能提升。


4、集合框架体系结构

要了解集合的常用接口、类以及类中提供的方法,底层源码的实现。

image-20221109173759470

集合的分类可以分为单列集合和双列集合两类:

  • 单列集合(Collection)
  • 双列集合(Map)

二、Collection集合


java.util.Collection<E> 是单列集合操作的最大的父接口,在该接口中定义了单列集合的所有操作。

在这里插入图片描述

Collection接口实现类的特点:

  • 有些Collection的子接口/实现类,可以存放重复的元素,有些不可以。
  • 有些Collection的子接口/实现类,有些是有序的(如 List),有些不是有序的(如 Set)。
  • Collection接口没有直接的实现子类,是用过它的子接口Set和List来实现的。

1、Collection集合常用方法

方法名称功能
boolean add(E e)添加单个元素
boolean addAll(Collection coll)把coll集合中的所有元素复制一份,并添加到当前集合中
void clear()清空集合中的所有元素
boolean contains(Object o)判断当前集合中是否包含指定的数据 (需要equals方法支持)
boolean isEmpty()判断当前合是否为空
boolean remove(Object o)删除(有相同元素,只能删除一个)(需要equals方法支持)
int size()获取集合中元素的个数
Object[] toArray()将集合变成Object数组返回
Iterator<E> iterator()返回此集合中元素的迭代器

在进行集合操作的时候有两个方法最为常用:【添加数据】add()、【输出数据】iterator(),在JDK1.5版本之前Collection只是一个独立的接口,但是从JDK1.5之后提供了Iterable父接口,并且在JDK1.8之后 Iterable 接口也得到一些扩充。

但是往往我们玩的都是Collection的两个子接口: List(有序有索引可重复)、Set(无序不可以重复)接口。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-adjokWQR-1668175055586)(Java%E9%9B%86%E5%90%88.assets/image-20221109175927326.png)]


使用场景的总结:

1、如果希望元素可以重复,又有索引,索引查询要快?

  • 用 ArrayList集合,基于数组的。(用的最多,例如获取用户数据等等)

2、如果希望元素可以重复,又有索引,增删首尾操作快?

  • 用 LinkedList集合,基于链表的。(栈、队列、排队系统)

3、如果希望增删改查都快,但是元素不重复、无序、无索引。

  • 用 HashSet集合,基于哈希表的。

4、如果希望增删改查都快,但是元素不重复、有序、无索引。

  • 用 LinkedHashSet集合,基于哈希表和双链表。

5、如果要对对象进行排序。

  • 用 TreeSet集合,基于红黑树。后续也可以用List集合实现排序(sort方法)。

2、Collection集合的遍历元素

2.1、 使用Iterator(迭代器)遍历

我们知道Collection继承了Iterable接口,而在Iterable接口中定义了一个iterator()抽象方法,用于返回一个Iterator对象即迭代器对象,来遍历集合中所有元素。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-cdHvnqm8-1668175624271)(Java%E9%9B%86%E5%90%88.assets/image-20221109205507959.png)]

而在Collection接口中重写了Iterable接口的iterator()方法,所以Collection的 子接口/实现类 都会有这个iterator()方法。

在这里插入图片描述
在这里插入图片描述

  • Iterator对象称为迭代器,主要用于遍历 Collection 集合中的元素。(因为它不能使用for+get(索引)方式取元素)
  • 所有实现了Collection接口的集合类都有一个iterator()方法,用于返回一个Iterator接口的实现类对象,即迭代器对象。
  • Iterator仅用于遍历集合,Iterator本身并不存放对象。


Iterator接口中的方法:

方法功能
boolean hasNext()如果仍有元素可以迭代,则返回 true
E next()返回迭代的下一个元素。
default void remove()使用迭代器删除集合中的元素,它会自动更新迭代器,并且更新集合。

迭代器的执行原理:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2sUCo2Cp-1668176087761)(Java%E9%9B%86%E5%90%88.assets/image-20221109222556515.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8V2Pz6SF-1668176087762)(Java%E9%9B%86%E5%90%88.assets/image-20221109222446475.png)]

迭代器源代码分析:

在这里插入图片描述

注意:

  • 在调用next()方法之前必须先调用hasNext()方法来检测下一个元素是否存在。若不调用且下一条记录无效时(也就是已经遍历完所有元素),再调用next()会抛出NoSuchElementException异常。

  • 当while循环结束后,iterator迭代器会指向最后一个元素,如果希望再次遍历集合,需要重新获取新的迭代器对象。

  • 如果在迭代器中添加删除指定元素,则会报ConcurrentModificationException并发修改异常。

    • 参考:https://blog.csdn.net/yztfst/article/details/97834440
  • 如果要在迭代器中删除指定元素,需要调用iterator的remove()方法,他会自动更新迭代器,并且更新集合。


2.2、使用增强for循环遍历

增强for循环,JDK1.5新特性

增强for循环可以代替iterator迭代器,它只能用于遍历数组或集合。

语法格式:

for(元素类型 变量名 :集合或数组名){
    sout(变量名)
}

如果遍历数组:foreach底层源码是普通for循环。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-pHPiOcge-1668180400683)(Java%E9%9B%86%E5%90%88.assets/image-20221109230502773.png)]


如果遍历集合:foreach底层源码是iterator迭代器(简化版的迭代器),只能对集合进行遍历操作。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-uXXgjYUU-1668180400683)(Java%E9%9B%86%E5%90%88.assets/image-20221109230702124.png)]

扩展:使用指定编码编译 javac -encoding utf-8 xx.java


三、常见的数据结构

数据接口有:数组、队列、栈、链表、树、散列、堆、图。

1、栈

特点:先进后出、后进先出、入栈(压栈)、出栈(弹栈)。


2、队列

特点: 先进先出、后进后出(例如排队做核酸)

在这里插入图片描述


3、数组

特点:查询快(有索引、元素内存连续分配)、增删慢(不断扩容)。

在这里插入图片描述


4、链表

特点:查询慢(需要遍历),增删快(不需要创建新的链表,只需修改链表中节点保存的地址)。

单项链表:
在这里插入图片描述


双向链表:
在这里插入图片描述

链表查询慢的原因:

  • 它需要遍历链表中的节点,然后使用算法判断是从前向后查,还是从后向前查。
  • 从前向后查:要查询节点的编号<节点数/2 。
  • 从后向前查:要查询节点的编号>=节点数/2。

5、树

在这里插入图片描述

  • 结点:树中的数据元素都称为结点。例如 A、B、C、D、E等等
  • 根:最上面的结点称之为根,一颗树只有一个根且由根发展而来,从另外一个角度来说,每个结点都可以认为是其子树的根。
  • 父亲:结点的上层结点,如图中,结点K的父亲是E、结点L的父亲是G。
  • 结点的度:结点所拥有的子树的个数称之为结点的度,如结点B的度为3
  • 树叶:度为0的结点,也叫作终端结点,图中D、K、F、L、H、I、J都是树叶。
  • 分支结点:度不为0的结点,也叫作非终端结点或内部结点,图中根、A、B、C、E、G都是分支结点
  • 结点的层次:从根节点到树中某结点所经路径上的分支树称为该结点的层次,根节点的层次规定为1,其余结点的层次等于其父亲结点的层次+1。

二叉树

二叉树是每个结点最多有两个子树的树结构。通常子树被称作“左子树”(left subtree)和“右子树”(right subtree)。(左边小、右边大)

在这里插入图片描述


查找二叉树

二叉树的一个重要应用是在它们查找中的使用,假设树中的每个结点存储一项数据,使得二叉树成为二叉查找树的性质是:对于树中的每个结点X,它的左子树中所有项的值小于X,而它的右子树中所有项的值大于X,这意味着该树所有的元素可以用某种一致的方式排序。

在这里插入图片描述


平衡二叉树

在生成二叉树/二叉查找树的时候是非常容易失衡的,造成的最坏的情况就是一边倒(只有左子树/右子树),这样将会导致树的检索效率大大降低,所以为了维持二叉树的平衡,大牛们提出了各种实现的算法,比如:AVL树–每个结点的左子树和右子树深度最多差1。

在这里插入图片描述


红黑树

红黑树顾名思义就是结点是红色或者黑色的平衡二叉树,它通过颜色的约束来维持着二叉树的平衡。

对于一颗有效的红黑树而言我们必须增加如下规则:

  • 每个结点都只能是红色或者黑色;
  • 根节点是黑色;
  • 每片叶子都是黑色的;
  • 如果一个结点是红色的,则它的两个子节点都是黑色的,也就是说在一条路径上不能出现相邻的两个红色结点;
  • 从任意一个结点到其每个叶子的所有路径都包含着相同数目的黑色结点。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-BMJz2C51-1668183144235)(4_红黑树.png)]

这些约束强制了红黑树的关键性质:从根到叶子的最长的可能路径不多于最短的可能路径的两倍长。结果就是这棵树大致上是平衡的,因为插入、删除和查找某个值得最坏情况时间都要求与树的高度成比例,这个高度理论上限允许红黑树只在最坏情况下都是高效的。


四、泛型


1、泛型概述

集合它可以存放任意对象,当把对象存储到集合后,他们都会被提升成Object类型。然后我们再取出每一个对象并且进行相应操作时,必须采用强制类型转换。

public class Demo01Generic {
    public static void main(String[] args) {
        // 创建一个ArrayList集合对象,指定存储数据的类型为String
        ArrayList<String> list = new ArrayList<>();
        list.add("aa");
        list.add("bbb");
        list.add("cccc");

        // 将运行时异常,提前到了编译时期,降低了程序员的工作量
        //list.add(1000); //只能存String类型数据

        // 使用增强for进行遍历
        for (String str : list) {
            System.out.println(str + "的长度: " + str.length());
        }

        System.out.println("===================");
        
        // 创建集合,不指定存储数据的类型
        // 默认按照Object类型处理
        ArrayList list2 = new ArrayList();
        list2.add("aa");
        list2.add("bbb");
        list2.add("cccc");

        // 可以存
        // 但是取出来进行强制类型转换,报出类型转换异常
        list2.add(2022);

        // 使用增强for进行遍历
        for (Object obj : list2) {
            // 因为创建ArrayList集合对象时,并没有指定存储数据的类型(泛型),
            // 所以内部存储的所有内容均被当做Object类型处理
            // 必须做强制类型转换(向下转型),存在安全隐患:类型转换异常 ClassCastException
            String str = (String) obj;
            System.out.println(obj + "的长度: " + str.length());
        }
    }
}

在这里插入图片描述

为什么会报错ClassCastException?因为上述的ArrayList集合只能存储同一类型对象(例如list2存储的都是字符串对象),当取出String类型数据时需要强转,即Object强转成String,又因为元素2022它强转后是Integer而不是String类型,所以把Integer类型的数据赋值给String类型后就会报ClassCastException类型转换异常。解决方案:使用泛型来约束集合存储指定类型数据。


什么是泛型?

泛型是程序设计语言的一种风格或范式。

泛型允许程序员在强类型程序设计语言中编写代码时使用一些以后才指定的类型(相当于占位符,可以被预先使用),在实例化时作为参数指明这些类型(实现代码的模板化,把数据类型当做参数传递)。其目的是加强类型安全以及减少类型转换的次数。

泛型是 JDK1.5之后增加的,允许在定义类和接口以及方法时使用类型参数(Type Parameter)。泛型它可以帮助我们建立类型安全的集合。提高了代码复用性和安全性。

使用泛型的好处?

  • 可以避免强制类型转换的麻烦。
  • 将运行时异常,提前到了编译时期,降低了程序员的工作量。
  • 一旦指定泛型,数据类型将被统一。
  • 实现代码的模板化,把数据类型当做参数传递。

2、泛型的分类

泛型类、泛型接口、泛型方法。

2.1、泛型类

如何定义泛型类:在类的声明处添加泛型变量即可。

泛型变量一般用大写字母表示 , 如 T(Type),E(Element),K(Key),V(Value)

如果一次要声明多个泛型变量,中间用逗号,分割即可。例如<T,E,K,V>

格式如下:

public class 类名<泛型变量>{
}
//例如
public class MyClass<T> {
}

示例:泛型类的使用

// 定义一个泛型类
public class MyClass<T> {

    // 定义成员变量
    private T t;

    // 无参构造器
    public MyClass() {
    }

    // 有参构造器
    public MyClass(T t) {
        this.t = t;
    }

   // set/get方法
    public T getT() {
        return t;
    }

    public void setT(T t) {
        this.t = t;
    }

    // toString方法
    @Override
    public String toString() {
        return "MyClass{" +
                "t=" + t +
                '}';
    }
}

测试类:

public class Demo02Generic {
    public static void main(String[] args) {
        // 使用无参构造器创建对象,并指定泛型
        MyClass<String> mc = new MyClass<>();
        // 使用set方法为成员变量赋值
        mc.setT("helloworld");
        System.out.println(mc); //MyClass{t=helloworld}

        // 使用get方法取值
        String str = mc.getT(); //因为创建对象的时候传了泛型,所以返回值类型就是Stirng类型
        System.out.println(str); //helloworld

        // 使用有参构造器创建对象
          MyClass<Integer> mc02 = new MyClass<>(100);//发生自动装箱操作
        //错误示范: 左侧<>中指定了Integer类型,有参构造右侧只能传递Integer数据,不能传递字符串
        //MyClass03<Integer> mc02 = new MyClass03<>("100");
      

        //调用toString方法获取数据
        String s2 = mc02.toString();
        System.out.println(s2); //MyClass{t=100}
    }
}

在这里插入图片描述


2.2、泛型方法

泛型方法可以是普通方法、静态方法和构造器方法。

泛型方法定义格式:

修饰符 <泛型变量> 返回值类型 方法名(参数类型 参数列表){
}
//例如Collections的两个泛型方法
public <T> T[] toArray(T[] a){
  return s.toArray(a); 
}

public static <T> boolean addAll(Collection<? super T> c, T... elements) {
    boolean result = false;
    for (T element : elements)
        result |= c.add(element);
    return result;
}

示例:泛型方法的使用

public class MyClass<T> {

    /*
       这种定义方法不叫泛型方法(因为它使用类上定义的泛型)
    */
    public void method(T t){
        System.out.println(t);
    }

    //定义非静态泛型方法
    //泛型方法: 该泛型只属于当前方法使用
    public <E> void show(E e) {
        System.out.println(e);
    }


     /*
        错误示范:
            静态方法必须使用类名直接调用,和对象无关
            但是类上的泛型,必须创建对象后才能确定具体的类型
            然而静态方法和对象无关,调用时根本没有对象,就没有泛型
        总结:
            静态方法,不能使用类上定义的泛型,只能使用方法中的泛型
     */
    /*public static void fun(T t){
        System.out.println(t);
    }*/


    //定义静态泛型方法
    public static <K> void test(K k) {
        System.out.println(k);
    }
}

在这里插入图片描述
测试类:

public class Demo01GenericMethod {
    public static void main(String[] args) {
        MyClass<String> mc01 = new MyClass<>();
        mc01.method("hello");

        /*
            错误示范:
                method方法使用类上的泛型已经被确定为了String,
                就不能传递String以外的类型
         */
        //mc01.method(100);
        
        /*
            正确写法:
                show方法上有自己的泛型,
                根据调用方法传递的参数的类型,来确定方法上泛型的类型
         */
        mc01.show("World");
        mc01.show(100);
        mc01.show(new Student("zs", 18));

        /*
            正确写法:
                test方法上有自己的泛型,
                根据调用方法传递的参数的类型,来确定方法上泛型的类型
         */
        MyClass.test("Java");
        MyClass.test(200);
        MyClass.test(new Student("ls",38));
    }
}

在这里插入图片描述


2.3、泛型接口

泛型接口定义格式如下:

public interface 接口名称<泛型变量> {
}
//例如
public interface Iterable<T> {}
public interface Collection<E> extends Iterable<E> {}
public interface List<E> extends Collection<E> {}
public interface Set<E> extends Collection<E> {}
public interface Map<K,V> {}

示例:泛型接口

public interface MyInter<T> {
    // 抽象方法
    /*public abstract */void method(T t);
}

实现类:MyInterImplA

/*
	已经确定接口要传的类型 String
 */
public class MyInterImplA implements MyInter<String> {
    @Override
    public void method(String str) {
        System.out.println(str);
    }
}

实现类:MyInterImplB

/*
    不确定接口要传的类型
    把实现类定义成泛型类,在实现接口时,就可以使用类上定义的泛型啦QAQ
 */
public class MyInterImplB<T> implements MyInter<T> {
    @Override
    public void method(T t) {
        System.out.println(t);
    }
}

测试类:

public class MyTest {
    public static void main(String[] args) {
        MyInterImplA mia = new MyInterImplA();
        mia.method("Hello");

        MyInterImplB<String> mib = new MyInterImplB<>(); //指定泛型
        mib.method("World");

        MyInterImplB<Student> mib2 = new MyInterImplB<>();//指定泛型
        mib2.method(new Student("zs",18));
    }
}

在这里插入图片描述


3、泛型通配符

泛型通配符: ? 可以理解成占位符,用来匹配泛型的,但是不能使用?定义泛型。

主要应用场景: 定义参数/变量时,不能确定变量的具体类型时,使用?代替。

示例1:

public class Demo01TongPeiFu {
    public static void main(String[] args) {
        List<String> list = new ArrayList<>();
        list.add("AAA");
        list.add("BBB");
        list.add("CCC");
        Set<Integer> set = new HashSet<>();
        set.add(111);
        set.add(222);
        set.add(333);
        //调用方法
        print(list);
        print(set);
        System.out.println("-------------");

        //调用方法
        print2(list);
        print2(set);

    }

    /**
     * 定义一个方法,完成以上两个集合的遍历
     * 泛型通配符: ? 就是一个占位符, 用来匹配泛型的,但是不能使用?定义泛型。
     * @param coll
     */
    public static void print2(Collection<?> coll) {
        for (Object o : coll) {
            System.out.println(o);
        }
    }

    /*
           定义一个方法,完成以上两个集合的遍历
               目前: 把print方法定义成了泛型方法
           方法参数:
               使用Collection接口定义变量,Collection接口在定义时是有泛型的,
               使用Collection接口定义变量,需要指定泛型
               泛型是不存在的多态的
           此处:
               print2方法是一个泛型方法
    */
    public static <T> void print(Collection<T> coll) {
        for (T t : coll) {
            System.out.println(t);
        }
    }
}

在这里插入图片描述


示例2:

public class Demo02TongPeiFuNotice {
    public static void main(String[] args) {
        List<Object> list1 = new ArrayList<>();//右侧<>中省略不写,就是Object
        List<Object> list2 = new ArrayList<Object>();//右侧<>中写Object也是可以的
        //List<Object> list3 = new ArrayList<String>();//错误: 左右两边<>中的数据类型必须一致,泛型不存在多态
        //List<Object> list4 = new ArrayList<Integer>();//错误: 左右两边<>中的数据类型必须一致,泛型不存在多态
        //List<Object> list5 = new ArrayList<Student>();//错误: 左右两边<>中的数据类型必须一致,泛型不存在多态
        List list6 = new ArrayList(); //不加泛型默认是Object类型

        //?通配符: 用来匹配泛型的,不能用来定义泛型
        //主要的应用场景: 定义参数/变量时,不能确定变量的具体类型时,使用?代替
        //?: 代表任意一种引用类型
        List<?> list;
        list = new ArrayList<>();//<>不写数据类型代表Object
        list = new ArrayList<String>(); //String
        list = new ArrayList<Integer>(); //Integer
        list = new ArrayList<Student>(); //Student
/*
	注意事项:
       1.泛型是不存在多态的,左侧<>中写的类型必须和右侧<>中的类型保持一致(可以省略右侧<>中的内容)
       2.使用泛型通配符,定义变量:
           List<?> list 可以接收哪些对象?
                   只要是List接口实现类的任意泛型对象就可以(创建对象时,只要在<>中写上一种引用类型就行)
       3.List<?> list: 理解为它是各种泛型List集合对象的父类
*/
    }
}

在这里插入图片描述


4、泛型上限和下限

泛型通配符的限制

4.1、 泛型的上限

一个父类的子类可以有任意多个,那如何表示出一个父类的任意子类呢?(找儿子)

泛型的上限:
// ?: 代表的是任意一种引用类型
        ? extends Person: 表示Person类型或者Person类型的任意子类型
        ? extends E: 表示E类型或者E类型的任意子类型

示例:泛型上限的使用

在这里插入图片描述

//Person类
public class Person {
    private String name;
    private int age;

    //生成空参,满参构造,set和get方法
    public Person() {
    }

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    @Override
    public String toString() {
        return "Person{" + "name='" + name + '\'' + ", age=" + age + '}';
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }
}
//Worker类 工人
public class Worker extends Person {
    //根据父类生成空参,满参构造
    public Worker() {
    }

    public Worker(String name, int age) {
        super(name, age);
    }

    @Override
    public String toString() {
        return "Worker{" + "name='" + getName() + '\'' + ", age=" + getAge() + '}';
    }
}
//Teacher类
public class Teacher extends Worker {
    //根据父类生成空参,满参构造
    public Teacher() {
    }

    public Teacher(String name, int age) {
        super(name, age);
    }

    @Override
    public String toString() {
        return "Teacher{" + "name='" + getName() + '\'' + ", age=" + getAge() + '}';
    }
}
//JavaTeacher类
public class JavaTeacher extends Teacher {
    public JavaTeacher() {
    }
    //根据父类生成空参,满参构造

    public JavaTeacher(String name, int age) {
        super(name, age);
    }

    @Override
    public String toString() {
        return "JavaTeacher{" + "name='" + getName() + '\'' + ", age=" + getAge() + '}';
    }
}

测试类:

@SuppressWarnings("all") //抑制警告
public class Demo01GenericShangXian {
    public static void main(String[] args) {
        ArrayList<Person> list1 = new ArrayList<>();
        list1.add(new Person("zs", 18));
        list1.add(new Person("ls", 28));
        list1.add(new Person("ww", 38));

        ArrayList<Worker> list2 = new ArrayList<>();
        list2.add(new Worker("zs01", 18));
        list2.add(new Worker("ls01", 28));
        list2.add(new Worker("ww01", 38));

        ArrayList<Teacher> list3 = new ArrayList<>();
        list3.add(new Teacher("zs02", 18));
        list3.add(new Teacher("ls02", 28));
        list3.add(new Teacher("ww02", 38));

        ArrayList<String> list4 = new ArrayList<>();
        list4.add("aaa");
        list4.add("bbb");

        ArrayList<Integer> list5 = new ArrayList<>();
        list5.add(100);
        list5.add(200);


        print(list1);
        print(list2);
        print(list3);
        // print(list4); //错误:String不是Person的子类
        // print(list5); //错误:Integer不是Person的子类
    }

    /**
     *  定义一个方法,只能完成以下3个集合的遍历 
     *       ArrayList<Person>、ArrayList<Worker>、ArrayList<Teacher>
     *       Worker是Person的子类,Teacher也是Person的子类
     *       ? extends Person:  代表Person类型或者Person类型的任意子类类型
     * @param list
     */
    public static void print(ArrayList<? extends Person> list) {
        for (Person person : list) {
            System.out.println(person);
        }
		 System.out.println("---------------");
    }
}

在这里插入图片描述

4.2、泛型的下限

一个子类的父类可以有任意多个,如何表示一个子类的任意父类类型呢?(找爹)

泛型的上限:
// ?: 代表的是任意一种引用类型
        ? super JavaTeacher: 代表JavaTeacher类型或者JavaTeacher类型的任意父类类型
        ? super T:           代表T类型或者T类型的任意父类类型

示例:泛型下限的使用

在这里插入图片描述

//Person类
public class Person {
    private String name;
    private int age;

    //生成空参,满参构造,set和get方法
    public Person() {
    }

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    @Override
    public String toString() {
        return "Person{" + "name='" + name + '\'' + ", age=" + age + '}';
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }
}
//Worker类 工人
public class Worker extends Person {
    //根据父类生成空参,满参构造
    public Worker() {
    }

    public Worker(String name, int age) {
        super(name, age);
    }

    @Override
    public String toString() {
        return "Worker{" + "name='" + getName() + '\'' + ", age=" + getAge() + '}';
    }
}
//Teacher类
public class Teacher extends Worker {
    //根据父类生成空参,满参构造
    public Teacher() {
    }

    public Teacher(String name, int age) {
        super(name, age);
    }

    @Override
    public String toString() {
        return "Teacher{" + "name='" + getName() + '\'' + ", age=" + getAge() + '}';
    }
}
//JavaTeacher类
public class JavaTeacher extends Teacher {
    public JavaTeacher() {
    }
    //根据父类生成空参,满参构造

    public JavaTeacher(String name, int age) {
        super(name, age);
    }

    @Override
    public String toString() {
        return "JavaTeacher{" + "name='" + getName() + '\'' + ", age=" + getAge() + '}';
    }
}

测试类:

public class Demo02GenericXiaXian {
    public static void main(String[] args) {
        ArrayList<Person> list1 = new ArrayList<>();
        list1.add(new Person("zs", 18));
        list1.add(new Person("ls", 28));
        list1.add(new Person("ww", 38));

        ArrayList<Worker> list2 = new ArrayList<>();
        list2.add(new Worker("zs01", 18));
        list2.add(new Worker("ls01", 28));
        list2.add(new Worker("ww01", 38));

        ArrayList<Teacher> list3 = new ArrayList<>();
        list3.add(new Teacher("zs02", 18));
        list3.add(new Teacher("ls02", 28));
        list3.add(new Teacher("ww02", 38));

        ArrayList<String> list4 = new ArrayList<>();
        list4.add("aaa");
        list4.add("bbb");

        ArrayList<Integer> list5 = new ArrayList<>();
        list5.add(100);
        list5.add(200);

        ArrayList<JavaTeacher> list6 = new ArrayList<>();
        list3.add(new JavaTeacher("zs02", 18));
        list3.add(new JavaTeacher("ls02", 28));
        list3.add(new JavaTeacher("ww02", 38));


        print(list1);
        print(list2);
        print(list3);

        // print(list4); //错误:String不是Teacher的父类
        // print(list5); //错误:Integer不是Teacher的父类
        // print(list6); //错误:JavaTeacher不是Teacher的父类
    }

    /**
     * 使用泛型下限变量一下三个集合定义一个方法
     *       ArrayList<Person> list1、ArrayList<Worker> list2 、ArrayList<Teacher> list3
     *
     * @param list
     */
    public static void print(ArrayList<? super Teacher> list) {
        for (Object o : list) {
            System.out.println(o);
        }
           System.out.println("---------------");
    }
}

在这里插入图片描述


五、List集合


1、List概述

java.util.List接口继承了java.util.Collection接口,因此List接口的实现类都实现了Collection接口的方法,所以List接口的实现类对象都可以调用来自于Collection接口的方法。

List集合的特点:有序(即存取元素的顺序一致)、有索引(可以通过索引方式获取元素)、可重复(可以存储相同的元素)。

List接口的常用实现类:ArrayList、LinkedList、CopyOnWriteArrayList(并发)。


2、List集合常用方法

List接口,除了拥有父接口的方法外,额外添加了一些与索引相关的方法。CRUD操作

方法签名:是用来区分不同方法的标示符,由方法名称和参数列表组成。例如public int add(int a,int b)的方法签名是 add(int a,int b)


2.1、根据索引添加元素

public void add(int index,E element); //往集合中指定的索引添加元素。

public boolean addAll(int index,Collection<? extends E> c); //往集合中指定索引处添加指定集合。

示例:

public class ListTest01 {
    public static void main(String[] args) {
    	// 创建一个List集合,并指定存储String类型数据
        List<String> list = new ArrayList<>(); //多态
        // 向List集合中添加数据
        list.add("北京");
        list.add("广东");
        list.add("深圳");
        list.add("杭州");
        list.add("武汉");
        System.out.println(list.size());//size:5(集合中元素的个数)
        System.out.println(list); //[北京, 广东, 深圳, 杭州, 武汉]

        System.out.println("---------");

        list.add(1, "上海"); //向指定索引处插入元素(存在元素移位操作)
        System.out.println(list.size()); //size:6
        System.out.println(list); //[北京, 上海, 广东, 深圳, 杭州, 武汉]

        System.out.println("---------");

        List<String> list2 = new ArrayList<>();
        list2.add("南京");
        list2.add("成都");
        list.addAll(4, list2); //往list集合中指定索引处添加list2集合
        System.out.println(list.size()); //8
        System.out.println(list);//[北京, 上海, 广东, 深圳, 南京, 成都, 杭州, 武汉]
    }
}

在这里插入图片描述


2.2、根据索引获取元素

public E get(int index);         //获取集合中指定索引的元素。

public int indexOf(Object o);    //获取集合中指定元素第一次出现的索引

public int lastIndexOf(Object o);//获取集合中指定元素最后一次出现的索引

示例:

public class ListTest01 {
    public static void main(String[] args) {
        List<String> list = new ArrayList<>();
        list.add("北京");
        list.add("广东");
        list.add("深圳");
        list.add("杭州");
        list.add("武汉");

        // 通过索引方式获取集合中的元素
        System.out.println(list.get(0)); //北京
        System.out.println(list.get(1)); //广东
        System.out.println(list.get(2)); //深圳

        System.out.println("-------");
        // 使用for+get(索引)方式遍历集合
        for (int i = 0; i < list.size(); i++) {
            System.out.println(list.get(i));
        }
    }
}

在这里插入图片描述


2.3、根据索引删除元素

public E remove(int index)  //删除集合中指定索引的元素,返回值是被删除的元素。

示例:

public class ListTest01 {
    public static void main(String[] args) {
        List<String> list = new ArrayList<>();
        list.add("北京");
        list.add("广东");
        list.add("深圳");
        list.add("杭州");
        list.add("武汉");
        System.out.println(list);
        String city = list.remove(3);
        System.out.println("被删除的元素是:" + city);
        System.out.println("--------");
        System.out.println(list);
    }
}

在这里插入图片描述


2.4、根据索引修改元素

public E set(int index,E element); //修改集合中指定索引的元素,返回值是被修改的元素。

示例:

public class ListTest01 {
    public static void main(String[] args) {
        List<String> list = new ArrayList<>();
        list.add("北京");
        list.add("广东");
        list.add("深圳");
        list.add("杭州");
        list.add("武汉");
        System.out.println(list);
        String str = list.set(2, "西安");
        System.out.println("修改的城市是:"+str);
        System.out.println("--------");
        System.out.println(list);
    }
}

在这里插入图片描述


2.5、截取指定范围的元素

public List<E> subList(int fromIndex, int toIndex); //截取指定范围的元素,并返回对应的集合对象 ,需注意:包含fromIndex对应的元素,不包含toIndex对应的元素。[fromIndex,toIndex)
public class ListTest01 {
    public static void main(String[] args) {
        List<String> list = new ArrayList<>();
        list.add("AAAA");
        list.add("BBBB");
        list.add("CCCC");
        list.add("DDDD");
        list.add("EEEE");
        System.out.println("list:" + list);
        System.out.println("-------");
        
        // 按照索引为[2,list.size() - 1]截取,并返回一个新的集合对象
        List<String> list2 = list.subList(2, list.size() - 1);
        System.out.println("list2:" + list2);
    }
}

在这里插入图片描述


3、List集合遍历、排序


3.1、List集合的遍历

Collection集合支持两种遍历方式:

  • Iterator迭代器。
  • 增强for循环。

List接口继承了Collection接口,以上两种遍历方式也适用于List,除次以外,List集合还支持for循环+get(索引)方式遍历。

List集合支持的三种遍历方式如下:

  • Iterator迭代器。
  • 增强for循环。
  • for循环+get(索引)。

3.2、List集合的排序

// List接口的默认方法,由接口的实现类对象调用。
// 功能:对集合排序
// Comparator比较器,里面有排序规则compare()方法
default void sort(Comparator<? super E> c) {
    Object[] a = this.toArray();
    Arrays.sort(a, (Comparator) c);
    ListIterator<E> i = this.listIterator();
    for (Object e : a) {
        i.next();
        i.set((E) e);
    }
}

示例:使用List接中的sort()方法对集合排序

// 方式一:实体类实现Comparator接口,重写compare()方法
// 学生类
public class Student implements Comparator<Student> {
    private String name;
    private int age;
    private int height;

    public Student(String name, int age, int height) {
        this.name = name;
        this.age = age;
        this.height = height;
    }

    public Student() {
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    @Override
    public String toString() {
        return "{ name:" + name + ",age:" + age + ",height:" + height + "}";
    }

    // 自定义排序规则
    @Override
    public int compare(Student o1, Student o2) {
       /* 
          第一个参数 - 第二个参数: 升序排列
          第二个参数 - 第一个参数: 降序排列
        */
        return o1.height-o2.height; //需求按照身高升序排列
    }
}


//测试类
public class ListTest02 {
    public static void main(String[] args) {
        ArrayList<Student> list = new ArrayList<>();
        list.add(new Student("张三", 18, 175));
        list.add(new Student("李四", 20, 170));
        list.add(new Student("王五", 16, 165));

        System.out.println(list);
        System.out.println("---------------");
        System.out.println("按照身高升序排列:");
        list.sort(new Student());
        System.out.println(list);
    }
}

在这里插入图片描述


//方式二:使用匿名内部对集合排序
//学生类
public class Student{
    private String name;
    private int height;
    private double weight;

    public Student(String name, int height, double weight) {
        this.name = name;
        this.height = height;
        this.weight = weight;
    }

    public Student() {
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getHeight() {
        return height;
    }

    public void setHeight(int height) {
        this.height = height;
    }

    public double getWeight() {
        return weight;
    }

    public void setWeight(double weight) {
        this.weight = weight;
    }

    @Override
    public String toString() {
        return "{ name:" + name + ",height:" + height + ",weight:" + weight + "}";
    }

}


//测试类
public class ListTest02 {
    public static void main(String[] args) {
        ArrayList<Student> list = new ArrayList<>();
        list.add(new Student("张三", 18, 100));
        list.add(new Student("李四", 20, 60));
        list.add(new Student("王五", 16, 97));

        System.out.println(list);
        System.out.println("---------------");
        System.out.println("按照体重降序排列:");
        list.sort(new Comparator<Student>() {
            @Override
            public int compare(Student o1, Student o2) {
            	// 解决浮点运算精度问题
                return Double.compare(o2.getWeight(),o1.getWeight());
            }
        });
        System.out.println(list);
    }
}

在这里插入图片描述


4、ListIterator(更强大的迭代器)


  • java.util.Listlterator接口继承了java.util.Iteator接口。

  • Iterator接口主要用于遍历Collection集合,例如ArrayList、HashSet等等。

  • Listlterator接口主要用于遍历List集合,例如ArrayList、LinkedList…

  • 由于Listlterator继承了Iterator,所以lterator有的功能Listlterator也有,除此之外它还增加了一些功能。例如在遍历集合的时候可以添加、修改元素,还可以反向遍历List集合;以及获取下一个元素和上一个元素的索引。

  • 如果你在遍历List集合的时候需要添加、或则修改元素,那么就可以使用Listlterator。

在这里插入图片描述

4.1、ListIterator常用方法


(1) 继承于Iterator中的方法:

  • boolean hasNext(): 如果仍有元素可以迭代,则返回 true。

  • E next(): 返回迭代的下一个元素。

  • void remove(): 使用迭代器删除集合中的元素,它会自动更新迭代器,并且更新集合。

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


        // 获取ListIterator对象
        ListIterator<Integer> lit = list.listIterator();

        System.out.println("使用ListIterator正向遍历List集合:");

        // 快捷键: itit+tab键 自动生成下面while这一堆
        while (lit.hasNext()) {
            Integer value = lit.next();
            if (value == 4) {
                // 删除4
                lit.remove();
            } else {
                System.out.println(value);
            }

        }
        System.out.println("当前list集合的元素内容是:" + list);

    }
}

在这里插入图片描述


(2) ListIterator特有的方法:

  • boolean hasPrevious() :是否有上一个元素。
  • E previous() :返回上一个元素。

注意:在反向遍历之前需要先正向遍历,否则反向遍历失败。

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

        // 获取ListIterator对象
        ListIterator<Integer> lit = list.listIterator();

        System.out.println("使用ListIterator正向遍历List集合:");
        // 在反向遍历之前需要先正向遍历,否则反向遍历失败
        // 正向遍历
        while (lit.hasNext()) {
            Integer value =  lit.next();
            System.out.println(value);
        }

        /*
            反向遍历
         */
        System.out.println("使用ListIterator反向遍历List集合:");
        while (lit.hasPrevious()){
            Integer previousValue = lit.previous();
            System.out.println(previousValue);
        }
    }
}

在这里插入图片描述
执行流程图:
在这里插入图片描述
在这里插入图片描述


  • int nextIndex():获取下一个元素的索引。
  • int previousIndex():获取上一个元素的索引。
public class ListTest04 {
    public static void main(String[] args) {
        ArrayList<Integer> list = new ArrayList<>();
        list.add(0);
        list.add(1);
        list.add(2);
        list.add(3);
        list.add(4);


        // 获取ListIterator对象
        ListIterator<Integer> lit = list.listIterator();

        // 正向遍历
        while (lit.hasNext()) {                      
            Integer value = lit.next();     //下一个元素的值
            int nextIndex = lit.nextIndex();//下一个元素的索引
            System.out.println("当前元素的值是:" + value + ",下一个索引是:" + nextIndex);
        }

        System.out.println("---------");

        // 反向遍历
        while (lit.hasPrevious()) {
            Integer previousValue = lit.previous(); //上一个元素的值
            int previousIndex = lit.previousIndex();//上一个元素的索引
            System.out.println("当前元素的值是:" + previousValue + ",上一个索引是:" + previousIndex);
        }
    }
}

在这里插入图片描述



  • void set(E e): 添加元素。
  • void add(E e): 修改元素。
public class ListTest04 {
    public static void main(String[] args) {
        ArrayList<Integer> list = new ArrayList<>();
        list.add(0);
        list.add(1);
        list.add(2);
        list.add(3);
        list.add(4);


        // 获取ListIterator对象
        ListIterator<Integer> lit = list.listIterator();

        // 正向遍历 快捷键init
        while (lit.hasNext()) {
            Integer value = lit.next();

            // 将值为4的元素改成666
            if (value == 4) {
                lit.set(88);
            }

            if (value == 3) {
                // 向集合中元素值为3的后面添加一个元素,元素值为5
                lit.add(5);
            }

            // 此处打印输出的是迭代器遍历得到的值
            System.out.println(value);
        }


        System.out.println("---------");

        System.out.println(list);

       /* // 反向遍历
        while (lit.hasPrevious()) {
            Integer previousValue = lit.previous(); //上一个元素的值
            int previousIndex = lit.previousIndex();//上一个元素的索引
            System.out.println("当前元素的值是:" + previousValue + ",上一个索引是:" + previousIndex);
        }*/
    }
}

在这里插入图片描述


5、ArrayList


ArrayList是List接口的实现类,所以List接口中的方法ArrayList都可以使用。

在这里插入图片描述

ArrayList和Vector都是List接口的实现类,他们都是基于数组实现的,不同点是一个线程安全、一个线程不安全,其特点如下:

ArrayList的特点:

  • 基于数组实现的(例如 transient Object[] elementData;
  • 查询快: 元素有索引,元素内存空间连续分配。
  • 增删慢: (数组长度是不可改变,需要频繁创建新数组,拷贝元素,销毁老数组。
  • 线程不同步,不安全,效率高。

Vector的特点:

  • 基于数组实现的。
  • 查询快: 元素有索引,元素内存空间连续分配。
  • 增删慢: (数组长度是不可改变,需要频繁创建新数组,拷贝元素,销毁老数组。
  • 线程同步,安全,效率低。

ArrayList构造器:

在这里插入图片描述


ArrayList常用API:


public boolean add(E e)                  // 将指定的元素添加到此集合的末尾

public void add(int index,E element)     // 往集合中的指定索引位置处添加一个数据

public E get(int index)                  //返回指定索引处的元素

public int size()                        //返回集合中的元素的个数

public E remove(int index)               //删除指定索引处的元素,返回被删除的元素

public boolean remove(Object o)          // 删除指定的元素,返回删除是否成功

public E set(int index,E element)        // 修改指定索引处的元素,返回被修改的元素


1、ArrayList定义如下:

在这里插入图片描述

// 继承了AbstractList抽象类
public class ArrayList<E> extends AbstractList<E>
        implements List<E>, RandomAccess, Cloneable, java.io.Serializable{ 
    //默认初始容量
    private static final int DEFAULT_CAPACITY = 10; 
    //默认元素集合
    private static final Object[] EMPTY_ELEMENTDATA = {}; 
    //无参构造实例默认元素集合
    private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
    //存放元素的数组
    transient Object[] elementData; 
    //记录ArrayList中存储的元素的个数
    private int size;
    ...    
}   

2、ArrayList的构造方法:

在这里插入图片描述

// 默认构造器
public ArrayList() {
    this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA; //空数组
}

// 可以指定容量大小的构造器
public ArrayList(int initialCapacity) {
    if (initialCapacity > 0) {
        this.elementData = new Object[initialCapacity];
    } else if (initialCapacity == 0) {
        this.elementData = EMPTY_ELEMENTDATA;
    } else {
        throw new IllegalArgumentException("Illegal Capacity: "+
                                           initialCapacity);
    }
}

// 传入一个Collection对象
public ArrayList(Collection<? extends E> c) {
    elementData = c.toArray();
    if ((size = elementData.length) != 0) {
        // c.toArray might (incorrectly) not return Object[] (see 6260652)
        if (elementData.getClass() != Object[].class)
            elementData = Arrays.copyOf(elementData, size, Object[].class);
    } else {
        // replace with empty array.
        this.elementData = EMPTY_ELEMENTDATA;
    }
}

三个构造方法目的都是为初始化elementData,一般我们使用无参构造器即可。如果你已经确定了容量大小可以使用第二种构造方法(超过10个就用这种方法),避免频繁扩容。


3、add()方法:

//从尾部插入数据
public boolean add(E e) {
    // 判断是否需要扩容
    ensureCapacityInternal(size + 1);  // Increments modCount!!
    // 在数组后面添加元素e
    elementData[size++] = e;
    return true;
}

ensureCapacityInternal()方法:判断是否扩容。

// 得到最小的扩容量
private void ensureCapacityInternal(int minCapacity) {
    ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}

// 判断是否需要扩容
private void ensureExplicitCapacity(int minCapacity) {
    modCount++;
    // /如果所需容量大于现数组容量,则进行扩容
    if (minCapacity - elementData.length > 0)
        grow(minCapacity);
}

4、grow()方法是ArrayList扩容的核心方法:

// 扩容机制
private void grow(int minCapacity) {
    // 老容量长度
    int oldCapacity = elementData.length;
    // 新容量长度 = 老容量长度+(老容量长度/2),右移一位相当于除以2
    // 先扩容1.5倍,当增加后还是不够用,则直接使用所需要的长度作为数组的长度。
    int newCapacity = oldCapacity + (oldCapacity >> 1);
    if (newCapacity - minCapacity < 0)
        newCapacity = minCapacity;
    if (newCapacity - MAX_ARRAY_SIZE > 0)
        newCapacity = hugeCapacity(minCapacity);
    // 数组拷贝
    elementData = Arrays.copyOf(elementData, newCapacity);
}

如果在实例化ArrayList对象的时候没有传递初始化的长度,那么它默认会使用一个空数组;当插入数据发现数组容量不够时,则会判断当前增长的容量与默认的容量大小,较大的一个数值作为新的数组开辟。所以得出一个结论:

版本说明
JDK1.9之后ArrayList默认的构造器,只会使用默认的空数组,使用的时候才会开辟数组(比如 add 后进行扩容),默认开辟长度为10。
JDK1.9之前ArrayList默认的构造器,实际上就会默认开辟大小为10的数组。

5、remove()方法:

// 删除指定内容的数据
public boolean remove(Object o) {
    if (o == null) {
       	// 删除数组中为null的数据
        for (int index = 0; index < size; index++)
            if (elementData[index] == null) {
                fastRemove(index); // 删除
                return true;
            }
    } else {
        // 删除数组中指定的数据
        for (int index = 0; index < size; index++)
            if (o.equals(elementData[index])) {
                fastRemove(index); // 删除
                return true; 
            }
    }
    return false;
}

通过循环遍历元素是否相等然后进行删除。因为ArrayList 允许空值,所以源码这里进行了多一次的判断是否为null的情况。可以看到核心删除方法是fastRemove。

private void fastRemove(int index) {
    modCount++;
    // 删除元素的位置
    int numMoved = size - index - 1;5 
    if (numMoved > 0)
        //数组拷贝,元素向前移动一位
        System.arraycopy(elementData, index+1, elementData, index,
                         numMoved);
    // size-1,最后一个元素赋值null
    elementData[--size] = null; 
}

6、LinkedList


6.1、LinkedList介绍

LinkedList也是List接口的实现类,底层基于链表实现的。

LinkedList的特点:增删快、查询慢、线程不安全。

  • 增删快: 不需要创建新的链表,只需要修改链表中节点保持的地址值即可。
  • 查询慢: 要么从前向后查,要么从后向前查 (依赖查询算法,从前往后查:编号<数量/2、从后往前查:编号>=数量/2)
  • 线程不同步,不安全,效率高。

在这里插入图片描述


6.2、LinkedList集合的使用


1、在LinkedList集合的首部和尾部添加元素。

public void addFirst(E e) //在LinkedList集合的首部添加元素
 public void addLast(E e) //在LinkedList集合的尾部添加元素
public class LinkedListDemo1 {
    public static void main(String[] args) {
        LinkedList<String> linkedlist = new LinkedList<>();
        linkedlist.add("北京");
        linkedlist.add("上海");
        linkedlist.add("广东");
        linkedlist.add("深圳");
        System.out.println("当前linkedlist集合的元素内容是:"+linkedlist);

        linkedlist.addFirst("杭州"); //在linkedlist集合的头部添加元素
        linkedlist.addLast("南京");  //在linkedlist集合的尾部添加元素

        System.out.println("当前linkedlist集合的元素内容是:"+linkedlist);

    }
}

在这里插入图片描述


2、获取LinkedList集合的首部元素和尾部元素。
 public E getFirst() //获取LinkedList集合的第一个元素。
 public E getLast()  //获取LinkedList集合的最后一个元素。
public class LinkedListDemo1 {
    public static void main(String[] args) {
        LinkedList<String> linkedlist = new LinkedList<>();
        linkedlist.add("北京");
        linkedlist.add("上海");
        linkedlist.add("广东");
        linkedlist.add("深圳");
        System.out.println("当前linkedlist集合的元素内容是:"+linkedlist);

        System.out.println("当前linkedlist集合的第一个元素内容是:"+linkedlist.getFirst());
        System.out.println("当前linkedlist集合的最后一个元素内容是:"+linkedlist.getLast());

    }
}


3、移除LinkedList集合的首部元素和尾部元素。

 public E removeFirst() //移除并返回LinkedList集合的第一个元素。
 public E removeLast()  //移除并返回LinkedList集合的最后一个元素。
public class LinkedListDemo1 {
    public static void main(String[] args) {
        LinkedList<String> linkedlist = new LinkedList<>();
        linkedlist.add("北京");
        linkedlist.add("上海");
        linkedlist.add("广东");
        linkedlist.add("深圳");
        System.out.println("当前linkedlist集合的元素内容是:" + linkedlist);

        String first = linkedlist.removeFirst();
        System.out.println("移除linkedlist集合第一个元素内容是:" + first);
        String last = linkedlist.removeLast();
        System.out.println("移除linkedlist集合最后一个元素内容是:" + last);

        System.out.println("当前linkedlist集合的元素内容是:" + linkedlist);
    }
}

在这里插入图片描述


4、添加和删除首部元素

  • public void push(E e) : 在LinkedList集合的首部添加元素,等价于addFirst(Ee)
  • public E pop() : 删除LinkedList集合的首部元素,等价于removeFirst()

六、Set集合


1、Set集合的介绍

Set集合的特点:

  • 无索引:不可以通过索引的方式获取元素。
  • 元素唯一,不允许重复。(如何保证元素唯一?依赖hashCode方法和equals方法)
  • 注意 : 不能直接说Set集合是有序/无序的,因为它到底有序/无序,要看具体实现类,如HashSet就是无序的,LinkedHashSet就是有序的。

在这里插入图片描述

Set接口继承Collection接口,它没有在Collection接口之上增加额外的方法。


Set集合的常用实现类的特点:

  • HashSet:无序、不重复、无索引。
  • LinkedHashSet:有序、不重复、无索引。
  • TreeSet:排序、不重复、无索引。

2、HashSet集合


HashSet实现了Set接口,HashSet的特点如下:

  • 无索引,没有基于索引操作集合的方法。
  • 元素唯一,元素不允许重复。
  • 无序的(存储和取出的顺序不一致)。
public class HashSetTest1 {
    public static void main(String[] args) {
        HashSet<String> hashSet = new HashSet<>();
        hashSet.add("Java");
        hashSet.add("Go");
        hashSet.add("php");
        hashSet.add("Rust");
        hashSet.add("Java");
        System.out.println("hashSet集合元素内容是:"+hashSet);
    }
}

在这里插入图片描述

HashSet底层数据结构是哈希表,不同的JDK版本,哈希表的组成结构是不一样的。

哈希表的组成结构如下:

  • JDK8以前(不包括JDK8),哈希表由数组+链表组成。
  • JDK8以后(包括JDK8),如果链表元素的数量没有超过8个,那么还是由数组+链表组成;如果链表元素的数量超过8个并且数组长度>=64(会把链表变换成红黑树,其目的是提高查询速度),哈希表由数组+链表+红黑树组成。

在这里插入图片描述


哈希值(hashCode):

  • 是JDK根据对象的地址,按照某种规则计算出来的int类型数值。
  • 如何获取对象的哈希值:使用Object类的hashCode()方法。

对象的哈希值特点:

  • 同一个对象多次调用hashCode()方法返回的哈希值是相同的。
  • 默认情况下,不同对象的哈希值是不同的。
// 获取对象的哈希值
public class SetDemo2 {
    public static void main(String[] args) {
        String name = "小明";
        System.out.println(name.hashCode()); // 756703
        System.out.println(name.hashCode()); // 756703
        String name1 = "小张";
        String name2 = "小明";
        System.out.println(name1.hashCode()); // 754929
        System.out.println(name2.hashCode()); // 756703
    }
}

String的哈希值算法

  • String内部是字符数组 private final char value[];
  • 例如:Stirng str =“abc”,对应的字符数组是:char ch ={‘a’,‘b’,‘c’}。
  • String类重写了hashCode方法,通过遍历字符串的每个字符来计算出字符串对象的hash值。

String类的hashCode方法:

// String根据每个字符的ASCII码值,简单相加获取哈希值 
public int hashCode() {
        int h = hash;//hash是String的int类型成员变量,默认值0
        if (h == 0 && value.length > 0) {
            char val[] = value;
            for (int i = 0; i < value.length; i++) {//遍历String内部的字符数组
                h = 31 * h + val[i];
            }
            hash = h;
        }
        return h;
    }
    
/*
    h = 31 * 0 + 97 = 97

    h = 31 * 97 + 98 = 3105

    h = 31 * 3105 + 99 = 96354
*/
public class Demo05StringHashCode {
    public static void main(String[] args) {
        String s = "abc";
        System.out.println(s.hashCode());//96354
    }
}


Object类的toString()方法

如果定义的类,没有重写toString()方法,默认返回的是对象的地址值, 但本质是对象的哈希值。
在这里插入图片描述


3、Set集合保证元素唯一性的原理


Set集合保证元素唯一性依靠集合存储对象的两个方法:hashCode()方法和equals()方法,这两个方法是来自于Object类的,因此集合的元素都有这两个方法。

  • public native int hashCode(): 返回该对象的哈希码值。
  • public boolean equals(Object obj) : 判断对象的地址是否相等。

Set集合保证元素唯一的流程:

1、当Set集合添加元素的时候,会调用该元素的 hashCode() 方法来计算哈希值,判断该哈希值对应的位置上是否有相同哈希值相同的元素。如果该位置上没有哈希值相同的元素,那么就将该元素存储到Set集合中;如果该位置上有哈希值相同的元素,那么就会产生哈希冲突。

2、如果产生了哈希冲突(哈希碰撞),然后调用元素的 equals() 方法和该位置上的所有元素进行比较。如果比较完成后该位置上的任意一个元素和要存储的元素相等,那么就不存存储,否则就存储。

在日常开发中,如果使用Set集合存储自定义对象,而且想要保证数据的唯一性,那么对象所属类必须重写equals()和hashCode()方法。(根据业务规则去重写这两个方法)

public class HashSetTest1 {
    public static void main(String[] args) {
        HashSet<String> hashSet = new HashSet<>();
        hashSet.add("Java");
        hashSet.add("Go");
        hashSet.add("php");
        hashSet.add("Rust");
        hashSet.add("Java");
        for (String str : hashSet) {
            System.out.println("集合元素的值:"+str+",集合元素的哈希值:"+str.hashCode());
        }
    }
}

在这里插入图片描述
在这里插入图片描述


HashSet集合存储元素的过程:

1、计算哈希值,使用哈希值%数组长度,计算在数组中存储的索引。
2、判断该索引下是否有元素。
3、没有元素: 直接存储。
4、如果有元素:调用equals方法( true: 不存储、 false: 存储)
HashSet<string> set = new HashSet<>(60);
//问题:内部的创建的数组长度真的是60吗?如果不是应该是多少?
//内部创建数组的长度:>=给定的数字的2的次方的一个最小值
//60:32 64 128 256
//64(2的6次方)>60,所以内部创建的数组长度是64

测试:HashSet集合存储对象所属的类没有重写hashCode()和equals()方法。

// 定义一个学生类,没有重写equals和hashCode方法
public class Student {
    // 定义成员变量
    private String name;
    private int age;

    // 有参构造
    public Student(String name, int age) {
        this.name = name;
        this.age = age;
    }

    // 无参构造
    public Student() {
    }

    
    // set/get方法
    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    
    // 重写toString方法
    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}

// 测试类
public class HashSetTest2 {
    public static void main(String[] args) {
        HashSet<Student> set = new HashSet<>();
        set.add(new Student("张三", 18));
        set.add(new Student("李四", 20));
        set.add(new Student("张三", 18));
        set.add(new Student("王五", 19));

        System.out.println(set);
    }
}

在这里插入图片描述


测试:HashSet集合存储对象所属的类重写hashCode()和equals()方法。

// 定义学生类,重写hashCode()和equals()方法。w
public class Student {
    // 定义成员变量
    private String name;
    private int age;

    // 有参构造
    public Student(String name, int age) {
        this.name = name;
        this.age = age;
    }

    // 无参构造
    public Student() {
    }


    // set/get方法
    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }


    // 重写toString方法
    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Student student = (Student) o;
        return age == student.age && Objects.equals(name, student.name);
    }

    @Override
    public int hashCode() {
        return Objects.hash(name, age);
    }
}


// 测试类
public class HashSetTest2 {
    public static void main(String[] args) {
        HashSet<Student> set = new HashSet<>();
        set.add(new Student("张三", 18));
        set.add(new Student("李四", 20));
        set.add(new Student("张三", 18));
        set.add(new Student("王五", 19));

        System.out.println(set);
    }
}

在这里插入图片描述


4、LinkedHashSet集合


在这里插入图片描述

LinkedHashSet继承自HashSet,底层也是基于哈希表实现的。

LinkedHashSet集合的特点:

  • 无索引,没有基于索引操作集合元素的方法。
  • 元素唯一,集合中的元素不可重复。
  • 有序,存储和取出的顺序一致。
public class LinkedHashSetTest01 {
    public static void main(String[] args) {
        Set<String> linkedHashSet = new LinkedHashSet<>();
        linkedHashSet.add("Java");
        linkedHashSet.add("C++");
        linkedHashSet.add("C#");
        linkedHashSet.add("Go");
        linkedHashSet.add("Java");
        linkedHashSet.add("Rust");
        System.out.println("当前linkedHashSet集合元素的内容是:"+linkedHashSet);
    }
}

在这里插入图片描述
LinkedHashSet的用法跟HashSet类似,只不过LinkedHashSet集合是有序的。


5、TreepSet集合


在这里插入图片描述

TreepSet集合是Set集合的实现类,底层基于红黑树实现的,TreepSet集合的特点如下:

  • 无索引,没有基于索引操作集合元素的方法。
  • 元素唯一,集合中的元素不可重复。
  • 有序,添加元素时按照元素对应的默认排序规则排序。(元素必须实现Comparable接口重写compareTo()方法

示例:TreeSet集合存储String类型数据,默认按照字符串的字典顺序升序排列

public class TreeSetTest {
    public static void main(String[] args) {
        Set<String> treeSet = new TreeSet<>();
        treeSet.add("Java");
        treeSet.add("C++");
        treeSet.add("C#");
        treeSet.add("Go");
        treeSet.add("Java");
        treeSet.add("Rust");
        // 字符串默认排序是按照字典顺序升序排列 a到z
        System.out.println("当前treeSet集合元素的内容是:"+treeSet);
    }
}

在这里插入图片描述


示例:TreeSet集合存储String类型数据,按照字符串的字典顺序降序排列

public class TreeSetTest2 {
    public static void main(String[] args) {
        // 
        Set<String> treeSet = new TreeSet<>(new Comparator<String>() {
            @Override
            public int compare(String o1, String o2) {
                // 整数的降序:后面减前面
                // 小数的降序:Double.compare(o2,o1)
                // 字符串的降序:o2.compareTo(o1)
                return o2.compareTo(o1);
            }
        });
        treeSet.add("Java");
        treeSet.add("C++");
        treeSet.add("C#");
        treeSet.add("Go");
        treeSet.add("Java");
        treeSet.add("Rust");
        System.out.println("当前treeSet集合元素的内容是:" + treeSet);
    }

在这里插入图片描述


七、Collections类


1、Collections类介绍

java.util.Collections类表示集合工具类,它的构造器是私有的,里面包含了一些操作集合的静态方法(工具方法)。


2、Collections类常用方法

2.1、往指定的集合添加多个元素

addAll(Collection<? super T> c, T... elements) //往指定的集合添加多个元素
public class CollectionsTest {
    public static void main(String[] args) {
        testAddAll();
    }


    /**
     * 往指定的集合添加多个元素
     */
    private static void testAddAll() {
        List<Integer> list = new ArrayList<>();
        Collections.addAll(list, 1, 2, 3, 4, 56, 7);
        System.out.println("当前list集合的元素内容是:" + list);
    }
}

在这里插入图片描述

2.2、打乱集合元素的顺序

shuffle(List<?> list) //参数是List接口实现类
public class CollectionsTest {
    public static void main(String[] args) {
        testShuffle();
    }

    /**
     * 打乱集合的元素
     */
    private static void testShuffle() {
        List<Integer> list = new ArrayList<>();
        for (int i = 0; i < 5; i++) {
            list.add(i);
        }
        System.out.println("当前list集合的元素内容是:" + list);
        Collections.shuffle(list);

        System.out.println("打乱之后的list集合的元素内容是:" + list);
    }
}

在这里插入图片描述
自己用Random也能实现。


2.3、集合元素的排序

sort(List<T> list) //按照默认的规则进行排序
//规则是由元素所属的类(例如 Integer 实现了Comparable接口重写了compareTo()方法)
//方法参数是List集合的实现类
sort(List<T> list, Comparator<? super T> c) //使用匿名内部类方式自定义排序规则(匿名内部类也是Comparator的子类/实现类)

在这里插入图片描述

public class CollectionsTest {
    public static void main(String[] args) {
        testSort();
    }


    /**
     * 集合元素的排序 对整数进行排序 默认规则是升序
     */
    private static void testSort() {
        List<Integer> list = new ArrayList<>();
        for (int i = 10; i >=0; i--) {
            list.add(i);
        }
        System.out.println("当前list集合的元素内容是:" + list);
       
        // 因为Integer类实现了比较器Comparator接口,重写了compare()方法,此方法定义了排序规则
        // 所以直接把list传参即可排序
        // 如果对集合的元素进行排序,让元素所属类实现比较器Comparator接口重写compare()方法,或者直接使用匿名内部类方式。
        Collections.sort(list);

        System.out.println("排序后的list集合的元素内容是:" + list);
    }
 	
 	/**
 	* 使用匿名内部类自定义比较器规则为升序
 	*/
	private static void testSort2() {
        List<Integer> list = new ArrayList<>();
        for (int i = 10; i >=0; i--) {
            list.add(i);
        }
        System.out.println("当前list集合的元素内容是:" + list);
        Collections.sort(list, new Comparator<Integer>() {
            @Override
            public int compare(Integer o1, Integer o2) {
                // 第一个参数-第二个参数 升序
                // 第二个参数-第一个参数 降序
                return o1-o2;
            }
        });

        System.out.println("排序后的list集合的元素内容是:" + list);
    }
}

在这里插入图片描述

2.4、集合元素的查找

int binarySearch(List<? extends Comparable<? super T>> list, T key)  // 返回元素在指定集合中的索引

3、斗地主案例


在这里插入图片描述

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

/**
 * 斗地主
 * @author 白豆五
 * @version 2022/11/8 17:29
 * @since JDK8
 */
public class DouDiZhuTest {
    public static void main(String[] args) {
        // 1、准备牌
        List<String> pokers = new ArrayList<>();  //牌盒
        List<String> colors = new ArrayList<>();  //花色
        List<String> numbers = new ArrayList<>(); //牌号

        // 向colors中添加花色
        Collections.addAll(colors,"♥","♠","♣","♦");

        // 向numbers添加牌数
        for (int i = 3; i <= 10; i++) {
            numbers.add(i + "");
        }
        Collections.addAll(numbers,"J","Q","K","A","2");


        // 将52张牌装入牌盒
        for (String number : numbers) {
            for (String color : colors) {
                String poker = color + number;
                pokers.add(poker);
            }
        }
        //将大王小王添加到牌盒中
        Collections.addAll(pokers,"大王","小王");

        // 2、打乱牌盒
        Collections.shuffle(pokers);


        //玩家
        List<String> p1 = new ArrayList<>();
        List<String> p2 = new ArrayList<>();
        List<String> p3 = new ArrayList<>();
        List<String> bottom = new ArrayList<>();


        // 3、发牌
        for (int i = 0; i < pokers.size(); i++) {
            if (i > 50) {
                bottom.add(pokers.get(i));
            } else {
                switch (i % 3) {
                    case 0:
                        p1.add(pokers.get(i));
                        break;
                    case 1:
                        p2.add(pokers.get(i));
                        break;
                    case 2:
                        p3.add(pokers.get(i));
                        break;
                }
            }
        }

        // 4、看牌
        System.out.println("玩家1:"+p1);
        System.out.println("玩家2:"+p2);
        System.out.println("玩家3:"+p3);
        System.out.println("底牌:"+bottom);

    }
}

在这里插入图片描述


八、Map集合


1、Map集合概述

java.util.Map<K,V> 集合是双列集合的顶层接口,K(key)表示键的类型,V(value)表示值的类型。

Map集合的特点:

  • 存储:以键值对方式存储;(k,v)
  • 取值:根据键获取对应的值。
  • 键不能重复,如果重复了,旧值会被覆盖。
  • 值可以重复。

Map接口的常用实现类:HashMap、LinkedHashMap、TreeMap。

在这里插入图片描述


2、Map集合常用方法


2.1、添加元素到Map集合

  • V put(K key, V value) : 将指定的键值对添加到Map集合中。
    • 如果键key,是第一次存储,返回null
    • 如果键key已经存在,返回是被替换掉的值
    • 键相同,值被替换
  • void putAll(Map<? extends K, ? extends V> m):将指定的子map集合添加到当前map集合中。

示例:往map集合添加元素

public class MapTest {
    public static void main(String[] args) {
        Map<String, String> map = new HashMap<>();
        map.put("韩国", "首尔");
        map.put("日本", "东京");
        map.put("新加坡", "新加坡");
        map.put("韩国", "釜山");
        System.out.println("当前map集合的元素内容是:" + map);
    }
}

在这里插入图片描述



2.2、 根据键,在Map集合中获取对应的值

  • V get(Object key):根据指定的键,在Map集合中获取对应的值。

示例:

public class MapTest {
    public static void main(String[] args) {
        Map<String, Integer> map = new HashMap<>();
        map.put("b", 98);
        map.put("d", 100);
        map.put("a", 97);
        map.put("c", 99);
        // 获取键为b对应的值。
        Integer value = map.get("b");
        System.out.println(value);//98
     }
}


2.3、删除Map集合的元素

  • V remove(Object key) : 删除map集合中指定key对应的键值对。返回值是被删除元素的值。

示例:删除Map集合的元素

public class MapTest {
    public static void main(String[] args) {
        Map<String, String> map = new HashMap<>();
        map.put("韩国", "首尔");
        map.put("日本", "东京");
        map.put("新加坡", "新加坡");
        map.remove("日本");
        System.out.println("当前map集合的元素内容是:" + map);
    }
}

在这里插入图片描述



2.4、替换Map集合的元素

  • default V replace(K key, V value) : 替换Map集合的元素,返回值是被替换元素的值,JDK1.8新增的。
  • default boolean replace(K key, V oldValue, V newValue) : 替换Map集合中指定key对应的值,需要传原来的值和被替换的值,JDK1.8新增的。

示例:替换Map集合的元素

public class MapTest {
    public static void main(String[] args) {
        Map<String, String> map = new HashMap<>();
        map.put("韩国", "首尔");
        map.put("日本", "东京");
        map.put("新加坡", "新加坡");
        map.replace("日本","富士山");
        System.out.println("当前map集合的元素内容是:" + map);
    }
}

在这里插入图片描述



2.5、集合是否包含指定的key和value

  • boolean containsKey(Object key):集合是否包含指定的键 。
  • boolean containsValue(Object key):集合是否包含指定的value。

示例:

public class MapTest {
    public static void main(String[] args) {
        Map<String, String> map = new HashMap<>();
        map.put("韩国", "首尔");
        map.put("日本", "东京");
        map.put("新加坡", "新加坡");
        boolean key = map.containsKey("韩国"); //是否包含键
        System.out.println(key);
        boolean value = map.containsValue("新加坡");//是否包含值
        System.out.println(value);
        System.out.println("当前map集合的元素内容是:" + map);
    }
}

在这里插入图片描述



2.6、获取Map集合中所有key和value

  • public Set<K> keySet() : 获取Map集合中所有的键,存储到Set集合中。
  • public Collection<V> values() :返回Map集合中的所有值,存储到Collection集合。

示例:获取Map集合中所有key(keySet)和value(values)

public class MapTest {
    public static void main(String[] args) {
        Map<String, Integer> map = new HashMap<>();
        map.put("b", 98);
        map.put("d", 100);
        map.put("a", 97);
        map.put("c", 99);

        // 1、获取map集合中的所有key,存到Set集合中
        Set<String> setKey = map.keySet();
        // 获取map集合中的所有value
        Iterator<String> it = setKey.iterator();
        while (it.hasNext()) {
            String key = it.next();
            System.out.println("k:" + key);
        }
       // 2、获取map集合中的所有value,存到Collection集合中
        Collection<Integer> values = map.values();
        for (Integer value : values) {
            System.out.println("v:"+value);
        }
    }
}

在这里插入图片描述



2.7、获取Map集合中所有键值对对象

  • Set<Map.Entry<K, V>> entrySet(): 取Map集合中所有键值对对象。
  • Entry<K, V>是Map接口的内部接口,还是一个泛型接口,在使用的时候需要写成这样: Map.Entry<K, V>>,Entry表示键值对对象(也就是对键和值包装的对象)。

![在这里插入图片描述](https://img-blog.csdnimg.cn/c352324fc32a476083b46f040d3c70a5.png
)

示例:获取Map集合中所有键值对对象

public class MapTest {
    public static void main(String[] args) {
        Map<String, Integer> map = new HashMap<>();
        map.put("b", 98);
        map.put("d", 100);
        map.put("a", 97);
        map.put("c", 99);

        // 获取map集合中所有的键值对对象
        Set<Map.Entry<String, Integer>> entrySet = map.entrySet();
        // 使用增强for遍历set集合
        for (Map.Entry<String, Integer> entry : entrySet) {
        	// entry:键值对对象
            String key = entry.getKey();//获取键值对对象的key
            Integer value = entry.getValue();//获取键值对对象的value
            System.out.println("key:"+key+"---value:"+value);
        }
        
        System.out.println("-------------------------");
        
        // 使用迭代器Iterator对象遍历set集合
        Iterator<Map.Entry<String, Integer>> it = entrySet.iterator();
        while (it.hasNext()) {
            Map.Entry<String, Integer> entry =  it.next();
            String key = entry.getKey(); //获取键值对对象的key
            Integer value = entry.getValue(); //获取键值对对象的value
            System.out.println("key:"+key+"---value:"+value);
        }
    }
}

在这里插入图片描述



3、Map集合的遍历


3.1、 根据键找值

public class MapTest {
    public static void main(String[] args) {
        // 创建Map集合对象,指定键和值的类型
        Map<String, Integer> map = new HashMap<>();
        // Map集合对象调用put方法,添加键值对
        map.put("b", 98);
        map.put("d", 100);
        map.put("a", 97);
        map.put("c", 99);

        // Map集合对象调用keySet方法,获取所有的键对应的Set集合
        Set<String> keys = map.keySet();
        
       // 增强for遍历Set集合
        for (String key : keys) {
            // 获取map中键对应的值
            Integer value = map.get(key);
            // 打印键和值
            System.out.println(key+"::"+value);
        }
    }
}

在这里插入图片描述

3.2、根据键值对对象遍历

public class MapTest {
    public static void main(String[] args) {
        Map<String, Integer> map = new HashMap<>();
        map.put("b", 98);
        map.put("d", 100);
        map.put("a", 97);
        map.put("c", 99);

        // Map集合对象调用entrySet方法,获取所有的键值对对应的Set集合
        Set<Map.Entry<String, Integer>> entrySet = map.entrySet();
        // 使用增强for遍历set集合,类型是Map.Entry<String, Integer> 
        for (Map.Entry<String, Integer> entry : entrySet) {
        	// entry:键值对对象
            String key = entry.getKey();//获取键值对对象的key
            Integer value = entry.getValue();//获取键值对对象的value
            // 打印键和值
            System.out.println("key:"+key+"---value:"+value);
        }
        
        System.out.println("-------------------------");
        
        // 使用迭代器Iterator对象遍历set集合
        Iterator<Map.Entry<String, Integer>> it = entrySet.iterator();
        while (it.hasNext()) {
            // 获取到当前的键值对对象
            Map.Entry<String, Integer> entry =  it.next();
            String key = entry.getKey(); //获取键值对对象的key
            Integer value = entry.getValue(); //获取键值对对象的value
            // 打印键和值
            System.out.println("key:"+key+"---value:"+value);
        }
    }
}

在这里插入图片描述

4、HashMap


java.util.HashMap<K,V>集合特点:

  • 键具备哈希特性: 哈希表。(由数组+单项链表/红黑树组成,链表数量>8 并且 数组元素>=64,链表将变成红黑树)

  • 查询速度非常快,增删速度也不慢。

  • 键要唯一: 键所属的类需要重写hashCode和equals方法。

  • 键无序: 不保证存入和取出的顺序是一致的。

  • 键无索引: 不能通过索引的方式获取键。

  • 允许存储null键和null值。

  • 线程不同步,不安全,但是效率高。


java.util.Hashtable<K,V>集合特点 : (不常用)

  • 键具备哈希特性: 哈希表。(由数组+单项链)

  • 查询速度非常快,增删速度也不慢。

  • 键要唯一: 键所属的类需要重写hashCode和equals方法。

  • 键无序: 不保证存入和取出的顺序是一致的。

  • 键无索引: 不能通过索引的方式获取键。

  • 不允许存储null键和null值。

  • 线程同步,安全,但是效率低。


示例:HashMap的使用

public class MapTest {
    public static void main(String[] args) {
        // 创建一个HashMap集合
        HashMap<String, String> map = new HashMap<>();
        // 向集合中存放元素
        map.put("韩国", "首尔");
        map.put("日本", "东京");
        map.put("新加坡", "新加坡");
        map.put("韩国", "釜山");
        // 打印输出
        System.out.println("当前map集合的元素内容是:" + map);
    }
}

在这里插入图片描述


5、LinkedHashMap


java.util.LinkedHashMap<K,V>集合特点:

  • 键具备哈希特性和链表特性。(由数组+双项链表/红黑树组成,链表数量>8 并且 数组元素>=64,链表将变成红黑树)

  • 查询速度非常快,增删速度也不慢。

  • 哈希特性保证键要唯一: 键所属的类覆盖重写hashCode和equals方法

  • 链表特性保证键有序: 保证存入和取出的顺序是一致的。

  • 键无索引: 不能通过索引的方式获取键。

  • 允许存储null键和null值。

  • 线程不同步,不安全,但是效率高。

学生类:

import java.util.Objects;

public class Student {
    private String name;//姓名
    private int age;//年龄

    public Student() {
    }

    public Student(String name, int age) {
        this.name = name;
        this.age = age;
    }

    // idea自动生成的
    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Student student = (Student) o;
        return age == student.age && Objects.equals(name, student.name);
    }

    @Override
    public int hashCode() {
        return Objects.hash(name, age);
    }

    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }
}

测试类:

public class LinkedHashMapDemo {
    public static void main(String[] args) {
        //1.创建LinkedHashMap集合对象map,键Student(存储姓名和年龄),值String(存储所在城市)
        HashMap<Student, String> map = new LinkedHashMap<>();
        //2.使用LinkedHashMap集合对象map调用put方法,添加多个键值对
        map.put(new Student("张三", 38), "北京");
        map.put(new Student("张三", 38), "南京");
        map.put(new Student("李四", 18), "上海");
        map.put(new Student("李四", 18), "武汉");
        map.put(new Student("王五", 28), "广州");
        map.put(new Student("王五", 28), "深圳");
        System.out.println(map);
        //3.使用LinkedHashMap集合对象map调用keySet方法,获取所有的键对应的Set集合对象set
        Set<Student> set = map.keySet();
        //3.遍历(增强for)所有的键对应的Set集合对象set
        for (Student stu : set) {
            //4.获取当前的键:stu
            //5.使用LinkedHashMap集合对象map调用get方法传递键stu,获取值保存String变量address中
            String address = map.get(stu);
            //6.输出键和值
            System.out.println(stu + "::::" + address);
        }
    }
}

在这里插入图片描述


6、TreeMap


TreeMap基于红黑树实现的(红黑树,是一种自平衡二叉查找树)
与HashMap相比,TreeMap是一个能比较元素大小的Map集合,会对传入的key进行了大小排序。其中,可以使用元素的自然顺序,也可以使用集合中自定义的比较器来进行排序;


7、Properties


Properties继承于HashTable集合,也是双列集合,即操作双列集合的方法它都有。但是一般都用它特有的方法(如 参数和返回值类型都是String类型的),好处是字符串方便写入文本文件中,Properties他是唯一一个能io流结合的双列集合。

public class Demo1Properties {
    public static void main(String[] args) {
        //创建Properties集合对象
        Properties properties = new Properties();
        System.out.println(properties);

        //setProperty(String key,String value): 向集合中存储键值对。
        properties.setProperty("name", "panpan");
        properties.setProperty("salary", "20k");
        System.out.println(properties);//{name=panpan, salary=20k}

        //String getProperty(String key): 获取集合中键对应的值,无此键返回null。
        System.out.println(properties.getProperty("name"));
        System.out.println(properties.getProperty("salary"));

        //Set<String> stringPropertyNames(): 集合中的所有键存储到Set集合。
        Set<String> propertyNames = properties.stringPropertyNames();

        //增强for遍历
        for (String propertyName : propertyNames) {
            String propertyValue = properties.getProperty(propertyName);
            System.out.println(propertyName + "======" + propertyValue);
        }

    }
}
  • 5
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

白豆五

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值