Java集合类总结上-Collection接口 1

一, 集合概述 1

我们知道数组可以保存多个对象,但是某些情况下我们无法确定到底需要保存多少个对象.由于数组的长度是不可变的,此时将不再适用于保存这些可变的对象.
为了保存这些数目不确定的对象,JDK提供了一系列特殊的类,这些类可以存储任意类型的具有共同属性的对象,并且对象长度可变,统称为集合.


数组的应用场景:

  • 适用于数据长度固定的情况,并且主要进行查询操作.

集合的应用场景:

  1. 无法预测存储数据的数量;
  2. 同时存储具有一对一关系的数据;
  3. 需要进行数据的增删;
  4. 数据重复问题(Set集合,无序不重复).

集合和数组的区别:

集合和数组的区别
1. 数组的长度固定,集合的长度动态扩展
2. 数组只能存储相同数据类型的数据,而集合可以存储不同数据类型的数据(为了让集合更专一,所以我们引入了泛型哈哈)
3. 数组可以存储基本数据类型数据,也可以是引用数据类型,而集合只能存储引用类型的数据
  • 泛型限制集合中只能存储一种数据类型的数据.

Q: 为什么集合中添加基本数据类型数据不报错?
如:

A: 因为装箱啊! 并不是因为集合中能存储基本数据类型数据了,而是经过装箱操作,将基本数据类型装换为对应的包装类对象了.


1.1 集合的分类

集合按照其存储结构可以分为两大类,即单列集合Collection 和 双列集合Map;

在这里插入图片描述

  • Collection: 单列集合类的根接口,用于存储一系列符合各种规则的元素,他有两个重要的子接口,分别是List 和 Set.List接口的特点是元素有序,可重复,实现类有ArrayList和LinkedList, Set接口的特点是元素无序且不可重复, 实现类有HashSet和TreeSet.
  • Map: 双列集合类的根接口, 用于存储具有键(Key)-值(Value)映射关系的元素.每个元素都包含一对键值,在使用Map集合时可通过指定的Key找到对应Value. Map接口的主要实现类是 HashMap 和 TreeMap;

二, Collection接口

Collection是所有单列集合类的根接口,其定义了一些子类(Set和List)公用的方法;

方法声明功能描述
boolean add(Object o)向集合中添加一个元素
boolean addAll(Collection c)将指定Collection中的所有元素添加到该集合中
void clear()删除该集合中的所有元素
boolean remove(Object o)删除该集合中的指定元素
boolean removeAll(Collection c)删除指定集合中的所有元素
boolean isEmpty()判断该集合是否为空
boolean contains(Object o)判断该集合是否包含某个元素
boolean contaionsAll(Collection c)判断该集合中是否包含指定集合中的所有元素
Iterator返回在该集合的元素上进行迭代的迭代器(Iterator),用于遍历该集合中的所有元素
int size()获取该集合元素个数

2.1 List(有序可重复)

特点:

  1. 元素有序可重复;(List的有序是指元素的存入和取出的前后顺序是相同的!)
  2. 可以精确控制每个元素的插入位置,或删除某个位置的元素(为设么精确? 因为是通过索引访问集合中的指定元素);
  3. List的两个主要实现类是ArrayList和LinkedList.

List除了继承了Collection的所有方法,还有一些特有方法:

方法声明功能描述
void add(int index, Object element)把element元素插入到List集合的Index处
boolean addAll(int index, Collection c)将集合c所包含的所有元素插入到List集合的Index处
Object get(int index)返回集合索引index处的所有元素
Object remove(int index)删除index处的元素
Object set(int index, Object element)将索引index处元素替换成element对象,并将替换后的元素返回
int indexOf(Obeject o)返回对象o在List集合中出现的位置索引
int lastIndexOf(object o)返回对象o在List集合中最后一次出现的位置索引
List subList(int fromIndex, int toIndex)返回从索引fromIndex(包含)到toIndex(不包括)处所有元素组成的子集合

