骖六龙行御九州之第十七天——集合与泛型

1.集合

2.Collection

3.Iterator迭代器

4.泛型

一:集合

1.1集合的使用回顾

  • Arrarylist增删改查

  • 代码示例:

public static void main(String[] args) {
    ArrayList<Integer> list = new ArrayList<Integer>();
    //添加
    list.add(111);
    list.add(222);
    list.add(333);
    //删除
    list.remove(0);
    //修改
    list.set(2,256);
    //查看
    for(int i=0; i<list.size(); i++){
        //获取
        System.out.println(list.get(i));
    }
}

1.2:什么是集合

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

  • 为什么学习集合

    • 在前面的学习中,我们知道的数据多了,可以使用数组存放或者使用ArraryList集合进行存放数据。

    • 那么,集合和数组既然都是容器,它们有啥区别呢?

      • 数组的长度是固定的。集合的长度是可变的。

      • 集合中存储的元素必须是引用类型数据

  • Java如何表示集合

    • Java提供了集合API

      • 用大白话来说就是一个家族

1.3集合家族

  • 如下图所示

    • 清爽版

  • 难受版

  • 简约版

  • 总结:

    • Collection:所有单根集合父类

      • List:是一个有序集合,可以放重复的数据

      • Set:是一个无序集合,不允许放重复的数据

    • Map:双根

      • 是一个无序集合,集合中包含一个键对象,一个值对象,键对象不允许重复,值对象可以重复

      • hashMap

二:Collection

2.1什么是Collection?

  • 是单例集合的顶层接口,它表示一组对象,这些对象也称为Collection的元素 JDK 不提供此接口的任何直接实现,它提供更具体的子接口(如Set和List)实现

  • Collection所代表的是一种规则(接口),它所包含的元素都必须遵循一条或者多条规则。

    • 有些集合允许重复元素

    • 而其他集合不允许

    • 方法名统一了(单根)

      • add

      • get

      • set

2.2:使用Collection

Collection c =null;   //创建对象实例化
c = new ArrayList<>();

2.2.3:构造方法

  • 因为该类是接口,所以没有构造方法

  • 创建实例

    • 多态

  • Collection c = new ArrayList<>();
    • 匿名内部类

    • 方法

2.2.4:常用方法

三:Iterator迭代器

3.1:什么是Iterator迭代器

  • 迭代器(Iterator)是一个对象,它的工作是遍历目标序列中的对象(获取集合中的数据),它提供了一种访问一个容器(container)对象中的各个元素的方法(提供了一种手段,专业获取数据),把访问逻辑从不同类型的集合类中抽象出来,又不必暴露该对象内部细节。通过迭代器,开发人员不需要了解容器底层的结构,就可以实现对容器的遍历。由于创建迭代器的代价小,因此迭代器通常被称为轻量级的容器。

  • 常常使用JDK提供的迭代接口进行Java集合的迭代。

  • 大白话就是专业的事情,找专业的人办

    • 代码实例

Iterator iterator = list.iterator();
    while(iterator.hasNext()){
            String string = iterator.next();
            //do something
            }
  • 在没有迭代器时,我们都是这么进行遍历元素的,如下:

    • 对于数组,我们是使用下标进行处理的P:

      • 代码实例

  int[] arrays = new int[10];
for(int i = 0 ; i < arrays.length ; i++){
        int a = arrays[i];
        //do something
        }
  • 对于这两种方式,我们总是都是先知道集合的内部结构,访问代码和集合本身是紧密耦合的,无法将访问逻辑从集合类和客户端代码中分离出来。同时每一种集合对应一种遍历方法,客户端代码无法复用。 在实际应用中如何需要将上面将两个集合进行整合是相当麻烦的。所以为了解决以上问题,Iterator模式腾空出世,它总是用同一种逻辑来遍历集合。使得客户端自身不需要来维护集合的内部结构,所有的内部状态都由Iterator来维护。客户端从不直接和集合类打交道,它总是控制Iterator,向它发送"向前","向后","取当前元素"的命令,就可以间接遍历整个集合。

  • 集合的内部结构不同,造成了一个结果。取值,也应该有自己的专业方式

  • 总结

    • 迭代器,就是一个类。作用是专业取数据!

3.2:如何使用迭代器

  • 迭代器,其实是一个内部类。每个集合都给自己提供了一个方法,可以帮我们得到迭代器的实例

  • 获取迭代器

  • 判断是否有元素

  • 获取元素

  • 代码实例

