集合

2020-05-12  本文已影响0人  上炼致知

集合的简介

集合是java中提供的一种容器,可以用来存储多个数据;

集合和数组都是容器,它们的区别:

集合继承关系

ArrayList类继承了抽象类AbstractList,同时实现接口List,而List接口又继承了Collection接口;Collection接口为最顶层集合接口

说明我们在使用ArrayList类时,该类已经把所有抽象方法进行了重写,即Collection接口的所有子类都会被重写;

集合Collection的方法

Collection接口中的方法,是集合中所有实现类必须拥有的方法;

迭代器的概述

Java中提供了很多个集合,它们在存储元素时,采用的存储方式不同,要取出这些集合中的元素,可通过一种通用的获取方式来完成;

Collection集合元素的通用获取方式:在取元素之前要先判断集合中有没有元素,如果有,就把这个元素取出来,继续再判断,如果还有就再取出来,直到把集合中的所有元素全部取出来,这种取出方式专业术语称为迭代

每种集合的底层数据结构都不同,例如:ArrayList底层是数组,LinkedList底层是链表,但是无论哪种集合,都会判断是否有元素,以及取出里面元素的动作;Java为我们提供了一个迭代器定义了统一的判断元素和取元素的方法;

接口Iterator的两个抽象方法

增强for循环

格式:

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

使用增强for循环遍历数组,好处是:代码少了,方便容器遍历;弊端:没有索引,不能操作容器里面的元素

int[] arr = {3, 1, 9, 0};
for (int i : arr) {
        System.out.println(i);
}
System.out.println(arr);//[I@1f89ab83
System.out.println(arr.toString());//[I@1f89ab83

增强for循环遍历集合:

ArrayList<String> arrayList = new ArrayList<>();
arrayList.add("abc1");
arrayList.add("abc2");
arrayList.add("abc4");
for (String str : arrayList) {
    System.out.println(str);
}

泛型的引入

泛型指明了集合中存储数据的类型 <数据类型>,是一个安全机制,保证程序的安全性;

泛型类

定义格式:修饰符 class 类名<代表泛型的变量>{}

class ArrayList<E> {
    public boolean add(E e) {}
    public E get(int index) {}
}

/**
 * 例如:ArrayList<String> list = new ArrayList<String>();
 * 此时,变量E的值就是String类型
 */
  
/**
 * 例如:ArrayList<Integer> list = new ArrayList<Integer>();
 * 此时,变量E的值就是Integer类型
 */

泛型的方法

定义的格式:修饰符 <代表泛型的变量> 返回值类型 方法名(参数){}

/**
 * 例如,ArrayList集合中的方法
 * public <T> T[] toArray(T[] a) {}
 * 该方法,用来把集合元素存储到指定数据类型的数组,返回已存储集合元素的数组
 */
ArrayList<String> list = new ArrayList<String>();
String[] arr = new String[100];
String[] result = list.toArray(arr);
//此时变量T的值就是String类型,变量T
//pulic <String> String[] toArray(String[] a) {};

ArrayList<String> list = new ArrayList<String>();
Integer[] arr = new Integer[100];
Integer[] result = arr.toArray(arr);
//此时变量的值就是Integer类型
//public <Integer> Integer[] toArray(Integer[] a) {};

泛型的接口

//带有泛型的接口
public interface List<E> {
    abstract boolean add(E e);
}
//实现类,先实现接口,不理会泛型
public class ArrayList<E> implements List<E> { }
//调用者
new ArrayList<String>();

//实现类,实现接口的同时,也指定了数据类型
public class XXX implements List<String> {}
//调用者
new XXX()

泛型的好处

泛型的通配符

泛型的通配符:?;匹配所有的数据类型

public static void iteratorFun(Collection<?> coll) {
    Iterator<?> it = coll.iterator();
    while (it.hasNext()) {
        System.out.println(it.next());
    }
}

泛型的限定

? extends Fu    限制的是父类,上限限定,可以传递Fu和它的子类
? super Sun 限制的是子类,下限限定,可以传递Sun和它的父类

List接口

List接口介绍

List是有序的Collection,此接口的用户可以对列表中每个元素的插入位置进行精确的地控制,用户可以根据元素的索引访问元素,并搜索列表中的元素;与Set不同,List允许存储重复的元素

