集合体系结构(Collection)

  • Post author:
  • Post category:其他




集合



1.集合类体系结构

单列:Collection

  • 可重复 list 实现类:ArrayList ,LinkedList …
  • 不可重复 set 实现类:HashSet, TreeSet…

双列: Map 实现类: HashMap, TreeMap…

Collection 是一个接口:Public interface Collection< E >

使用,通过多态的方法:

    public static void main(String[] args) {
        Collection<String> c = new ArrayList<String>();
        c.add("hello");
        c.add("world");
        System.out.println(c);
        // [hello, world]

Collection 集合的常用方法

  • boolean add(E e)
  • boolean remove(E e)
  • void clean()
  • boolean contains(Object o)
  • boolean isEmpty(E e)
  • int size()

集合的遍历

  • Iterator< E > 迭代器 集合的专用遍历方式
  • 是通过集合iterator 的方法得到的,依赖于集合而存在

Iterator 中的常用方法

  • E next()返回迭代器的下一个元素
  • boolean hasNext() 如果迭代器具有更多元素,则返回true,否则为false
    public static void main(String[] args) {
        Collection<String> c = new ArrayList<String>();
        c.add("hello");
        c.add("world");
        System.out.println(c);
        // [hello, world]
        Iterator<String> iterator = c.iterator();
//        if(iterator.hasNext()) {
//            System.out.println(iterator.next());  // hello
//        }
//
//        if(iterator.hasNext()) {
//            System.out.println(iterator.next());  // world
//        }
//
//        if(iterator.hasNext()) {
//            System.out.println(iterator.next());  // 不会执行
//        }
        // 循环遍历
        while (iterator.hasNext()){
            String s = iterator.next();
            System.out.println(s);
        }
    }



2.List 集合

  • 有序集合(也称为序列)
  • 可以通过整数索引访问元素
  • 允许重复的元素
    public static void main(String[] args) {
        List<String> list = new ArrayList<String>();
        list.add("hello");
        list.add("world");
        list.add("world");
        System.out.println(list);
        // [hello, world, world]
    }

List集合的特有方法

  • void add(int index,E e)
  • E remove(int index)
  • E set(int index,E e)
  • E get(int index)

都是通过索引的操作

        // 用for循环改进遍历
        for (int i = 0; i < list.size(); i++) {
            System.out.println(list.get(i));
        }


这里注意一个问题:并发修改异常

(ConcurrentModificationException)

当方法检测到对象的并发修改,但不允许这种修改时,抛出此异常。

例如,某个线程在 Collection 上进行迭代时,通常不允许另一个线性修改该 Collection。通常在这些情况下,迭代的结果是不确定的。如果检测到这种行为,一些迭代器实现(包括 JRE 提供的所有通用 collection 实现)可能选择抛出此异常。执行该操作的迭代器称为快速失败 迭代器,因为迭代器很快就完全失败,而不会冒着在将来某个时间任意发生不确定行为的风险。

来看一个案例:

    public static void main(String[] args) {
        List<String> list = new ArrayList<String>();
        list.add("hello");
        list.add("world");
        list.add("java");
        System.out.println(list);
        // [hello, world, world]
        // 用for循环改进遍历
//        for (int i = 0; i < list.size(); i++) {
//            System.out.println(list.get(i));
//        }
        Iterator<String> it = list.iterator();
        while (it.hasNext()){
            String s = it.next();
            if(s.equals("world")){
                list.add("javaee");
            }
        }
    }

这里的结果会报错:

在这里插入图片描述

产生并发修改异常的原因:

  • 在迭代器遍历的过程中,通过集合对象修改了集合中元素的长度,造成了迭代器获取元素中判断预期修改值和实际修改值不一致

解决方案

  • 用for 循环遍历,然后对集合对象做相应的操作即可,因为get的方法没有进行预期修改值和实际修改值的判断,add方法仍然进行了实际修改值的自增。

列表迭代器:

ListIterator< E > 是通过List 集合的listIterator() 的方法得到的。允许我们沿任一方向遍历该迭代器。

列表迭代器的常用方法:

  • E next() 返回迭代器的下一个元素
  • boolean hasNext() 如果具有更多元素,返回true
  • E previous() 返回迭代器的上一个元素
  • boolean hasPrevious() 在相反方向如果具有更多元素,返回true
  • void add(E e) 将指定的元素,插入在列表中,位置在该迭代器指向的下一个位置,并把迭代器的指向重新指向为新插入的元素。
    public static void main(String[] args) {
        List<String> list = new ArrayList<String>();
        list.add("hello");
        list.add("world");
        list.add("java");
        System.out.println(list);

// 生成列表迭代器
        ListIterator<String> lit = list.listIterator();
        while(lit.hasNext()){
            String s =lit.next();
            if(s.equals("world")){
                lit.add("javaee");
            }
            System.out.println(s);
        }

        System.out.println(list);



3. 增强for循环

简化数组和Collection集合的遍历

我们来看看Collection 的定义:

public interface Collection< E >extends Iterable< E >

它继承了 Iterable< E >

我们接着来看:public interface Iterable< T >

实现这个接口允许对象成为 “foreach” 语句的目标。(增强for 循环)

其内部原理是Iterator的迭代器

注意:容易发生并发修改异常

        // 增强 for循环
        int[] arr = new int[] {1,2,3};
        for (int i:arr) {
            System.out.println(i);
        }

所以我们目前如果想要遍历一个数组,可以用三种方法:

  • 迭代器遍历
  • 普通 for 循环
  • 增强 for 循环



4. List集合常用子类

  • ArrayList : 底层数据结构是可调节的动态数组,查询快,增删慢
  • LinkedList :底层数据结构是链表,查询慢,增删快

    常用的功能方法都继承了List 了,都差不多。
        // LinkedList 的特有功能
        LinkedList<String> linklist = new LinkedList<>();
        linklist.add("hello");
        linklist.add("world");
        linklist.add("java");

        linklist.addLast("javase");
        linklist.addLast("python");

        System.out.println(linklist.getFirst());
        System.out.println(linklist.getLast());

        System.out.println(linklist.removeFirst());
        System.out.println(linklist.removeLast());



5. Set 集合

public interface Set< E >extends Collection< E >

  • 不包含重复元素的集合
  • 没有带索引的方法,不能用for循环遍历
  • 可以用迭代器和增强 for 遍历

HashSet 实现了该接口,但是并不能保证数据的存储顺序

public class HashSet< E >extends AbstractSet< E >implements Set< E >, Cloneable, Serializable

实现了Set 的结构,此类实现 Set 接口,由哈希表(实际上是一个 HashMap 实例)支持。它不保证 set 的迭代顺序;特别是它不保证该顺序恒久不变。此类允许使用 null 元素。

哈希值:

哈希值是JDK根据对象的地址或字符串或数字,计算出来的int类型的数值

Object 类有一个 public int hashCode() 的方法或者对象的哈希值

HashSet 集合保证元素唯一性的原因:

在这里插入图片描述

哈希表:

是通过数组加链表实现的

在这里插入图片描述

案例:利用HashSet 存储对象并遍历:

        HashSet<String> hashSet = new HashSet<>();
        hashSet.add("hello");
        hashSet.add("world");
        hashSet.add("java");

        for (String s:hashSet
             ) {
            System.out.println(s);
        }

对于类对象,我们有时认为对象的成员变量一致时,则对象是重复的,所以我们需要重新写一下hashCode() 和 equals() 的方法,在Idea中,我们可以 Alt + Insert 可以自动生成这两个方法



6. LinkedHashSet 集合

public class LinkedHashSet< E >extends HashSet< E >implements Set< E >, Cloneable, Serializable

  • 由哈希表和链接列表实现,具有可预知迭代顺序
  • 由链表保证元素有序,也就是元素的存储和取出的顺序一致
  • 由哈希表保证元素唯一,元素不重复
        // LinkedHashSet
        LinkedHashSet<String> linkedHashSet = new LinkedHashSet<>();
        linkedHashSet.add("hello");
        linkedHashSet.add("world");
        linkedHashSet.add("java");

        for (String s:linkedHashSet
        ) {
            System.out.println(s);
        }
        // hello
        //world
        //java

LinkedHashSet 循环遍历的时候,元素的顺序是可以保证的



7. TreeSet 集合

public class TreeSet< E >extends AbstractSet< E >implements NavigableSet< E >, Cloneable, Serializable

  • 根据 NavigableSet< E > 间接实现了Set 的接口
  • 基于 TreeMap 的 NavigableSet 实现。使用元素的自然顺序对元素进行排序,或者根据创建 set 时提供的 Comparator 进行排序,具体取决于使用的构造方法。
  • 元素有序,可以按照一定的规则进行排序,具体排序方式取决于构造方法 TreeSet():根据其元素的自然排序进行排序
  • TreeSet(Comparator comparator) :根据指定的比较器进行排序
  • 没有带索引的方法,所以不能使用普通for循环遍历
  • 由于是Set集合,所以不包含重复元素的集合

案例:实现不重复随机数的生产和遍历

public class DetRandom {
    // 获取不重复的随机数
    // 获取10个1-20
    public static void main(String[] args) {
//        Set<Integer> s = new HashSet<>();

        Set<Integer> s = new TreeSet<Integer>();
        Random r = new Random();

        while (s.size()<10){
            int random = r.nextInt(20)+1;
            s.add(random);
        }

        for(Integer i: s){
            System.out.println(i);
        }

    }



8. 泛型

  • 是JDK5中引入的特性,它提供了编译时类型安全检测机制,该机制允许在编译时检测到非法的类型

    它的本质是参数化类型,也就是说所操作的数据类型被指定为一个参数。一提到参数,最熟悉的就是定义方法时有形参,然后调用此方法时传递实参。那么参数化类型怎么理解呢?顾名思义,就是将类型由原来的具体的类型参数化,然后在使用/调用时传入具体的类型。这种参数类型可以用在类、方法和接口中,分别被称为泛型类、泛型方法、泛型接口
  • 泛型定义格式

    • <类型>:指定一种类型的格式。这里的类型可以看成是形参
    • <类型1,类型2…>:指定多种类型的格式,多种类型之间用逗号隔开。这里的类型可以看成是形参将来具体调用时候给定的类型可以看成是实参,并且实参的类型只能是引用数据类型
  • 泛型的好处

    • 把运行时期的问题提前到了编译期间
    • 避免了强制类型转换

泛型的定义格式:

  • 泛型类: 修饰符 class 类名<类型> { }
  • 泛型方法 :修饰符 <类型> 返回值类型 方法名(类型 变量名) { }
  • 泛型接口 :修饰符 interface 接口名 < 类型 >

类型通配符:

  • 类型通配符的作用

    为了表示各种泛型List的父类,可以使用类型通配符
  • 类型通配符的分类

    • List<?>:表示元素类型未知的List,它的元素可以匹配任何的类型

      这种带通配符的List仅表示它是各种泛型List的父类,并不能把元素添加到其中
    • 类型通配符上限:<? extends 类型>

      List<? extends Number>:它表示的类型是Number或者其子类型
    • 类型通配符下限:<? super 类型>

      List<? super Number>:它表示的类型是Number或者其父类型
public class GenericDemo {
	public static void main(String[] args) {
	//类型通配符:<?> 
	List<?> list1 = new ArrayList<Object>(); 
	List<?> list2 = new ArrayList<Number>(); 
	List<?> list3 = new ArrayList<Integer>(); 
	System.out.println("--------");
	//类型通配符上限:<? extends 类型> // 
	List<? extends Number> list4 = new ArrayList<Object>(); 
	List<? extends Number> list5 = new ArrayList<Number>(); 
	List<? extends Number> list6 = new ArrayList<Integer>(); 
	System.out.println("--------");
	//类型通配符下限:<? super 类型> 
	List<? super Number> list7 = new ArrayList<Object>(); 
	List<? super Number> list8 = new ArrayList<Number>(); 
	// List<? super Number> list9 = new ArrayList<Integer>(); 
	} 
} 

可变参数:

  • 可变参数介绍

    可变参数又称参数个数可变,用作方法的形参出现,那么方法参数个数就是可变的了
  • 可变参数定义格式

    修饰符 返回值类型 方法名(数据类型… 变量名) { }
  • 可变参数的注意事项

    这里的变量其实是一个数组

    如果一个方法有多个参数,包含可变参数,可变参数要放在最后
public static int sum(int... a) {
 int sum = 0; 
 for(int i : a) { 
 sum += i; 
 }
 return sum; 
}

可变参数的使用:

  • Arrays工具类中有一个静态方法:

    public static List asList(T… a):返回由指定数组支持的固定大小的列表

    返回的集合不能做增删操作,可以做修改操作
  • List接口中有一个静态方法:(JDK9 新特性)

    public static List of(E… elements):返回包含任意数量元素的不可变列表

    返回的集合不能做增删改操作
  • Set接口中有一个静态方法:(JDK9 新特性)

    public static Set of(E… elements) :返回一个包含任意数量元素的不可变集合

    在给元素的时候,不能给重复的元素。返回的集合不能做增删操作,没有修改的方法



9.Map 集合

map集合的概述

  • interface Map<K,V> K:键的类型;V:值的类型

map 集合的特点

  • 键值对映射关系
  • 一个键对应一个值
  • 键不能重复,值可以重复
  • 元素存取无序

方法介绍:

在这里插入图片描述

public class MapDemo02 {
    public static void main(String[] args) {
        //创建集合对象
        Map<String,String> map = new HashMap<String,String>();

        //V put(K key,V value):添加元素
        map.put("张无忌","赵敏");
        map.put("郭靖","黄蓉");
        map.put("杨过","小龙女");

        //V remove(Object key):根据键删除键值对元素
//        System.out.println(map.remove("郭靖"));
//        System.out.println(map.remove("郭襄"));

        //void clear():移除所有的键值对元素
//        map.clear();

        //boolean containsKey(Object key):判断集合是否包含指定的键
//        System.out.println(map.containsKey("郭靖"));
//        System.out.println(map.containsKey("郭襄"));

        //boolean isEmpty():判断集合是否为空
//        System.out.println(map.isEmpty());

        //int size():集合的长度,也就是集合中键值对的个数
        System.out.println(map.size());


        //输出集合对象
        System.out.println(map);
    }
}

Map集合的获取功能

在这里插入图片描述

Map集合的遍历:

方式一:

  • 获取所有键的集合。用keySet()方法实现
  • 遍历键的集合,获取到每一个键。用增强for实现
  • 根据键去找值。用get(Object key)方法实现
public class MapDemo02 {
    public static void main(String[] args) {
        //创建集合对象
        Map<String, String> map = new HashMap<String, String>();

        //添加元素
        map.put("张无忌", "赵敏");
        map.put("郭靖", "黄蓉");
        map.put("杨过", "小龙女");

        //获取所有键值对对象的集合
        Set<Map.Entry<String, String>> entrySet = map.entrySet();
        //遍历键值对对象的集合,得到每一个键值对对象
        for (Map.Entry<String, String> me : entrySet) {
            //根据键值对对象获取键和值
            String key = me.getKey();
            String value = me.getValue();
            System.out.println(key + "," + value);
        }
    }
}

方式二:

  • 获取所有键值对对象的集合

    Set<Map.Entry<K,V>> entrySet():获取所有键值对对象的集合
  • 遍历键值对对象的集合,得到每一个键值对对象
  • 用增强for实现,得到每一个Map.Entry

    根据键值对对象获取键和值
  • 用getKey()得到键
  • 用getValue()得到值
public class HashMapDemo {
    public static void main(String[] args) {
        //创建HashMap集合对象
        HashMap<String, Student> hm = new HashMap<String, Student>();

        //创建学生对象
        Student s1 = new Student("林青霞", 30);
        Student s2 = new Student("张曼玉", 35);
        Student s3 = new Student("王祖贤", 33);

        //把学生添加到集合
        hm.put("itheima001", s1);
        hm.put("itheima002", s2);
        hm.put("itheima003", s3);

        //方式1:键找值
        Set<String> keySet = hm.keySet();
        for (String key : keySet) {
            Student value = hm.get(key);
            System.out.println(key + "," + value.getName() + "," + value.getAge());
        }
        System.out.println("--------");

        //方式2:键值对对象找键和值
        Set<Map.Entry<String, Student>> entrySet = hm.entrySet();
        for (Map.Entry<String, Student> me : entrySet) {
            String key = me.getKey();
            Student value = me.getValue();
            System.out.println(key + "," + value.getName() + "," + value.getAge());
        }
    }
}

案例:统计字符串中每个字符出现的次数

案例需求

  • 键盘录入一个字符串,要求统计字符串中每个字符串出现的次数。
  • 举例:键盘录入“aababcabcdabcde” 在控制台输出:“a(5)b(4)c(3)d(2)e(1)”
import java.util.HashMap;
import java.util.Scanner;
import java.util.Set;
import java.util.TreeMap;

/*
    需求:
        键盘录入一个字符串,要求统计字符串中每个字符串出现的次数。
        举例:键盘录入“aababcabcdabcde”	在控制台输出:“a(5)b(4)c(3)d(2)e(1)”

    思路:
        1:键盘录入一个字符串
        2:创建HashMap集合,键是Character,值是Integer
        3:遍历字符串,得到每一个字符
        4:拿得到的每一个字符作为键到HashMap集合中去找对应的值,看其返回值
            如果返回值是null:说明该字符在HashMap集合中不存在,就把该字符作为键,1作为值存储
            如果返回值不是null:说明该字符在HashMap集合中存在,把该值加1,然后重新存储该字符和对应的值
        5:遍历HashMap集合,得到键和值,按照要求进行拼接
        6:输出结果
 */
public class HashMapDemo {
    public static void main(String[] args) {
        //键盘录入一个字符串
        Scanner sc = new Scanner(System.in);
        System.out.println("请输入一个字符串:");
        String line = sc.nextLine();

        //创建HashMap集合,键是Character,值是Integer
//        HashMap<Character, Integer> hm = new HashMap<Character, Integer>();
        TreeMap<Character, Integer> hm = new TreeMap<Character, Integer>();

        //遍历字符串,得到每一个字符
        for (int i = 0; i < line.length(); i++) {
            char key = line.charAt(i);

            //拿得到的每一个字符作为键到HashMap集合中去找对应的值,看其返回值
            Integer value = hm.get(key);

            if (value == null) {
                //如果返回值是null:说明该字符在HashMap集合中不存在,就把该字符作为键,1作为值存储
                hm.put(key,1);
            } else {
                //如果返回值不是null:说明该字符在HashMap集合中存在,把该值加1,然后重新存储该字符和对应的值
                value++;
                hm.put(key,value);
            }
        }

        //遍历HashMap集合,得到键和值,按照要求进行拼接
        StringBuilder sb = new StringBuilder();

        Set<Character> keySet = hm.keySet();
        for(Character key : keySet) {
            Integer value = hm.get(key);
            sb.append(key).append("(").append(value).append(")");
        }

        String result = sb.toString();

        //输出结果
        System.out.println(result);
    }
}



10. Collections 集合工具类

Collections类的作用

  • 是针对集合操作的工具类

Collections类常用方法

在这里插入图片描述

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

/*
    Collections类的概述
        是针对集合操作的工具类

    Collections类的常用方法
        public static <T extends Comparable<? super T>> void sort​(List<T> list):将指定的列表按升序排序
        public static void reverse​(List<?> list):反转指定列表中元素的顺序
        public static void shuffle​(List<?> list):使用默认的随机源随机排列指定的列表
 */
public class CollectionsDemo01 {
    public static void main(String[] args) {
        //创建集合对象
        List<Integer> list = new ArrayList<Integer>();

        //添加元素
        list.add(30);
        list.add(20);
        list.add(50);
        list.add(10);
        list.add(40);

        //public static <T extends Comparable<? super T>> void sort​(List<T> list):将指定的列表按升序排序
//        Collections.sort(list);

        //public static void reverse​(List<?> list):反转指定列表中元素的顺序
//        Collections.reverse(list);

        //public static void shuffle​(List<?> list):使用默认的随机源随机排列指定的列表
        Collections.shuffle(list);

        System.out.println(list);
    }
}



最后附上一张集合框架图

在这里插入图片描述



版权声明:本文为xiaocl123原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。