package 获取迭代器;
​
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
​
public class Demo {
    public static void main(String[] args) {
        Collection list = new ArrayList<>();
        list.add(1);
        list.add(2);
        list.add(3);
        Iterator it = list.iterator();
        while (it.hasNext()){
            System.out.println(it.next());
        }
    }
}

3.3迭代器的原理

  • 每个集合的内部都维护了一个Iterator迭代器(内部类)

例如:ArrayList集合

    • 在ArrayList内部首先是定义一个内部类Itr,该内部类实现Iterator接口,如下:

private class Itr implements Iterator<E> {
    int cursor;       // index of next element to return 前进
    int lastRet = -1; // index of last element returned; -1 if no such 后退
    int expectedModCount = modCount;
    //do something
}
    • 而ArrayList的iterator()方法实现:

public Iterator<E> iterator() {
        return new Itr();
    }
    • 所以通过使用ArrayList.iterator()方法返回的是Itr()内部类,所以现在我们需要关心的就是Itr()内部类的实现:

      • 在Itr内部定义了三个int型的变量:cursor、lastRet、expectedModCount。

        • 其中cursor表示下一个元素的索引位置

        • lastRet表示上一个元素的索引位置。

        • 从cursor、lastRet定义可以看出,lastRet一直比cursor少一所以hasNext()实现方法异常简单,只需要判断cursor和lastRet是否相等即可。

        • 代码实例

public boolean hasNext() {
    return cursor != size;
}
      • 对于next()实现其实也是比较简单的,只要返回cursor索引位置处的元素即可,然后修改cursor、lastRet即可:

        • 代码实例

   public E next() {
            checkForComodification();
            int i = cursor;    //记录索引位置
            if (i >= size)    //如果获取元素大于集合元素个数,则抛出异常
                throw new NoSuchElementException();
            Object[] elementData = ArrayList.this.elementData;
            if (i >= elementData.length)
                throw new ConcurrentModificationException();
            cursor = i + 1;      //cursor + 1
            return (E) elementData[lastRet = i];  //lastRet + 1 且返回cursor处元素
        }
               final void checkForComodification() {
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
        }
        • heckForComodification()主要用来判断集合的修改次数是否合法,即用来判断遍历过程中集合是否被修改过。modCount用于记录ArrayList集合的修改次数,初始化为0,每当集合被修改一次(结构上面的修改,内部update不算),如add、remove等方法,modCount + 1,所以如果modCount不变,则表示集合内容没有被修改。该机制主要是用于实现ArrayList集合的快速失败机制,在Java的集合中,较大一部分集合是存在快速失败机制的。所以要保证在遍历过程中不出错误,我们就应该保证在遍历过程中不会对集合产生结构上的修改(当然remove方法除外),出现了异常错误,我们就应该认真检查程序是否出错而不是catch后不做处理。

      • 对于remove()方法的是实现,它是调用ArrayList本身的remove()方法删除lastRet位置元素,然后修改modCount即可

        • 代码实例

public void remove() {
            if (lastRet < 0)
                throw new IllegalStateException();
            checkForComodification();
 
            try {
                ArrayList.this.remove(lastRet);
                cursor = lastRet;
                lastRet = -1;
                expectedModCount = modCount;
            } catch (IndexOutOfBoundsException ex) {
                throw new ConcurrentModificationException();
            }
        }
  • ListIterator:

    • 接口Iterator在不同的子接口中会根据情况进行功能的扩展,例如针对List的迭代器ListIterator,该迭代器只能用于各种List类的访问。ListIterator可以双向移动。添加了previous()等方法。如果是List集合,想要在迭代中操作元素可以使用List集合的特有迭代器ListIterator,该迭代器支持在迭代过程中,添加元素和修改元素。

3.3:for循环、forEach、Irerator对比

  • 代码实例

package 三种遍历方式的区别;
​
import java.util.ArrayList;
import java.util.Iterator;
​
public class Demo {
    public static void main(String[] args) {
        ArrayList<Integer> list = new ArrayList<>();
        list.add(1);
        list.add(2);
        list.add(3);
        for (int i = 0;i< list.size();i++){
            System.out.println(list.get(i));
        }
        for (Integer arrayList:list){
            System.out.println(list);
        }
        Iterator it = list.iterator();
        while (it.hasNext()){
            System.out.println(it.next());
        }
    }
}
  • 相同点:都是用于遍历集合元素的。

  • 不同点

  1. 形似差别(样子差的多)

//for循环的形式是:
for(int i=0;i<arr.size();i++){...}
​
//foreach的形式是:
for(int i:arr){...}
​
//iterator的形式是
Iterator it = arr.iterator();
while(it.hasNext()){ object o =it.next(); ...}

