打怪升级之小白的大数据之旅(十八)<Java面向对象进阶之集合一概述>

打怪升级之小白的大数据之旅(十八)

Java面向对象进阶之集合一概述

上次回顾

上一章,我们学习了Java中的一些常用核心类如Math,System,Arrays,包装类等,理解起来其实都不是很难,主要是方法比较多,当我们需要的时候,随机查询文档就好,本章开始对集合进行分享,我会将集合拆分开来慢慢讲解,集合不论是Java工程师,还是大数据都是比较重要的一个知识点。好了,开始进入正题…

集合

集合是什么?

  • 集合是java提供的一种容器,可以用来存储多个对象数据
  • 集合是java中的重点,基本上面试java中集合的考点最多,对于大数据来说,集合也是相对来说的重点
  • 首先看一下Java集合的框架图集合框架图
    我先介绍一下整个框架,然后再对内部进行讲解,这样便于大家理解:
    • 首先,collection和map,Collection 表示一组对象,Map表示一组映射关系或键值对
    • 它们哥俩实际上是一个根接口,它们根据实际的不同功能,就有了子接口,如collection中的List和Set,map中的HashMap和Hashtable等
    • 当有了这些子接口,我们就需要针对不同的需求场景定义不同的接口实现类,拿List来说,实现类就诞生出了ArrayList,LinkedList和Vector
    • 了解清楚集合的构成之后,下面我介绍一下集合的迭代器,前面我们说到,集合是Java提供的一种容器,用来存储多个对象数据,那么自然我们就需要有一种方法来从容器中获取对象数据,这个方法就是迭代器接口,因为我们使用List集合情况很多,因此,专门有一个子迭代器接口Listlterator。
    • 对象排序接口,容器工具类这些都是跟迭代器一样,为容器服务的接口,后面我们会详细介绍这两个

为什么要有集合?

  • 集合的存储的元素是引用数据类型的容器
  • 当我们看到这个概念时,不免会有疑问,集合是存储引用数据类型的容器,数组既可以存储引用数据类型,也可以存储基本数据类型,那么,为什么要有集合?
  • 下面,我们来看看数组与集合的对比
数组集合
数组创建后,长度不可变集合创建后,长度可变
数组中提供的属性和方法较少,常用增删改查操作不方便集合提供了更丰富的API可以对内部元素进行操作
数组存储数据的特点单一,只能是有序的,可重复的集合存储多元化,可以对无序数据进行存储
数组中可以存储基本数据类型,也可以存储引用数据类型集合只能存储引用数据类型

虽然集合只能存储引用数据类型,不过别忘了,我们上一章讲了包装类,因此,基本数据类型也可以存储进去哦

Collection

概述

  • 前面提到了Collection是集合两大跟接口中的一个,Collection表示一组对象,这些对象也称为collection的元素
  • 一些collection允许有重复的元素,一些不可以,因此,针对这个问题,我们就创建了两个子接口List和Sat来分别对collection进行存储
  • 在java中,JDK不提供根接口Collection的任何直接实现,因此,Collection接口通常用来传递collection,并在需要最大普遍性的地方操作这些collection
  • Collection<E>是父接口,因此,它提供了可用于操作所有所属集合的方法,<E>是泛型,我们下面会简单讲解一下它的用法,后面会单独对其详细展开来讲

