首页 > 其他分享 >Collection实现类、迭代器、泛型

Collection实现类、迭代器、泛型

时间:2024-09-30 22:22:56浏览次数:12  
标签:String 迭代 元素 Collection 集合 add 泛型 public

1.Collection实现类

1.1.集合的由来

        先说说集合的由来,假如现在有一个需求:存储80个学生的一门成绩。

那么我们有以下存储方式:
        1.变量:如果存取内容比较多,需要一个一个声明,声明80个变量,每个变量存储一个学生成绩
                  int i1=100;
                  int i2=30;
                       .....(显然非常麻烦,我们也不会去用)
        2.数组:相对于变量,可以一次性声明多个变量,而我们必须要提前预估容量(数组的长度),才能声明数组,如果已经存满了80个学生成绩,又来20个学生成绩,利用扩容机制(类似StringBuilder扩容原理)。重新开辟长度为100数组 new int[100],将原来的80个学生成绩拷贝到新数组中,再把新来的20个成绩也拷贝到新数组中.(这种方式显然比上一种好得多)
        3.集合(Collection)容器:方便开发者使用,集合是JDK提供的,别人写好的,我们关心的角度不再是数组的开辟容量以及扩容,我们关心的焦点 增(添加元素) 删(删除元素) 改(修改元素) 查(获取元素/判断元素)。

        那么集合是如何对元素进行存储和增删改查等操作呢,下面来详细看看。

1.2.集合的主要继承体系

        还有就是要知道集合的主要继承体系如下图:

        当然集合的继承之间的关系肯定没上图这么简单,不一定是直接继承,也有可能是间接继承,但最重要的是掌握上图中的主要继承体系中的接口和类的实现。

        既然Collection接口是集合中的顶层接口,那么它中定义的所有功能子类都可以使用。查阅API中描述的Collection接口。Collection 层次结构中的根接口。Collection 表示一组对象,这些对象也称为 collection 的元素。一些 collection 允许有重复的元素,而另一些则不允许。一些 collection 是有序的,而另一些则是无序的。


1.3.Collection体系通用方法

        继续查阅API ,发现Collection接口中很多集合的操作方法,那么这些方法都具体能做什么呢?这里我们不关心具体创建的Collection中的那个子类对象,这里重点演示的是Collection接口中的方法。
        创建集合的格式:
        Collection 变量名 = new ArrayList();//父类引用指向子类对象
添加&获取功能:

        boolean add(Object e):向集合容器中添加元素
        int size():获取存储的元素个数
        c.clear():清空集合中的元素

        回想学过的获取长度或元素个数的:数组: arr.length
                                                                  字符串: str.length()
                                                                  StringBuilder: sb.length()

public class CollectionDemo01 {
    public static void main(String[] args) {
        Collection c=new ArrayList();
        c.add("张三");
        c.add("李四");
        c.add("王五");
        System.out.println(c);
        System.out.println(c.size());
        c.clear();//清空集合中的元素
        System.out.println(c);
    }
}

     由于Colection是接口,因此我们在这里实现它的子类,即父接口指向子类对象,也就是多态的思想。在存储元素的时候,集合中,我们一般一个集合只存储一种类型的元素,是为了防止后期处理数据混乱。

        运行结果:

集合的判断功能:
        boolean contains(Object o):判断集合中是否包含指定元素,如果包含就返回true,否则就返回false
        原理:
                底层会拿着要判定的元素与集合中的元素利用equals()方法一一对比
                如果在对比的过程中equals()方法返回true => 代表该集合包含要判定的元素 =>contains方法返回true,如果与集合中所有元素利用equals()对比均返回false=>代表该集合不包含要判定的元素=>contains方法返回false。首先简单看一下contains方法的使用:

public class CollectionDemo02 {
    public static void main(String[] args) {
        Collection c=new ArrayList();
        c.add(345);
        c.add(123);
        c.add(408);
        System.out.println(c.contains(345));//true  依然遵循contains方法的原理
        System.out.println(c.contains(321));//false
    }
}

        那么为了验证底层用的是equals方法进行比较的,首先我们建一个Person类:

public class Person{
    private String name;
    private int age;
    
    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }
    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}

        其次再使用contains方法,比较对象在集合中的存储:

public class CollectionDemo02 {
    public static void main(String[] args) {
        Person p1 = new Person("张三",19);
        Person p2 = new Person("李四",18);
        Collection c=new ArrayList();
        c.add(p1);
        c.add(p2);
        Person p3 = new Person("张三",19);
        System.out.println(c);//输出集合元素
        System.out.println(c.contains(p1));//true
        System.out.println(c.contains(p3));//false
    }
}

        本身输出集合元素会调用toString方法,输出对象的对象也会调用toString方法,在不重写的情况下,默认会打印十六进制哈希值,这里按照重写后的方式输出。而p1和p2都在集合中,p3的存储内容和p1的一样,但并没有加入集合,运行结果:

        如果contains方法仅仅比较存储的内容,那第二行应该返回true,因为存储的内容一样。其实默认的equals方法比较的就是地址值,显然p1和p3两对象的地址值不一样,那么现在重写以一下equals,再看看运行结果:

 //将该代码重写在Person类中
    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;

        Person person = (Person) o;

        if (age != person.age) return false;
        return name.equals(person.name);
    }

再看运行结果:


        显然,p1和p3比较后,显示集合中包含p3元素并返回true,比较的就是内容,而不再是地址值。

        boolean isEmpty():判断集合中是否有元素,如果有元素,那么就返回false,否则就返回true
                原理:利用size()判断集合是否为空
                        如果size()==0,说明集合中无元素,isEmpty()返回true
                        如果size()!=0,说明集合中有元素,isEmpty()返回false

public class CollectionDemo02 {
    public static void main(String[] args) {
        Person p1 = new Person();
        Person p2 = new Person();
        Collection c=new ArrayList();
        c.add(p1);
        c.add(p2);
        System.out.println(c.isEmpty());
        c.clear();//清空集合元素
        System.out.println(c.isEmpty());
    }
}

        删除功能:

集合中的移除功能:
        boolean remove(Object o)
                如果移除成功返回true,否则返回false
        原理:
        1.先查找:将要删除的元素与集合中的元素利用equals方法一一对比,一旦equals方法返回true,代表找到该元素,如果与集合中的元素利用equals方法一一对比均返回false,找不到该元素
        2.如果没找到,remove返回false,如果找到,再去删除集合已有元素

public class CollectionDemo03 {
    public static void main(String[] args) {
        Collection c = new ArrayList();
        Person p1 = new Person("张三", 19);
        Person p2 = new Person("李四", 18);
        c.add(p1);
        c.add(p2);
        Person p3 = new Person("张三", 19);
        System.out.println(c.remove(p3));
        System.out.println(c);
    }
}

运行结果:

        上面我们已经重写了equals方法和toString方法,让它来比较内容,所以这里判断的p3存储的信息就是p1,既然匹配到了就直接remove进行移除,再次输出集合元素,就只剩下p2的信息了。

2.迭代器

        java中提供了多种集合,它们在存储元素时,采用的存储方式不同。我们要取出这些集合中的元素,可通过一种通用的获取方式来完成。Collection集合元素的通用获取方式:在取元素之前先要判断集合中有没有元素,如果有,就把这个元素取出来,继续在判断,如果还有就再取出来。一直把集合中的所有元素全部取出。这种取出方式专业术语称为**迭代**。

        先看看API:

        发现Collection接口继承了Iterable<E>,说明Collection实现的集合是可迭代的,其他如有继承也是可以进行迭代。进入Iterable<E>里面可以看到:

2.1.迭代器使用

        集合通用迭代方式:
        public interface Iterable  {
                Iterator  iterator(); //返回一个迭代器

        }

        public interface Iterator  { // 迭代器的设计中含有三个方法
                boolean hasNext(); //判断容器中元素是否存在
                Object  next();//取出下一个元素
                void remove();//移除当前元素
        }  

        下面先使用迭代器中的方法:

public class CollectionDemo04 {
    public static void main(String[] args) {
        Collection c = new ArrayList();
        c.add("张三");
        c.add("李四");
        //1.先获取一个通用的迭代器对象
        Iterator iterator = c.iterator();
        //2.利用迭代器中方法
        System.out.println(iterator.hasNext());//判断容器中是否存在下一个元素,有就返回true,否则返回false
        System.out.println(iterator.next());//取出下一个元素       //hasNext指针从容器中的第一个元素上方开始移动,所以这里的下一个元素就是第一个元素

        System.out.println(iterator.hasNext());//继续判断下一个元素,此时hasNext指针继续移动到第二个元素
        System.out.println(iterator.next());//取出下一个元素,也就是在第一个元素的基础之上,即第二个元素

        System.out.println(iterator.hasNext());//此时指针指向了第二个元素下方,即指向空,所以返回false
        System.out.println(iterator.next());//因此这里在取出元素的时候就出现没有下一个元素的异常
    }
}

        需要知道,hasNext( )判断方式:

        运行结果:

        判断到第二个元素,再进行判断就会输出false,此时已经没有下一个元素了,再输出就报错:java.util.NoSuchElementException,即没有元素异常。
 

2.2.迭代器遍历

        既然是遍历,就要输出集合中的所有元素,我们打印集合时输出的那不叫遍历,将元素逐个取出才叫遍历。
        这里用for循环和while循环来遍历。

        首先看while循环迭代:

public class CollectionDemo04 {
    public static void main(String[] args) {
        Collection c = new ArrayList();
        c.add("张三");
        c.add("李四");
        c.add("王五");
        Iterator iterator = c.iterator();
        while (iterator.hasNext()) {
            System.out.println(iterator.next());
        }
    }
}

结果:

        iterator.hasNext()会经判断后返回一个布尔值,如果为true就继续遍历输出。

        再看for循环迭代:

public class CollectionDemo04 {
    public static void main(String[] args) {
        Collection c = new ArrayList();
        c.add("张三");
        c.add("李四");
        c.add("王五");
        for(Iterator iterator = c.iterator(); iterator.hasNext();){
            System.out.println(iterator.next());
        }
    }
}

        Iterator iterator = c.iterator();这里获取一个迭代器对象,类似于我们int i=0;初始化变量。

iterator.hasNext()依旧在判断条件位置。输出和while的一样。

2.3.并发修改异常

        先看代码:

public class CollectionDemo05 {
    public static void main(String[] args) {
        Collection c = new ArrayList();
        c.add("abc");
        c.add("def");
        c.add("gdk");
        Iterator iterator = c.iterator();
        while (iterator.hasNext()){
            if (iterator.next().equals("def")){
                c.remove(iterator.next());
            }
        }
    }
}

        运行结果:

问题: java.util.ConcurrentModificationException(并发修改异常)
        由于我们在使用迭代器的方法遍历过程中,使用了集合的方法进行 删除/添加(导致集合结构的改变),从而导致并发修改异常。

解决方案:
        如果使用迭代器的方式进行遍历,在遍历过程中需要针对集合添加/删除元素,我们要选用迭代器的方法来完成,从而避免并发修改异常。
        如果使用集合的方式进行遍历,在遍历过程中需要针对集合添加/删除元素,我们要选用集合的方法来完成,从而避免并发修改异常。

        解决后:

        显然就时把c.remove(element);改成iterator.remove();既然用到了迭代器,那删除元素的方式就用迭代器的方式。错误就避免了。
 

2.4.增强for循环

        JDK1.5新特性:增强for,我们日后遍历数组或集合经常使用增强for。

JDK 5新特性:
         增强for循环(for-each):
                适用场景:
                        简化集合和数组的遍历
                        要求集合必须继承或实现 Iterable 接口
        格式:
           for(要遍历的容器中的元素类型 变量名 : 数组/集合){
                 //取出元素
           }
        注意事项:
        1.增强for在遍历的时候无法操作数组的索引
        2.增强for针对集合来说,底层依然使用的是迭代器
        3.注意增强for上元素的类型

看完基本使用,就容易理解了:

public class CollectionDemo06 {
    public static void main(String[] args) {
        //遍历数组
        int index = 0;//索引
        int[] arr = {12, 45, 2, 66};
        for (int element : arr) {
            System.out.print(element+" ");
            index++;
        }
        System.out.println();
        //遍历集合
        Collection c = new ArrayList();
        c.add("张三");
        c.add("李四");
        c.add("王五");
        for (Object element : c) {
            System.out.print(element+" ");
        }
    }
}

        先是用增强for遍历一个数组,由于没索引,所以可以自己加一个index来记录索引,方便后期使用,这里我只解释不再使用。再是使用增强for遍历集合,也是一样。需要注意的是,增强for针对集合来说,底层依然使用的是迭代器,所以要避免并发修改错误,在进行使用增强for移除集合中的元素的时候记得使用迭代器,而不是使用集合的方式直接remove元素。

运行结果:

3.泛型


        JDK 5新特性泛型:广泛的类型
                根据泛型的定义位置,分为三种:
        1.类上的泛型 public class ArrayList<E>
        2.方法上的泛型 <T> T[] toArray(T[] a)
        3.接口上的泛型 public interface Collection<E>

3.1.类上的泛型

          定义在类上泛型
格式:
        class 类名<E,Q,A,...>{//<E,Q,A,...> 泛型变量
           //在类中就可以使用泛型变量
         } 

直接看演示:

public class GenericDemo02<E> {
    public void method01(E e){
        System.out.println(e);
    }
    public static void method02(){

    }
    public static void main(String[] args) {
        GenericDemo02<String> gd01=new GenericDemo02<String>();
        //GenericDemo02<String> gd01 = new GenericDemo02<>();等效于上一行
        gd01.method01("李四");
        GenericDemo02.method02();
        GenericDemo02<Integer> gd02 = new GenericDemo02<>();
        gd02.method01(123);
    }
}

        在声明对象的时候通过<>指定具体类型,例如<String>那么这个String相当于替代掉类上的E,类上的泛型一旦确定为String,类中E的地方,也会被替换成String。那么如果在new对象时用Integer类型,则方法中使用的E也会默认转化为Integer类型。

        总之就是说在创建对象的时候指定泛型变量的类型,该泛型变量就被替换成该类型。

总结:

        1.类上泛型可以在类中直接使用
        2.类上的泛型通过创建该类的对象指定具体类型
        3.如果创建对象时候不指定具体类型,泛型默认会被替换成Object
        4.静态方法不能使用类上泛型,因为静态方法可以通过类名直接调用,无法确定泛型的具体类型

注:集合类上使用泛型

public class GenericDemo03 {
    public static void main(String[] args) {
        Collection<String> c=new ArrayList<>();
        c.add("123");
        c.add("qwe");
        c.add("qhk");
        System.out.println(c.size());
        Collection c2=new ArrayList<String>();  //虽然没有报错,但是无法确定泛型的具体类型,写法错误
    }
}

        集合中一旦确定了元素类型,就只能添加相应类型的元素,否则就报错。

3.2.方法上的泛型


        格式:
             权限修饰符 <T,Q,E,...> 返回值类型 方法名(T t,Q q,...){     

                                                      //<T,Q,E,...> 方法上的泛型声明
                                                      //可以在方法的形参以及方法内使用
            }

public class GenericDemo04 {
    public <T> void method(T t){
        System.out.println(t);
    }
    public static void main(String[] args) {
        GenericDemo04 gd = new GenericDemo04();
        gd.method("String");//String
        gd.method(123);//123
    }
}

        对于gd.method("String");当我们传递一个字符串的时候,此时形参的T被替换为String,替换为:method(String t)。当gd.method(123);我们传递一个整数值的时候,此时形参的T被替换为int对应的包装类,此时替换为:method(Integer t)。

        所以说,方法上的泛型,是当我们为方法传参的时候确定下来的。根据传入参数的类型,方法上的泛型也会跟着变成对应的包装类型。

集合中使用泛型的方法:toArray()

        只要给泛型方法传入什么类型,泛型变量就会被替换为什么类型。

Collection接口中toArray方法:
    public abstract <T> T[] toArray(T[] a):返回一个包含此集合中所有元素的数组(将集合转成数组)

public class GenericDemo05 {
    public static void main(String[] args) {
        ArrayList<String> a = new ArrayList<>();
        a.add("cbv");
        a.add("uiv");
        a.add("cnk");
        String[] strings=new String[a.size()];
        String[] str=a.toArray(strings);
        for (String s : str) {
            System.out.print(s+"  ");//cbv  uiv  cnk  
        }
    }
}

        new一个能存储集合中元素的数组,再用集合对象调用该方法,传入所要返回的数组,下面再用一个for循环输出数组元素。这就是toArray方法基本使用。