2.条件差别:()内容不一样

    • for循环需要知道集合或数组的大小,而且需要是有序的,不然无法遍历;

    • foreach和iterator都不需要知道集合或数组的大小,他们都是得到集合内的每个元素然后进行处理;

3.多态差别:

      • 需要访问内部的成员,不能实现多态;

    • iterator是一个接口类型,可以使用相同方式去遍历不同集合中元素,而不用考虑集合类的内部实现(只要它实现了 java.lang.Iterable 接口),而且他还能随时修改和删除集合的元素,能够将遍历序列的操作与序列底层的结构分离。迭代器统一了对容器的访问方式。这也是接口的解耦的最好体现。

    • 例如:如果使用 Iterator 来遍历集合中元素,一旦不再使用 List 转而使用 Set 来组织数据,那遍历元素的代码不用做任何修改,如果使用 for 来遍历,那所有遍历此集合的算法都得做相应调整,因为List有序,Set无序,结构不同,他们的访问算法也不一样。

    • iterator获取到的内容,如果不使用泛型约束,获取到数据类型都会提升到Object类型

4.效率差别:

    • 采用ArrayList对随机访问比较快,而for循环中的get()方法,采用的即是随机访问的方法,因此在ArrayList里,for循环较快。

      • 如果集合是ArrayList集合,我们想要随机获取。三种方式,建议使用for

    • 采用LinkedList则是顺序访问比较快,iterator中的next()方法,采用的即是顺序访问的方法,因此在LinkedList里,使用iterator较快。

      • 如果集合是LinkedList集合,而且顺序访问。使用iterator较快

    • 从数据结构角度分析,for循环适合访问顺序结构,可以根据下标快速获取指定元素.而Iterator 适合访问链式结构,因为迭代器是通过next()和Pre()来定位的.可以访问没有顺序的集合。

    • 集合的数据结构不一样,需求不一样。三者的取值速度就会有所差异

5.foreach 和 iterator 的其他区别:

    • 使用foreach循环语句的优势在于更加简洁,更不容易出错,不必关心下标的起始值和终止值,底层由iterator实现的,他们最大的不同之处就在于remove()方法上。

    • 如果在forEach循环的过程中调用集合的remove()方法,就会导致循环出错,因为循环过程中list.size()的大小变化了,就导致了错误。 所以,如果想在循环语句中删除集合中的某个元素,就要用迭代器iterator的remove()方法,因为它的remove()方法不仅会删除元素,还会维护一个标志,用来记录目前是不是可删除状态,例如,你不能连续两次调用它的remove()方法,调用之前至少有一次next()方法的调用。

3.4:集合迭代中的转型

  1. 在使用集合时,我们需要注意以下几点:

    • 集合中存储其实都是对象的地址。

    • 集合中可以存储基本数值吗?jdk1.5版本以后可以存储了。

      • 因为出现了基本类型包装类,它提供了自动装箱操作(基本类型对象),这样,集合中的元素就是基本数值的包装类对象。

2.存储时提升了Object。取出时要使用元素的特有内容,必须向下转型。

package 集合向下转型;
​
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
​
public class Demo {
    public static void main(String[] args) {
        Collection c= new ArrayList();
        c.add("a");
        c.add("b");
        c.add("c");
        Iterator it = c.iterator();
        while (it.hasNext()){
             System.out.println(((String) it.next()).substring(0));
        }
    }
}

四、泛型

4.1什么是泛型

  • 泛型,即“参数化类型”

    • 在前面学习集合时,我们都知道集合中是可以存放任意对象的,只要把对象存储集合后,那么这时他们都会被提升成Object类型。当我们在取出每一个对象,并且进行相应的操作,这时必须采用类型转换

2.ArrayList可以存放任意类型,例子中添加了一个String类型,添加了一个Integer类型,再使用时都以String的方式使用,因此程序崩溃了。为了解决类似这样的问题(在编译阶段就可以解决),泛型应运而生。

  • 我们将第一行声明初始化list的代码更改一下,编译器会在编译阶段就能够帮我们发现类似这样的问题。

package 泛型;
​
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
​
public class Demo {
    public static void main(String[] args) {
        ArrayList<String> c= new ArrayList();
        c.add("a");
        c.add("b");
        c.add("c");
        Iterator it = c.iterator();
        while (it.hasNext()){
            System.out.println(((String) it.next()).substring(0));
        }
    }
}