常用方法

  • 添加元素
    • add(): 添加元素对象到当前结合中
    • addAll(Collection other):添加other集合中的所有元素对象到当前集合中
    • 示例代码:
      @Test
      	public void test2(){
      		Collection coll = new ArrayList();
      		coll.add(1);
      		coll.add(2);
      		
      		System.out.println("coll集合元素的个数:" + coll.size());
      		
      		Collection other = new ArrayList();
      		other.add(1);
      		other.add(2);
      		other.add(3);
      		
      		coll.addAll(other);
      //		coll.add(other);
      		System.out.println("coll集合元素的个数:" + coll.size());
      	}
      
    • add与addAll方法有一个需要注意的点:在这里插入图片描述
    • add方法在添加元素是,如果是单独添加,那么它在集合中就是单独存储的,如图第一排,当add方法添加一个数组时,它会将数组当作一个元素进行存储,如图右下那个,而addAll方法是会将数据单独进行存放
    • 因此,当我们在对数据进行添加时,如果是单独的数据,那么使用add方法,当我们需要对一组数据进行添加时,就使用addAll方法
  • 删除元素
    • boolean remove(Object obj) :从当前集合中删除第一个找到的与obj对象equals返回true的元素。
    • boolean removeAll(Collection<?> coll):从当前集合中删除所有与coll集合中相同的元素
  • 判断
    • boolean isEmpty():判断当前集合是否为空集合。
    • boolean contains(Object obj):判断当前集合中是否存在一个与obj对象equals返回true的元素
    • boolean containsAll(Collection<?> c):判断c集合中的元素是否在当前集合中都存在。即c集合是否是当前集合的“子集”
  • 获取元素个数
    • int size():获取当前集合中实际存储的元素个数
    • 这个方法我们后面对集合的遍历会用到
  • 交集
    • boolean retainAll(Collection<?> coll):当前集合仅保留与c集合中的元素相同的元素,即当前集合中仅保留两个集合的交集
    • 交集的意思就是两个集合中共有的部分
  • 转为数组
    • Object[] toArray():返回包含当前集合中所有元素的数组
  • 示例代码:老样子,我就不一 一举例了,综合练习一下
    import java.util.ArrayList;
    import java.util.Collection;
    
    public class Demo1Collection {
        public static void main(String[] args) {
    		// 创建集合对象 
        	// 使用多态形式
        	Collection<String> coll = new ArrayList<String>();
        	// 使用方法
        	// 添加功能  boolean  add(String s)
        	coll.add("小李广");
        	coll.add("扫地僧");
        	coll.add("石破天");
        	System.out.println(coll);
    
        	// boolean contains(E e) 判断o是否在集合中存在
        	System.out.println("判断  扫地僧 是否在集合中"+coll.contains("扫地僧"));
    
        	//boolean remove(E e) 删除在集合中的o元素
        	System.out.println("删除石破天:"+coll.remove("石破天"));
        	System.out.println("操作之后集合中元素:"+coll);
        	
        	// size() 集合中有几个元素
    		System.out.println("集合中有"+coll.size()+"个元素");
    
    		// Object[] toArray()转换成一个Object数组
        	Object[] objects = coll.toArray();
        	// 遍历数组
        	for (int i = 0; i < objects.length; i++) {
    			System.out.println(objects[i]);
    		}
    
    		// void  clear() 清空集合
    		coll.clear();
    		System.out.println("集合中内容为:"+coll);
    		// boolean  isEmpty()  判断是否为空
    		System.out.println(coll.isEmpty());  	
    	}
    }
    

iterator迭代器

概述

  • 我们现在知道了集合怎么创建了,下面肯定就需要对这个集合进行遍历,因此,下面我来为大家介绍一下迭代器iterator
  • 首先普及一下迭代这个词:即Collection集合元素的通用获取方式。在取元素之前先要判断集合中有没有元素,如果有,就把这个元素取出来,继续在判断,如果还有就再取出出来。一直把集合中的所有元素全部取出。这种取出方式专业术语称为迭代
  • 针对经常需要遍历集合中所有元素的需求,JDK提供了一个接口java.util.Iterator,它也是Java集合中的一员,Collection与Map接口主要用于存储元素,Iterator重要用于迭代访问(遍历)Collection中的元素,因此Iterator对象被称为迭代器

获取迭代器的方法

- public Iterator iterator()获取集合对应的迭代器,用来遍历集合中的元素的

  • 注意一下,迭代器的使用并不是new()一个迭代器,而是直接使用集合.iterator()的方法进行使用

iterator接口的常用方法及使用

  • public E next():返回迭代的下一个元素。
  • public boolean hasNext():如果仍有元素可以迭代,则返回 true。
  • public void remove():通过迭代器删除元素
  • 接下来我们通过案例学习如何使用Iterator迭代集合中元素
    import java.util.ArrayList;
    import java.util.Collection;
    import java.util.Iterator;
    
    public class Demo {
        public static void main(String[] args) {
            // 使用多态方式 创建对象
            Collection coll = new ArrayList();
    
            // 添加元素到集合
            coll.add("串串星人");
            coll.add("吐槽星人");
            coll.add("汪星人");
            //遍历
            //使用迭代器 遍历   每个集合对象都有自己的迭代器
            Iterator it = coll.iterator();
            while(it.hasNext()){ //判断是否有迭代元素
                String s = (String)it.next();//获取迭代出的元素,因为不知道it.next()的类型,因此需要向下转型
                System.out.println(s);
            }
        }
    }	
    