List接口:

List接口的常用实现类有:ArrayList集合、LinkedList;

List接口常用方法

增加元素:

删除元素:

查询元素:

替换元素:

List<String> list = new ArrayList<>();
//尾部添加元素
list.add("abc");
list.add("123");
list.add("一二三");
list.add("唐");
System.out.println(list); //[abc, 123, 一二三, 唐]

//指定位置插入指定元素
list.add(3, "隋");
System.out.println(list); //[abc, 123, 一二三, 隋, 唐]

//删除指定位置元素
list.remove(1)  ;
System.out.println(list); //[abc, 一二三, 隋, 唐]

//替换(修改)指定位置元素
list.set(1, "四五六");
System.out.println(list); //[abc, 四五六, 隋, 唐]

for (int i = 0; i < list.size(); i++) {
    String str = list.get(i);
    //返回列表中指定位置的元素
    System.out.println(str);
}

List的并发修改异常

在迭代过程中,不能使用集合的方法对元素进行操作,否则会导致迭代器并不知道集合中的变化,容易引发数据的不确定性;

解决办法:可通过ListIterator迭代器进行操作元素,ListIterator的出现,解决了使用iterator迭代过程中可能会发生的错误情况;

List集合存储数据的结构

List接口下有很多个集合,它们存储元素所采用的结构方式不同,因此这些集合有各自的特点,根据不同场景下使用不同的集合;

集合的数据存储常用结构有:堆栈、队列、数组、链表;

ArrayList集合

ArrrayList集合数据存储的结构是数组结构,元素增删慢,查找快;由于日常开发中使用最多的功能为查询数据,遍历数据,所以ArrayList是最常用的集合;

LinkedList集合

LinkedList集合数据存储的结构是链表结构,方便元素添加、删除的集合,实际开发中对一个集合元素的添加与删除经常涉及到首尾操作,而LinkedList提供了大量收尾操作的方法;LinkedList是List的实现类,List中的方法LinkeddList都可以使用;

在开发中,LinkedList集合可以作为堆栈和队列的集合使用

添加:

获取:

移除:

弹出和压入:

判断:

Vector集合

Vector集合数据存储的结构是数组结构,是JDK最早提供的集合,Vector中提供了一个独特的取出方式,就是枚举Enumeration,它其实就是早起的迭代器;此接口Enumeration的功能与Iterator接口的功能类似;

Vector集合已被ArrayList替代,枚举Enumeration已被迭代器Iterator替代;

Vector常见方法(被ArrayList替代)

Enumeration枚举常见方法(被Iterator替代)

Set接口

LIst接口可以存放重复元素,但是Set接口不能存储重复元素;通过元素的equals方法来判断是否为重复元素;

HashSet集合介绍

HashSet集合实现Set接口,由哈希表支持(实际上是一个HashMap集合),HashSet集合不能保证迭代的顺序与元素存储顺序相同

HashSet集合,采用哈希表结构存储数据,保证元素唯一性的方式依赖于:hashCode()与equals()方法;

HashSet集合存储数据的结构

什么是哈希表:哈希表底层使用的也是数组机制,数组中也存放对象,而这些对象往数组中存放时的位置比较特殊,当需要把这些对象给数组中存放时,那么会根据这些对象的特有数组结合相应的算法,计算出这个对象在数组中的位置,然后把这个对象存放在数组中,而这样的数组就称为哈希数组,即哈希表

当向哈希表中存放元素时,需要根据元素的特有数据结合相应的算法,这个算法其实就是Object类中的hashCode方法,由于任何对象都是Object类的子类,所以任何对象都有这个方法,即就是在给哈希表中存放对象时,会调用对象的hashCode方法,算出对象在表中的存放位置;

这里需要注意,如果两个两个对象的hashCode方法算出来的结果一样,这样现象称为哈希冲突,这时会调用对象的equals方法,比较这两个对象是不是同一个对象,如果equals方法返回的是true,那么就不会把第二个对象存放在哈希表中,如果返回的是false,就会把这个值存放在哈希表中;