2.1.1 List的实现类 一: ArrayList

概括:
-底层数组实现,动态增长,随机查找速度快,但插入和删除速度慢;

特点:

  1. ArrayList的底层是由数组实现的;
  2. 长度动态增长;
  3. 类似于数组,ArrayList在内存中占用一片连续的内存空间,故在List中间插入和删除数据较慢;
  4. 由于如同数组一样,ArrayList在中间位置处理数据消耗资源较多(会发生大量的数组复制),所以ArrayList更适合于通过索引查找和更新元素;
  5. ArrayList中的元素可为Null.
  • ArrayList的三种遍历方式:
    List arraylist = new ArrayList();

    ///1.foreach循环遍历集合
    for(Object i : arrayList){
        System.out.println(i);
    }

    ///2. 通过索引遍历Arraylitst
    for(int i=0; i< arrayList.size(); i++){
        System.out.println(arrayList.get(i));
    }

    /// 3. Iterator迭代器遍历集合
    Iterator iterator = arrayList2.iterator();
    while(iterator.hasNext()){
        System.out.println(iterator.next());
    }
  • ArrayList的常见操作:
import java.util.ArrayList;
import java.util.Iterator;
public class ArrayListTest {
    public static void main(String[] args) {
        ArrayList arrayList = new ArrayList();
        ///1.添加元素
        arrayList.add("Java No.1");
        arrayList.add("Python");
        arrayList.add(2020);
        arrayList.add(1,"boy next door");
        ///2.获取集合的个数:
        System.out.println("arrayList集合中的元素有: "+arrayList.size());
        //3.删除集合中的"2020":(索引删除或直接删除);
        arrayList.remove("2020");
            //arrayList.remove(3);
        System.out.println(arrayList);
        //4.添加A集合到B集合
        ArrayList arrayList2 = new ArrayList();
        arrayList2.add(arrayList);
        arrayList2.add(1,"today's date is this");
        ///5.判定集合中是否包含某元素
        boolean flag = arrayList2.contains("Python");
        System.out.println("arrayList2中有Python吗? --"+flag);
        System.out.println(""+arrayList2.size());
    }
}

ArrayList应用实例-信息管理
ArrayList遍历的实例-员工工资表

2.1.2 List的实现类 二: LinkedList

概括:双链表实现,查找效率低,但增删效率高.

ArrayList集合在查询和更新元素时速度较快,但在增删元素时效率较低. 为了克服这种局限性,可以使用List的另一个实现类 LinkedList.
LinkedList内部维护了一个双向循环链表,链表中的每一个元素都使用引用的方式来记住它的前一个元素和后一个元素,从而可以将所有的元素彼此连接起来.当插入或删除一个元素时,只需要修改元素之间的这种引用关系即可.


LinkedList类特有的方法:

方法声明功能描述
void add(int index, E e)在此列表中指定位置插入指定元素
void addFirst(E e)将指定元素插入到列表的开头
void addLast(E e)将指定元素插入到此列表的末尾
public E getFirst()返回此列表的第一个元素
public E getLast()返回此列表的最后一个元素
public E removeFirst()删除并返回列表的第一个元素
public E removeLast()删除并返回列表的最后一个元素

方法的使用示例:

import java.util.LinkedList;

public class LinkedListTest {
    public static void main(String[] args) {
        LinkedList linkedListTest = new LinkedList();
        linkedListTest.add("Java");
        linkedListTest.add("Mysql");
        linkedListTest.add("Jvm虚拟机");

        System.out.println(linkedListTest);
         1. 增加元素
        //void add(int index, E e)
        linkedListTest.add(1,"Hadoop");
        //void addFirst()
        linkedListTest.addFirst("C语言");
        //void addLast()
        linkedListTest.addLast("Hive");

        System.out.println(linkedListTest);
        /// 2. 返回元素
        System.out.println(""+ linkedListTest.getFirst());
        System.out.println(""+ linkedListTest.getLast());

        ///3. 删除元素
        System.out.println("删除了"+linkedListTest.removeFirst());
        System.out.println("删除了"+linkedListTest.removeLast());
    }
}
  • 执行结果:

2.1.3, List的实现类三: Vector (☆)

  • Vector与ArrayList的异同点: (lsit接口, 底层数组, 随机查找和更新)

相同点:

  • 都是实现的 List接口, 数据要求有序可重复;
  • 底层都是基于数组实现,并且可以动态增长;
  • 也都有数组的特性,在内存中连续存储,故而,随机查找(效率高,插入删除效率低;

不同点: ( 线程安全, 内存扩充, 拥有的方法)

  1. 从线程安全问题来说,

    • ArrayList属于线程不安全(线程不同步)即当多线程进行对ArrayList集合的操作时,有可能对数据进行不正确的操作。如果只有一个线程会访问到集合,那最好是使用ArrayList,因为它不考虑线程安全,效率会高些
    • Vector是线程安全的(同步线程),即当在同一时刻只能有一个线程进行对Vector集合的操作时,但是Vector要做到线程同步,需要大量的花费,可能消耗大量的内存或者CPU。如果有多个线程会访问到集合,那最好是使用Vector,因为不需要我们再去考虑和编写线程安全的代码。
  2. 从内存扩充上来说,

    • ArrayList在内存不够时默认增加原来的0.5倍
    • Vector是默认增加原来的1倍
  3. 从拥有的具体方法来说,

    • Vector提供indexOf(object,start)方法,ArrayList没有。

2.2 Set(无序不重复)

Set接口概述:

  • Set接口继承自Collection接口,它与Colleciton中的方法基本一致,只是比Collection接口更加严格了.
  • Set接口要求集合内的元素无序且不可重复.
  • Set接口主要有两个实现类, HashSet 和 TreeSet. 其中HashSet根据对象的Hash值来确定元素在集合中的存储位置,因此具有良好的存取和查找功能. TreeSet则是以二叉树的方式来存储元素,它可以实现对集合中的元素进行排序.
  • HashSet是基于哈希表实现的,数据是无序的,HashSet元素可以是null但只能有一个null(因为不允许元素重复啊)
  • TreeSet是基于二叉树实现的,可以实现数据的--自动排序--,确保集合元素处于排序状态,不允许放入空值。
  • HashSet的性能优于TreeSet,一般情况下建议使用HashSet,如果需要使用排序功能建议使用TreeSet

2.2.1 Set接口的实现类一: HashSet (去重不排序)

HashSet是Set接口的一个实现类,它所存储的元素都是无序不重复的.

  • HashSet集合中添加元素的流程

    • 当调用HashSet集合的add()方法存入元素时,首先调用当前存入对象的hashCode()方法获得对象的哈希值,然后根据对象的哈希值计算出一个存储位置. 如果该位置上没有元素,则直接将元素存入. 如果该位置元素存在,则会调用equals()方法让当前存入的元素依次和该位置上的元素进行比较,返回结果为false就将该元素存入集合,返回为true则说明元素相同,就将该元素舍弃.
    • 流程如下:
  • 由以上赘述,我们可以知道,为了保证HashSet正常工作,我们通常需要重写toString(),hashCode()和equals()方法.

举个栗子:

  • 一,对于已经重写好的类我们直接拿来用就行(比如String 类)

Set集合没有提供get方法,所以对Set集合的遍历只能通过增强for循环和迭代器进行遍历

import java.util.*;
public class HashSetTest {

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		Set hashSet = new HashSet();  
		hashSet.add("Jack");
		hashSet.add("Tom");
		hashSet.add("Jack");  
	///HashSet 的两种遍历方式:(迭代器 和 foreach循环)
    ///1. 迭代器
		Iterator iterator = hashSet.iterator();
		while(iterator.hasNext()) {
			System.out.println(iterator.next());
		}
    ///2.foreach循环
        for (Object object : hashSet) {
			System.out.println(object);
		}
	}
}