注: 在进行集合元素取出时,如果集合中已经没有元素了,还继续使用迭代器的next方法,将会发生java.util.NoSuchElementException没有集合元素的错误

迭代器的实现原理

  • 我们在之前案例已经完成了Iterator遍历集合的整个过程。当遍历集合时,首先通过调用集合的iterator()方法获得迭代器对象,然后使用hashNext()方法判断集合中是否存在下一个元素,如果存在,则调用next()方法将元素取出,否则说明已到达了集合末尾,停止遍历元素
  • Iterator迭代器对象在遍历集合时,内部采用指针的方式来跟踪集合中的元素,为了让初学者能更好地理解迭代器的工作原理,接下来通过一个图例来演示Iterator对象迭代元素的过程:
    迭代器原理
  • 具体步骤:
    • 在调用Iterator的next方法之前,迭代器的索引位于第一个元素之前,指向第一个元素
    • 当第一次调用迭代器的next方法时,返回第一个元素,然后迭代器的索引会向后移动一位,指向第二个元素,
    • 当再次调用next方法时,返回第二个元素,然后迭代器的索引会再向后移动一位,指向第三个元素,
    • 依此类推,直到hasNext方法返回false,表示到达了集合的末尾,终止对元素的遍历。

增强for与iterable接口

  • 首先介绍一下增强for循环,我们前面的案例都是使用List的实现类ArrayList进行演示的,ArrayList等下会详细介绍,在上面的案例中,我们会发现ArrayList实例化的对象和数组很像,那么我们可不可以使用我们熟悉的数组遍历for循环进行遍历呢?
  • 当然可以.我先卖个关子,等会介绍ArrayList再将怎么使用普通for循环进行遍历
  • 前面学习了iterator对集合进行遍历,下面这个增强for循环,其原理就是使用迭代器实现的,只是使用增强for更加便捷,
  • 定义格式
    for(元素的数据类型  变量 : Collection集合or数组){ 
      	//写操作代码
    }
    
  • 示例代码如下:
    // 遍历数组
    public class NBForDemo1 {
        public static void main(String[] args) {
    		int[] arr = {3,5,6,87};
           	//使用增强for遍历数组
    		for(int a : arr){//a代表数组中的每个元素
    			System.out.println(a);
    		}
    	}
    }
    // 遍历集合
    public class NBFor {
        public static void main(String[] args) {        
        	Collection<String> coll = new ArrayList<String>();
        	coll.add("小河神");
        	coll.add("老河神");
        	coll.add("神婆");
        	//使用增强for遍历
        	for(String s :coll){//接收变量s代表 代表被遍历到的集合元素
        		System.out.println(s);
        	}
    	}
    }
    

java.lang.Iterable接口与增强for实现原理

  • 在上面案例中,我们会发现,数组也可以使用增强for循环进遍历,这是为什么呢?下面就来介绍一下Iterable接口
  • 它的作用就是实现这个接口允许对象成为 “foreach” 语句的目标
  • Java 5时Collection接口继承了Iterable接口,因此Collection系列的集合就可以直接使用foreach循环遍历
  • Iterable接口的抽象方法:
    • public Iterator iterator(): 获取对应的迭代器,用来遍历数组或集合中的元素的。
  • 自定义某容器类型,实现java.lang.Iterable接口,发现就可以使用foreach进行迭代。
  • 自定义一个可以使用for循环的实现类
    import java.util.Iterator;
    public class TestMyArrayList {
    	public static void main(String[] args) {
    		MyArrayList<String> my = new MyArrayList<>();
    		for(String obj : my) {
    			System.out.println(obj);
    		}
    	}
    }
    class MyArrayList<T> implements Iterable<T>{
    	@Override
    	public Iterator<T> iterator() {
    		return null;
    	}
    }
    
  • 我们发现,自定义的迭代器可以使用增强for循环进行遍历了,所以我们就可以得出前面总结的结论:增强for循环(for each)本质上就是使用Iterator迭代器进行遍历的
  • 为了进一步理解这个机制,我们使用案例与断点调试来了解增强for循环的具体实现步骤:
import java.util.ArrayList;
import java.util.Collection;

public class TestForeach {
	public static void main(String[] args) {
		Collection<String> coll = new ArrayList<>();
		coll.add("陈琦");
		coll.add("李晨");
		coll.add("邓超");
		coll.add("黄晓明");
		
		//调用ArrayList里面的Iterator iterator()
		for (String str : coll) {
			System.out.println(str);
		}
	}
}

源码实现步骤:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

modCount与fail-fast机制(快速失败机制)

  • 当使用foreach或Iterator迭代器遍历集合时,同时调用迭代器自身以外的方法修改了集合的结构,例如调用集合的add和remove方法时,就会报ConcurrentModificationException
  • 示例代码:
    import java.util.ArrayList;
    import java.util.Collection;
    import java.util.Iterator;
    
    public class TestForeach {
    	public static void main(String[] args) {
    		Collection<String> list = new ArrayList<>();
    		list.add("hello");
    		list.add("java");
    		list.add("atguigu");
    		list.add("world");
    		
    		Iterator<String> iterator = list.iterator();
    		while(iterator.hasNext()){
    			list.remove(iterator.next());
    		}
    	}
    }
    
  • 什么是快速失败机制?
    • 如果在Iterator、ListIterator迭代器创建后的任意时间从结构上修改了集合(通过迭代器自身的 remove 或 add 方法之外的任何其他方式),则迭代器将抛出 ConcurrentModificationException。
    • 因此,面对并发的修改,迭代器很快就完全失败,而不是冒着在将来不确定的时间任意发生不确定行为的风险。
    • 这样设计是因为,迭代器代表集合中某个元素的位置,内部会存储某些能够代表该位置的信息。当集合发生改变时,该信息的含义可能会发生变化,这时操作迭代器就可能会造成不可预料的事情
    • 因此,果断抛异常阻止,是最好的方法。这就是Iterator迭代器的快速失败(fail-fast)机制
  • 如何实现快速失败机制呢?
    • 在ArrayList等集合类中都有一个modCount变量。它用来记录集合的结构被修改的次数。
    • 当我们给集合添加和删除操作时,会导致modCount++。
    • 然后当我们用Iterator迭代器遍历集合时,创建集合迭代器的对象时,用一个变量记录当前集合的modCount。例如:int expectedModCount = modCount;,并且在迭代器每次next()迭代元素时,都要检查 expectedModCount != modCount,
    • 如果不相等了,那么说明你调用了Iterator迭代器以外的Collection的add,remove等方法,修改了集合的结构,使得modCount++,值变了,就会抛出ConcurrentModificationException。
  • 正确的遍历删除
    • 我们知道了快速失败机制之后,当确实有需求,需要进行元素删除,应该怎么办呢?其实吧,迭代器自己有一个remove()方法,调用这个方法就可以删除了
    • 还是前面示例代码,我们再次对其元素进行删除:
      public class TestForeach {
          public static void main(String[] args) {
              Collection<String> list = new ArrayList<>();
              list.add("hello");
              list.add("java");
              list.add("atguigu");
              list.add("world");
      
              Iterator<String> iterator = list.iterator();
              while(iterator.hasNext()){
                  // 这个删除是collection的方法,删除会报异常
                  // list.remove(iterator.next());
                  // 正确的删除方式
                  iterator.next();// 首先取出这个元素
                  iterator.remove(); // 然后调用迭代器的删除方法进行删除
              }
          }
      }
      

总结

本章节着重从集合的概念,集合的基本方法、迭代器以及快速访问机制进行了分享,本章的一些案例都是使用ArrayList实现类进行举例说明的,因为我们下一章会对List接口以及其实现类ArrayList/LinkedList进行讲解。好了,本期内容就是这些,感谢大家耐心观看,欢迎后台吐槽。

posted @ 2021-04-16 18:15  数据民工  阅读(19)  评论(0)    收藏  举报