总结:保证HashSet集合元素的唯一性,其实就是根据对象的hashCode和equals方法来决定的;如果我们往HashSet集合中存放自定义的对象,那么保证其唯一,就必须覆写hashCode和equals方法建立属于当前对象的比较方式

特点:

  1. 无序集合,存储和取出的顺序不同
  2. 没有索引
  3. 不存储重复元素
  4. 底层数据结构是哈希表;存取都比较快;线程不安全,运行速度快
HashSet<String> hs = new HashSet<>();
hs.add("zhangsan");
hs.add("lisi");
hs.add("wangwu");
hs.add("zhangsan");
//迭代器方法取出集合中的每个元素
Iterator<String> iterator = hs.iterator();
while (iterator.hasNext()) {
    System.out.print(iterator.next() + " "); //lisi zhangsan wangwu 
}
System.out.println();
//增强for循环的方法取出集合中的每个元素
for (String str : hs) {
    System.out.print(str + " ");//lisi zhangsan wangwu 
}
System.out.println();

哈希表的数据结构

哈希表的数据解构.JPG

字符串对象的哈希值

public int hashCode() {
    int h = hash;
    if (h == 0 && value.length > 0) {
        char val[] = value;
        for (int i = 0; i < value.length; i++) {
            h = 31 * h + val[i];
        }
        hash = h;
    }
    return h;
}

哈希表的存储过程

  1. 首先调用本来的hashCode()方法算出哈希值
  2. 在容器中找是否有与新元素哈希值相同的老元素,如果没有直接存入,如果有转到第三步
  3. 新元素会与该索引位置下的老元素利用equals方法一一对比,一旦新元素.equals(老元素)返回true,那么停止对比,说明元素重复,不再存入;如果与该索引位置下的老元素都通过equals方法对比返回false,说明没有重复,存入

HashSet存储自定义类型元素

给HashSet中存放自定义类型元素时,需要重写对象中的hashCode和equals方法,建立自己的比较方法,才能保证HashSet集合中的对象唯一

LinkedHashSet介绍

HashSet有一个子类LinkedHashSet,它是链表和哈希表组合的一个数据存储结构

它能保证元素的有序性,存储和取出顺序相同

线程不安全,运行速度快

判断集合元素唯一的原理

ArrayList的contains方法判断元素是否重复原理

HashSet的add/contains等方法判断元素是否重复原理

Set集合不能存放重复集合,其添加方法在添加时会判断是否有重复元素,有重复元素就不添加,没有重复才添加;

HashSet集合由于是无序的,其判断唯一的依据是元素类型的hashCode和equals方法的返结果,规则如下:

先判断新元素与集合内已经有的旧元素的HashCode值:如果不同,说明是不同元素,添加到集合;如果相同,再判断equals比较结果,返回true则元素相同,返回false则元素不同,添加到集合;

所以,使用HashSet存储自定义类型,如果没有重写该类的hashCode和equals方法,则判断重复时使用的是地址值,如果想通过内容比较元素是否相同,需要重写该元素类的hashCode和equals方法

总结

List Set
有序集(元素存与取的顺序相同);
可以存储重复的元素
无序的集合(元素存与取的顺序可能不同)
不能存储重复的元素
List集合中特有的方法:
void add(int index, Object element)
Object get(int index)
Object remove(int index)
Object set(int index, Object element)
ArrayList LinkedList
底层数据结构是数组
查询快,增删慢
底层数据结构是链表
查询慢,增删快
HashSet LinkedHashSet
元素唯一,不重复
底层结构是哈希表结构
元素的存和取顺序不能保证一致
元素唯一,不重复
底层结构是哈希表结构+链表结构
元素的存取顺序一致

Map接口

Map接口概述

Map接口下的集合与Collection接口下的集合存储数据的形式不同:

Collection接口下的集合 Map接口下的集合
元素孤立存在,向集合中存储元素采用一个一个元素的方式存储 元素成对存在,每个元素由键和值两部分组成,通过键可以找到对应的值
称为单列集合 称为双列集合
不能包含重复的键,值可以重复,每个键只能对应一个值
常用集合为HashMap集合、LinkedHashMap集合

Map接口中常用集合概述