执行结果:

  • 二,对于自定义类的对象,如果我们需要使用Set的无序不重复特性,我们需要重写 toString(), hashCode()以及equals()方法,那么在哪里重写? 当然是能接触到实体类属性的实体类中啦!

我们可以使用编辑器的source选项自动生成 toString(),hashCode(),以及 toString()方法.但是最好能熟练的自己手写出来.

  1. toString()的重写方法:
    在自定义的实体类中重写 toString()方法
    public void toString(){
        return 想要打印输出的实体类属性;
    }
  1. hashCode()的重写方法:
    哈希算法的核心思想就是将集合分成若干个存储区域(可以看成一个个桶),每个对象可以计算出一个哈希码,可以根据哈希码分组,每组分别对应某个存储区域,集合的元素根据它的哈希码就可以被分到不同的存储区域。又因为存在哈希冲突(哈希码相同的元素处于相同的内存区域,所以这也是为什么我们需要用 equals()方法 进行更进一步的比较)
//因为是重写,所以要注意同名同参同返回.
public int hashCode(){
    //把能够区分实体类对象的信息(比如每个学生的学号,每个身份证的唯一身份证号)作为哈希码
    return this.id;
}
  1. equals()的重写方法:
//因为是重写,所以要注意同名同参同返回.
public boolean equals(Object obj){
    //1,首先判断对象是否相同,相等返回true,不用继续比较属性了
    if(this==obj)
        return true;
    //2,判断obj是否是实体类的实例对象(可以用反射,也可以用 instanceOf判断), 如果是就强制转换为实体类的类型,然后返回比较id的结果
    //同一个类在JVM的=中只会有一个CLASS对象, 见反射一文
    ///2-1, 借助反射重写equals()方法
/*
    if(obj.getClass()==实体类.class){
        ///强制转换,让obj转为实体类的类型
        实体类 实体类对象 = (实体类)obj; 
        return this.id == obj.id;
    // }
*/
   //2-2, 借助instanceOf()重写equals()方法 
    if( obj instanceOf 实体类){
        ///强制转换,让obj转为实体类的类型
        实体类 实体类对象 = (实体类)obj; 
        return this.id == obj.id;
    }

    return false;
}


代码示例: HashSet的实例-学生信息管理

2.2.2 Set接口的实现类二: TreeSet (去重且排序)

TreeSet是Set接口的另一个实现类;其内部采用自平衡的排序二叉树来存储元素,这样的结构可以保证TreeSet集合没有重复的元素并且可以对元素进行排序;

  • 示例代码:
public class TreeSetTest {
    public static void main(String[] args) {
        Set treeSet = new TreeSet();

        treeSet.add("Sb");
        treeSet.add("Ada");
        treeSet.add("Red");

        Iterator it = treeSet.iterator();
        while(it.hasNext()){
            System.out.println(it.next());
        }
    }
}

结果如下:

  • 原理透析:

由上述代码运行结果可知,集合中的字符串按顺序打印出来.这些元素之所以能够排序,是因为每次将一个元素放入TreeSet集合时,就会把这个元素与集合中已经存在的元素进行比较,最后把它插入到有序的元素序列中.

集合中的元素在进行比较时,都会调用compareTo()方法,该方法是在Comparable接口中定义的,JDK中大多数的类都实现了该接口并拥有这个compareTo()方法;
那么我们在使用TreeSet集合存放自定义类的对象时,如何能让这些元素排好序呢? 当然是实现Comparable接口,重写compareTo()方法(这就是自然排序)咯;

A.自然排序 Comparable的使用

  • 举个栗子:

注意: 使用自然排序,对字母的排序是区分大小写的! 大写字母大于所有的小写字母;

  • 示例代码如下:

Person实体类
package JavaReview.src;

public class PersonEntity implements Comparable {
    private String name;
    private int age;