4.2:Java中的伪泛型

  • Java中的伪泛型:

    • 泛型只在编译时存在,编译后就被擦除,在编译之前我们就可以限制集合的类型,起到作用

    • 例如:

      • 编译前:ArrayList al=new ArrayList();

      • 编译后:ArrayList al=new ArrayList();

4.3如何使用泛型

  • 泛型有三种使用方式,分别为:泛型类、泛型接口、泛型方法

4.3.1:泛型类

  • 泛型类型用于类的定义中,被称为泛型类。通过泛型可以完成对一组类的操作对外开放相同的接口。最典型的就是各种容器类,如:List、Set、Map。

    • 在定义类的时候,该类后后面如果有泛型,该类就是泛型类

class 类名称 <泛型标识:可以随便写任意标识号,标识指定的泛型的类型>{
  private 泛型标识 /*(成员变量类型)*/ var; 
  .....
​
  }
}
  • 一个最普通的泛型类:

    • 泛型类

package 泛型.泛型类;
​
import java.util.ArrayList;
import java.util.Collection;
​
public class Demo {
    public static void main(String[] args) {
        A<String> a = new A<>();
        a.m("");
​
    }
}
class A<E>{
    public void m(E e){
        System.out.println("张毅是个小可爱");
    }
​
}
 

4.3.2:泛型接口

  • 格式:

package 泛型.泛型类;
​
public class Demo1 {
    public static void main(String[] args) {
       new B<>(){        //匿名内部类
           @Override
           public void m(Object o) {
           }
       };
​
​
    }
}
interface B <E>{
    public void m(E e);
}

4.3.3:泛型方法

  • 格式:修饰符 <泛型> 返回值类型 方法名 (参数){}

  • 注意

package 泛型方法12;
​
public class Demo {
    public static void main(String[] args) {
        m("abc");
    }
​
    public  static <E> void  m(E e){
​
        System.out.println(e.getClass());
​
    }
}

4.4:泛型优点

  • 泛型的好处

  1. 将运行时期的ClassCastException,转移到了编译时期变成了编译失败。

  2. 避免了类型强转的麻烦。

4.5:泛型通配符问题

  • 通配符类型可以理解为一种泛型调用时传递的一种特殊数据类型,表示参数允许在某个范围内变化。通配符类型有三种,分别是

    • ?

    • ? extends

    • ? super

  • 通配符表示一种未知类型,并且对这种未知类型存在约束关系.

4.5.1:通配符类型(?)

  • :无边界

      • 不要单独使用(直接用)

      • 参数化

  • 代码实例(直接用)

package 通配符14;
​
import java.util.ArrayList;
​
public class Demo {
    public static void main(String[] args) {
        ArrayList<?> list = new ArrayList<>();
​
        
    }
}
​
package 通配符14;
​
import java.util.ArrayList;
​
public class Demo {
    public static void main(String[] args) {
        ArrayList<Integer> list = new ArrayList<>();
        ArrayList<String> list2 = new ArrayList<>();
        m(list2);
​
    }
​
    public static void m(ArrayList<?>  list){
​
​
    }
}
​

4.5.2: 上限通配

    • 这里?表示一个未知的类,而T是一个具体的类,在实际使用的时候T需要替换成一个具体的类,表示实例化的时候泛型参数要是T或T的子类。

import java.util.ArrayList;
​
public class Demo {
    public static void main(String[] args) {
        ArrayList<Integer> list = new ArrayList<>();
        ArrayList<Double> list2 = new ArrayList<>();
        ArrayList<Object> list3 = new ArrayList<>();
        m(list3);
​
    }
​
    public static void m(ArrayList<? extends Number>  list){
​
​
    }
}

4.5.3: 下限通配

import java.util.ArrayList;
​
public class Demo {
    public static void main(String[] args) {
        ArrayList<Integer> list = new ArrayList<>();
        ArrayList<Double> list2 = new ArrayList<>();
        ArrayList<Object> list3 = new ArrayList<>();
        ArrayList<Number> list4 = new ArrayList<>();
        m(list4);
​
    }
​
    public static void m(ArrayList<? super Number>  list){
​
​
    }
}
​

4.5.4:常见泛型表示

  • E、T、K、V、N

  • 本质上以下字母都可以相互替换,但我们按照下面定义约定俗成的含义来使用。

  1. E - Element (在集合中使用,因为集合中存放的是元素)

  2. T - Type(Java 类)

  3. K - Key(键)

  4. V - Value(值)

  5. N - Number(数值类型)

  6. ? - 表示不确定的java类型

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值