HashMap<K, V> LinkedHashMap<K, V>
存储数据采用哈希表结构,元素的存取顺序不一致,由于需要保证键的唯一不重复,需要重写自定义类型的hashCode()和equals()方法 是HashMap下的子类,存储数据采用哈希表结构+链表结构,通过链表结构可以保证元素的存取顺序一致;通过哈希表结构可以保证键唯一不重复,需要重写键的hashCode()和equals()方法;

Map接口常用方法

//创建Map对象
Map<String, String> map = new HashMap<>();
//给map添加元素
map.put("星期一", "Monday");
map.put("星期日", "Sunday");
System.out.println(map);//{星期日=Sunday, 星期一=Monday}

System.out.println(map.put("星期一", "Mon"));//Monday
System.out.println(map);//{星期日=Sunday, 星期一=Mon}

//根据指定key获取对应的value
String eng = map.get("星期一");
System.out.println(eng);//Mon

//删除指定元素,返回被删除元素的值
String value = map.remove("星期日");
System.out.println(value);//Sunday
System.out.println(map);//{星期一=Mon}

Map集合遍历键找值方式

键找值方式:即通过元素中的键,获取键所对应的值;

  1. 获取Map集合中所有的键,由于键是唯一的,所以返回一个Set集合存储所有的键;
  1. 遍历键的Set集合,得到每一个键;

  2. 根据键获取键所对应的值;

    Map<String, String> map = new HashMap<>();
    map.put("yi", "one");
    map.put("er", "two");
    map.put("san", "three");
    //获取map中所有的key
    Set<String> keySet = map.keySet();
    //遍历存放所有key的Set集合
    Iterator<String> iterator = keySet.iterator();
    
    while (iterator.hasNext()) {
        //得到每一个key
        String key = iterator.next();
        //通过key获取对应的value
        String value = map.get(key);
        System.out.println(key + " = " + value);
    }
    

Entry键值对对象

在Map接口设计时,提供了一个嵌套接口:Entry;Entry将键值对的对应关系封装成了对象即键值对对象,这样在遍历Map集合时,就可以从每个键值对(Entry)对象中获取对应的键与对应的值;

Map集合遍历键值对方式

键值对方式:即通过集合中每个键值对(Entry)对象,获取键值对(Entry)对象中的键与值;

  1. 获取Map集合中,所有的键值对(Entry)对象,以Set集合形式返回;
  2. 遍历包含键值对(Entry)对象的Set集合,得到每一个键值对对象;
  3. 通过键值对(Entry)对象,获取Entry对象中的键和值;
Map<String, String> map = new HashMap<>();
map.put("yi", "one");
map.put("er", "two");
map.put("san", "three");

//获取键值对对象的Set集合
Set<Map.Entry<String, String>> entrySet = map.entrySet();
//遍历键值对对象的Set集合
Iterator<Map.Entry<String, String>> iterator = entrySet.iterator();
while (iterator.hasNext()) {
    //得到每一个键值对
    Map.Entry<String, String> entry = iterator.next();
    //通过每一对键值对得到key
    String key = entry.getKey();
    //通过每一对键值对得到value
    String value = entry.getValue();
    System.out.println(key + " = " + value);
}
System.out.println("=======================");
// 增强for循环方式遍历
for (Map.Entry<String, String> entry : entrySet) {
    //通过每一对键值对得到key
    String key = entry.getKey();
    //通过每一对键值对得到value
    String value = entry.getValue();
    System.out.println(key + " = " + value);
}

HashMap存储自定义类型键值

LinkedHashMap特点

Hashtable的特点

底层数据结构是哈希表,特点和HashMap一样;

HashMap Hashtable
线程不安全的集合,运行速度快 线程安全集合,运行速度慢
和Vector一样,被取代
允许存储null值,null键 不允许存储null值,null键
子类Properties依然活跃

静态导入

在导包的过程中可以直接导入静态部分,这样某个类的静态成员就可以直接使用了

可变参数

public static void main(String[] args) {
    int[] arr = {1, 3, 5, 7};
    int sum = add(arr);
    System.out.println(sum);
}
public static int add(int...param) {
    int sum = 0;
    for (int i = 0; i < param.length; i++) {
      sum += param[i];
    }
    return sum;
}

Collections集合工具类

集合继承体系

集合继承体系.png
上一篇 下一篇

猜你喜欢

热点阅读