    public PersonEntity(String name, int age) {
        this.name = name;
        this.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;
    }
    //重写 toString()
    @Override
    public String toString(){
        return "姓名:"+name+", 年龄: "+age;
    }
    public int compareTo(Object obj){
        int _age =((PersonEntity)obj).getAge();
        String _name=((PersonEntity)obj).getName();
        /* 对于compareTo()比较两个变量,
            0表示相等(只输出一个);
            1表示前者大于后者(升序);
            -1表示前者小于后者(降序);
         */

        比较年龄大小,年龄相同则继续比较姓名.
        int num = this.age - _age;//比较年龄
        ///String类中已经实现了Comparable接口并重写了compareTo()方法
        //所以此处我们可以直接用string类型的对象直接调用compareTo()方法
        //下面一行代码的意思是,当年龄相同时,我们比较姓名的字母大小.
        int num2 = num==0 ?this.name.compareTo(_name) : num;
        return num2;
    }
}

Person操作类
package JavaReview.src;

import java.util.Iterator;
import java.util.Set;
import java.util.TreeSet;

public class PersonOperator {
    public static void main(String[] args) {
        //创建treeSet
        Set treeSet = new TreeSet();

        //初始化PersonEntity
        PersonEntity p1 = new PersonEntity("wangwu",23);
        PersonEntity p2 = new PersonEntity("agou",33);
        PersonEntity p3 = new PersonEntity("zhangsan",24);
        PersonEntity p4 = new PersonEntity("sanbing",33);
        PersonEntity p5 = new PersonEntity("abo",88);

        //放入集合
        treeSet.add(p1);
        treeSet.add(p5);
        treeSet.add(p2);
        treeSet.add(p3);
        treeSet.add(p4);

        //迭代遍历
        Iterator it = treeSet.iterator();
        while(it.hasNext()){
            System.out.println(it.next());
        }

    }
}

执行结果如下:

  • 对自然排序 Comparable接口的总结:
    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-popuoXru-1616593972865)(2021-03-24-20-56-38.png)]

比如上面的例子,主要条件是年龄排序,次要条件是年龄相同时,用姓名排序.

B.比较器排序 Coparator的使用

  • 举个栗子:

代码如下:

实体类不继承comparable接口,只有私有属性和get,set方法

///操作类
package com.leecode.cyy;

import java.util.Comparator;
import java.util.Iterator;
import java.util.Set;
import java.util.TreeSet;

public class PerosonOperator {

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		 //创建treeSet
    ///采用匿名内部类的方式在集合的构造方法中获得 比较构造器的对象.
        Set treeSet = new TreeSet(
        	new Comparator<PersonEntity>() {
                ///重写Comparator中的方法
				@Override
				public int compare(PersonEntity o1, PersonEntity o2) {
					// TODO Auto-generated method stub
					int num = o1.getAge() - o2.getAge();
					
					int num2 = num ==0 ? (o1.getName().compareTo(o2.getName()) ): num;
					
					return num2;
				}
        			
				}
        );

        //初始化PersonEntity
        PersonEntity p1 = new PersonEntity("wangwu",23);
        PersonEntity p2 = new PersonEntity("agou",33);
        PersonEntity p3 = new PersonEntity("zhangsan",24);
        PersonEntity p4 = new PersonEntity("sanbing",33);
        PersonEntity p5 = new PersonEntity("abo",88);

        //放入集合
        treeSet.add(p1);
        treeSet.add(p5);
        treeSet.add(p2);
        treeSet.add(p3);
        treeSet.add(p4);

        //迭代遍历
        Iterator it = treeSet.iterator();
        while(it.hasNext()){
            System.out.println(it.next());
        }
        
	}
}
  • 结论:

为了访问集合元素更加便捷,我们引入了泛型.
跳转查看Java中的泛型概念

posted @ 2022-05-26 20:31  青松城  阅读(47)  评论(0编辑  收藏  举报