3.3.接口上的泛型

        第一种格式:
                接口上的泛型格式:
                 interface 接口名<T,E,Q...>{     //接口上声明的泛型变量,都可以在接口中使用
                }

        首先创建一个父接口Father。

public interface Father<T>{
    void method(T t);
}

        再创建Son类来继承父接口,此时Father后面可以传入指定类型,如传入String类型,则父接口中的T也会自动转化为String类型(包括方法)。也就是:当我们在定义类的时候实现父接口,为父接口传入类型,此时接口上的泛型变量会被替换为该类型,同时在接口中用到该泛型变量的位置都会被替换成该类型。

public class Son implements Father<String> {
    @Override
    public void method(String s) {
        System.out.println(s);
    }
}

        最后创建测试类,使用son调用method方法时,也只能为method方法传入String类型,也就是字符串,传入其他类型均会报错:

public class Demo {
    public static void main(String[] args) {
        Son son = new Son();
        son.method("abc");
    }
}

        第二种格式
                接口上的泛型:
                interface 接口名<T,E,Q,....>{     //接口上声明泛型变量(参数),可以在接口中使用
                 }

                仍使用上面的父接口,再次初始化创建Son类继承该父接口,即:

public class Son<Q> implements Father<Q> {//Q会替换接口上的T,接口中用的T的位置也会被替换成Q
    @Override
    public void method(Q q) {
        System.out.println(q);
    }
}

        当类上定义泛型以及实现的接口也定义泛型,此时泛型被确定的时机: 创建该类对象的时候

也就是:

public class Demo {
    public static void main(String[] args) {
        Son<Integer> son = new Son<>();//定义Son类对象的时候类上的泛型被确定(Integer)
                                      //进而接口上的泛型也确定(Integer)
        son.method(123);
    }
}

        这里Son的对象实现时确定了类上的泛型为Integer,此时,Son类中以及父接口中的泛型都会变成Integer,用son调用method方法时,会提示传入一个整型参数。所以我们说:当类上定义泛型以及实现的接口也定义泛型,此时泛型被确定的时机: 创建该类对象的时候。

3.4.增强for使用泛型
 

        增强for上使用泛型
                for(要遍历的容器中的元素类型 变量名 : 数组/集合){
                     //取出元素
                }

public class Demo {
    public static void main(String[] args) {
        Collection c=new ArrayList();//创建对象的时候没指明实际类型  那么类中的泛型会被替换成Object
        c.add(123);     //Object e=new Integer(123);
        c.add("abc");   //Object e="abc";
        c.add("def");   //Object e="def";
        for (Object o : c) {
            System.out.print(o+" ");//123 abc def
        }
//------------------------------------------------------------------------------------------
        Collection<String> c02=new ArrayList<>();//创建对象的时候指定为String类型  那么类中的泛型会被替换成String
        c02.add("abc");
        c02.add("bf");
        c02.add("as");
        for (String s : c02) {
            System.out.print(s+" ");//abc bf as
        }
    }
}

        第一种c创建对象的时候不指定集合的类型,默认为Object类,那再使用增强for循环的时候,也需要使用Object来遍历。而使用泛型后,第二种c02,在创建对象的时候指定为String类型,那再使用增强fors循环就要使用相应的String类型。

3.5.使用泛型的优点
 

        JDK 5泛型的优点:
                1.使用泛型可以将运行时期可能出现问题(例如:类型转换异常)提升到编译时期(报错时机提前,将错误解决在萌芽阶段)
                2.一定程度上避免强制类型转换,可以直接使用集合中元素类型的特有方法

      使用泛型后,添加指定String类型后,再往集合中添加元素时,只能添加字符串,如果不是字符串类型就会直接编译报错,显然此时c.add(123)这行直接标红报错,可以规范集合中的元素,哪怕在不经意间输错也能及时发现。这也就是泛型的优点之一。

public class Demo02 {
    public static void main(String[] args) {
        Collection<String> c=new ArrayList();
        c.add("abc");
        c.add("def");
        //c.add(123);  // ClassCastException  直接编译报错
        c.add("vbn");
        for (String s : c) {
            System.out.println(s.charAt(0));//获取每个字符串元素的索引为0的字符
        }
    }
}

标签:String,迭代,元素,Collection,集合,add,泛型,public
From: https://blog.csdn.net/2301_77206753/article/details/142532542

相关文章

  • 测试驱动项目设计需求迭代
      测试工作在Java工程项目中的作用不可或缺。测试驱动和模型驱动以及迭代开发。项目的测试工作分为黑盒测试和白盒测试。黑盒测试并不会让你知道很多让你不应该知道的细节。白盒测试透明,项目组的开发人员也是不能触碰。程序设计的编写开发人员主要工作是编写项目的源代码,完成......
  • c++泛型编程
    一、模板template1.1概念C++重模板可以让类或函数声明一种通用类型,使得函数或类中的某些成员变量或成员变量的参数、返回值在实际上的使用中可以是任何类型。模板可以让程序员写出与类型无关的代码,是泛型编程的基础。模板主要分为两种实现方式:函数模板类模板1.2函数......
  • opencascade TopoDS_Iterator源码学习拓扑迭代器
    opencascadeTopoDS_Iterator前言遍历给定TopoDS_Shape对象的底层形状,提供对其组件子形状的访问。每个组件形状作为带有方向的TopoDS_Shape返回,并且由原始值和相对值组成的复合体。方法1//!创建一个空的迭代器。TopoDS_Iterator();2//!子形状上创建一个迭代器。如......
  • 线性方程组的迭代方法
    目录直接方法与迭代方法常规迭代算法选择迭代求解器预条件子预条件子示例均衡和重新排序使用线性运算函数取代矩阵        数值线性代数最重要也是最常见的应用之一是可求解以A*x=b形式表示的线性方程组。当A为大型稀疏矩阵时,您可以使用迭代方法求解线......
  • 章15——泛型generic
    泛型的引入泛型引入前后代码的比较publicstaticvoidmain(String[]args){ArrayListarrayList=newArrayList();arrayList.add(newDog("wang",10));arrayList.add(newDog("xin",1));arrayList.add(newDog("ran&quo......
  • Python 迭代器双指针
    我们知道在cpp这种指针语言里面,双指针是这么写的:for(autoi=v.begin(),j=v.begin();j<v,end();j++){//dosomething...//updatepointeriwhile(cond){i++;}}对于py这样不带指针的,一般就只能这么写:i=0forjinrange(len(lst)):#do_something......
  • 设计模式之迭代器模式
    迭代器模式迭代器模式(IteratorPattern)是一种行为设计模式,它提供了一种顺序访问集合对象中各个元素的方法,而不需要暴露该对象的内部表示。迭代器模式主要用来遍历集合,如列表、树、图等数据结构。目的迭代器模式的主要目的是将集合对象的遍历行为从集合对象中分离出来,使用一个独......
  • maven 使用SNAPSHOT版本确实可以帮助开发团队更高效地迭代和测试新功能
    使用SNAPSHOT版本确实可以帮助开发团队更高效地迭代和测试新功能。下面是一个更详细的解释:快速迭代频繁构建和部署:由于SNAPSHOT版本通常与持续集成(CI)工具结合使用,因此每次提交代码后都可以触发构建和部署流程。这意味着每次有新的代码更改时,都会有一个新的SNAPSHOT版本产......
  • XX产品XX版本迭代测试报告(简版)
    一、总体目标XX产品XX版本,除了新增的需求条目以外,还整合了各个项目提交的产品bug以及部分需求。对XX的整体业务流程进行了整体的回归验证,同时对对接的外系统如XXXX等也进行了回归验证。具体如下:一级领域产品三级目标关键特性1XX领域XX产品描述每次迭代的关......
  • 【JAVA-数据结构】包装类&简单认识泛型(1)
        这篇包含包装类和泛型相关知识,会用两篇文章进行讲解。1包装类        在Java中,由于基本类型不是继承自Object,为了在泛型代码中可以支持基本类型,Java给每个基本类型都对应了一个包装类型。1.1基本数据类型和对应的包装类除了Integer和Character......