Java集合之Collection、Iterator以及泛型的学习总结

1、集合概述

1.1、什么是集合

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

问题:集合和数组既然都是容器,它们有啥区别呢?

  • 数组的长度是固定的。集合的长度是可变的。
  • 数组中存储的是同一类型的元素,可以存储基本数据类型值。集合存储的都是对象。而且对象的类型可以不一致。在开发中一般当对象多的时候,使用集合进行存储。

1.2、集合框架

JAVASE提供了满足各种需求的API,在使用这些API前,先了解其继承与接口操作架构,才能了解何时采用哪个类,以及类之间如何彼此合作,从而达到灵活应用。

集合本身是一个工具,它存放在java.util包中。
集合按照其存储结构可以分为两大类,分别是单列集合java.util.Collection和双列集合java.util.Map

2、Collection集合

2.1、基本介绍

在这里插入图片描述

  • Collection:单列集合类的根接口,用于存储一系列符合某种规则的元素。它有两个重要的子接口,分别是java.util.Listjava.util.Set。Collection接口中定义的是所有单列集合中共性的方法,即所有单列集合都可以使用的方法。该接口没有带索引的方法。
  • List:该集合特点是元素有序(存储和取出的顺序相同)、元素可重复有索引(可以使用普通的for循环遍历)。List接口的主要实现类有java.util.Vectorjava.util.ArrayListjava.util.LinkedList
  • Set:该集合特点是元素无序(相对而言,TreeSet和HashSet无序,LinkedHashSet有序)、元素不可重复没有索引(不可以使用普通的for循环遍历)。Set接口的主要实现类有java.util.HashSetjava.util.TreeSet

2.2、常用方法

Collection是所有单列集合的父接口,因此在Collection中定义了单列集合(List和Set)通用的一些方法,这些方法可用于操作所有的单列集合。方法如下:

方法描述
public boolean add(E e)把给定的对象添加到当前集合中
public void clear()清空集合中所有的元素,不删除集合
public boolean remove(E e)把给定的对象在当前集合中删除
public boolean contains(E e)判断当前集合中是否包含给定的对象
public boolean isEmpty()判断当前集合是否为空
public int size()返回集合中元素的个数
public Object[] toArray()把集合中的元素,存储到数组中

方法演示:

import java.util.ArrayList;
import java.util.Collection;

public class CollectionDemo {
    public static void main(String[] args) {
        // 使用多态创建集合对象
        Collection<String> coll = new ArrayList<>();
        System.out.println(coll); //结果为[],重写了toString方法

        /* add:把给定的对象添加到当前集合中,返回值为boolean值
         */
        boolean b1 = coll.add("Atlantis");
        coll.add("Olivia");
        coll.add("长安");
        System.out.println("b1:" + b1); //结果为b1:true
        System.out.println(coll); //结果为[Atlantis, Olivia, 长安]

        /* size:返回集合中元素的个数。
         */
        int size = coll.size();
        System.out.println("size:" + size); //结果为size:3

        /* toArray:把集合中的元素,存储到数组中。
         */
        Object[] array = coll.toArray();
        for (int i = 0; i < array.length; i++) {
            System.out.print(array[i] + " "); //结果为Atlantis Olivia 长安
        }

        /* remove:把给定的对象在当前集合中删除,返回值为boolean值
             集合中存在元素,删除元素,返回true
             集合中不存在元素,删除失败,返回false
         */
        boolean b2 = coll.remove("长安");
        System.out.println("b2:" + b2); //结果为b2:true
        boolean b3 = coll.remove("Andersen");
        System.out.println("b3:" + b3); //结果为b3:false
        System.out.println(coll); //结果为[Atlantis, Olivia]

        /* contains:判断当前集合中是否包含给定的对象。
             包含返回true,否则为false
         */
        boolean b4 = coll.contains("Olivia");
        System.out.println("b4:" + b4); //结果为b4:true
        boolean b5 = coll.contains("Andersen");
        System.out.println("b5:" + b5); //结果为b5:false

        /* isEmpty:判断当前集合是否为空。
             集合为空返回true,集合不为空返回false
         */
        boolean b6 = coll.isEmpty();
        System.out.println("b6:" + b6); //结果为b6:false

        /* clear:清空集合中所有的元素,不删除集合,无返回值。
         */
        coll.clear();
        System.out.println(coll); //结果为[]
        System.out.println(coll.isEmpty()); //结果为true
    }
}

Tips: 有关Collection中的方法可不止上面这些,其他方法可以自行查看API学习。

3、Iterator迭代器

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

3.1、Iterator接口

在程序开发中,经常需要遍历集合中的所有元素。针对这种需求,JDK专门提供了一个接口java.util.IteratorIterator接口也是Java集合中的一员,但它与CollectionMap接口有所不同,Collection接口与Map接口主要用于存储元素,而Iterator主要用于迭代访问(即遍历)Collection中的元素,因此Iterator对象也被称为迭代器。

想要遍历Collection集合,那么就要获取该集合迭代器完成迭代操作,获取迭代器的方法:
   public Iterator iterator(): 获取集合对应的迭代器,用来遍历集合中的元素的。

Iterator接口的常用方法如下:
   public E next():返回迭代的下一个元素。
   public boolean hasNext():如果仍有元素可以迭代,则返回 true。

3.2、使用方法

因为Iterator迭代器是一个接口,我们无法直接使用,需要使用Iterator接口的实现类对象。而Collection接口中有一个方法为iterator(),这个方法返回的就是迭代器的实现对象。

迭代器的使用步骤:
  (1)使用集合中的方法iterator()获取迭代器的实现类对象,使用Iterator接口接收(多态)。
  (2)使用Iterator接口中的方法hasNext()判断是否存在迭代对象。
  (3)使用Iterator接口中的方法next()获取集合中的下一个元素。

接下来我们学习如何使用Iterator迭代集合中元素:

import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;

public class IteratorDemo {
    public static void main(String[] args) {
        // 使用多态创建集合对象
        Collection<String> coll = new ArrayList<>();
        // 添加元素到集合
        coll.add("Atlantis");
        coll.add("Olivia");
        coll.add("长安");
        // 使用迭代器进行遍历
        // coll.iterator():每个集合对象都有自己的迭代器
        // Iterator<E>:接口是有泛型的,迭代器的泛型与集合泛型一致。
        Iterator<String> iterator = coll.iterator();
        while (iterator.hasNext()) { //判断是否存在  迭代对象
            String str = iterator.next(); //获取迭代出的元素
            System.out.print(str + " "); //结果为Atlantis Olivia 长安
        }
    }
}

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

3.3、实现原理

  我们在上一小节已经完成了Iterator遍历集合的整个过程。当遍历集合时,首先通过调用t集合的iterator()方法获得迭代器对象,然后使用hashNext()方法判断集合中是否存在下一个元素,如果存在,则调用next()方法将元素取出,否则说明已到达了集合末尾,停止遍历元素。

  Iterator迭代器对象在遍历集合时,内部采用指针的方式来跟踪集合中的元素,为了让初学者能更好地理解迭代器的工作原理,接下来通过一个图例来演示Iterator对象迭代元素的过程:
在这里插入图片描述
  在调用Iteratornext()方法之前,迭代器的索引位于第一个元素之前,不指向任何元素,当第一次调用迭代器的next()方法后,迭代器的索引会向后移动一位,指向第一个元素并将该元素返回,当再次调用next()方法时,迭代器的索引会指向第二个元素并将该元素返回,依此类推,直到hashNext()方法返回false,表示到达了集合的末尾,终止对元素的遍历。

3.4、增强for循环

  增强for循环(也称for each循环)是JDK1.5以后出来的一个高级for循环,专门用来遍历数组和集合的。它的内部原理其实是个Iterator迭代器,使用for循环的格式简化了迭代器的书写。增强for循环用于遍历Collection和数组,通常只进行遍历元素,不要在遍历的过程中对集合元素进行增删操作。
  所有的单列集合都可以使用增强for循环来遍历数据。

增强for循环格式:

for(元素的数据类型  变量 : Collection集合or数组){ 
  	//写操作代码
}

使用增强for循环来遍历数组和集合:

        /* 遍历数组 */
        int[] arr = {3, 5, 6, 87};
        // 使用增强for循环数组
        for (int a : arr) {//a代表数组中的每个元素
            System.out.println(a);
        }

        /* 遍历集合 */
        Collection<String> coll = new ArrayList<>();
        coll.add("Atlantis");
        coll.add("Olivia");
        coll.add("长安");
        // 使用增强for循环遍历
        for (String str : coll) {//接收变量str代表被遍历到的集合元素
            System.out.println(str);
        }

Tips: 增强for循环必须有被遍历的目标。目标只能是Collection或者是数组。增强for循环仅仅作为遍历操作出现。

4、泛型

4.1、泛型的概述

  在前面学习集合时,我们都知道集合中是可以存放任意对象的,只要把对象存储集合后,那么这时他们都会被提升成Object类型。当我们在取出每一个对象,并且进行相应的操作,这时必须采用类型转换。
  而泛型是一种未知的数据类型,当我们不知道使用什么数据类型的时候,就可以使用泛型。当然,泛型也可以看做是一个遍历,用来接收数据类型。
在这里插入图片描述

Tips: 一般在创建对象时,将未知的类型确定具体的类型。当没有指定泛型时,默认类型为Object类型。

4.2、泛型有哪些好处?

  介绍使用泛型的好处前,我们先来试一下不使用泛型会怎么样。观察下面代码,程序在运行时发生了异常java.lang.ClassCastException

public class GenericDemo {
	public static void main(String[] args) {
		Collection coll = new ArrayList();
		coll.add("Atlantis");
		coll.add("Olivia");
		coll.add(23); // 由于集合没有做任何限定,任何类型都可以给其中存放
		Iterator it = coll.iterator();
		while(it.hasNext()){
			// 需要打印每个字符串的长度,就要把迭代出来的对象转成String类型
			String str = (String) it.next();
			System.out.println(str.length());
		}
	}
}

  我们把上面的代码使用泛型进行更改,代码如下:

public class GenericDemo {
	public static void main(String[] args) {
        Collection<String> coll = new ArrayList<String>();
		coll.add("Atlantis");
		coll.add("Olivia");
        // coll.add(23); // 当集合明确类型后,存放类型不一致就会编译报错
        // 集合已经明确具体存放的元素类型,那么在使用迭代器的时候,迭代器也同样会知道具体遍历元素类型
        Iterator<String> it = coll.iterator();
        while(it.hasNext()){
            String str = it.next();
            //当使用Iterator<String>控制元素类型后,就不需要强转了。获取到的元素直接就是String类型
            System.out.println(str.length());
        }
	}
}

  我们可以发现,当使用泛型后,将原本运行时出现的异常ClassCastException,转移到了编译时的错误,这样更容易我们发现问题,同时也避免了类型强制的麻烦。

Tips: 泛型是数据类型的一部分,我们将类名与泛型合并一起看做数据类型。

4.3、泛型的定义和使用

  泛型,用来灵活地将数据类型应用到不同的类、方法、接口当中。将数据类型作为参数进行传递。

4.3.1、含有泛型的类

定义格式:

修饰符 class 类名<代表泛型的变量> {  }

例如,API中的ArrayList集合:

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

在创建对象的时候确定泛型
例如,ArrayList<String> list = new ArrayList<String>();。此时,变量E的值就是String类型,那么我们的类型就可以理解为:

class ArrayList<String>{ 
     public boolean add(String e){ }

     public String get(int index){  }
     ...
}

再例如,ArrayList<Integer> list = new ArrayList<Integer>();。此时,变量E的值就是Integer类型,那么我们的类型就可以理解为:

class ArrayList<Integer> { 
     public boolean add(Integer e) { }

     public Integer get(int index) {  }
     ...
}

举例自定义泛型类

public class MyGenericClass<MVP> {
	//没有MVP类型,在这里代表 未知的一种数据类型 未来传递什么就是什么类型
	private MVP mvp;
     
    public void setMVP(MVP mvp) {
        this.mvp = mvp;
    }
     
    public MVP getMVP() {
        return mvp;
    }
}

/* 使用自定义的泛型类 */ 
public class GenericClassDemo {
  	public static void main(String[] args) {		 
         // 创建一个泛型为String的类
         MyGenericClass<String> my = new MyGenericClass<String>();    	
         // 调用setMVP
         my.setMVP("Atlantis");
         // 调用getMVP
         String mvp = my.getMVP();
         System.out.println(mvp);
         //创建一个泛型为Integer的类
         MyGenericClass<Integer> my2 = new MyGenericClass<Integer>(); 
         my2.setMVP(123);   	  
         Integer mvp2 = my2.getMVP();
    }
}

4.3.2、含有泛型的方法

定义格式:

修饰符 <代表泛型的变量> 返回值类型 方法名(参数){  }
public class MyGenericMethod {	  
    public <MVP> void show(MVP mvp) {
    	System.out.println(mvp.getClass());
    }
    
    public <MVP> MVP show2(MVP mvp) {	
    	return mvp;
    }
}

使用格式:** 调用方法时,确定泛型的类型**

public class GenericMethodDemo {
    public static void main(String[] args) {
        // 创建对象
        MyGenericMethod mm = new MyGenericMethod();
        // 演示看方法提示
        mm.show("aaa");
        mm.show(123);
        mm.show(12.45);
    }
}

4.3.3、含有泛型的接口

定义格式:

修饰符 interface接口名<代表泛型的变量> {  }
public interface MyGenericInterface<E>{
	public abstract void add(E e);
	
	public abstract E getE();  
}

使用格式:
  1、定义类时确定泛型的类型。此时,泛型E的值就是String类型。

public class MyImp1 implements MyGenericInterface<String> {
	@Override
    public void add(String e) {
        // 省略...
    }

	@Override
	public String getE() {
		return null;
	}
}

   2、始终不确定泛型的类型,直到创建对象时,确定泛型的类型。

public class MyImp2<E> implements MyGenericInterface<E> {
	@Override
	public void add(E e) {
       	 // 省略...
	}

	@Override
	public E getE() {
		return null;
	}
}

/* 使用 */
public class GenericInterface {
    public static void main(String[] args) {
        MyImp2<String>  my = new MyImp2<String>();  
        my.add("aa");
    }
}

4.4、泛型通配符

  当使用泛型类或者接口时,传递的数据中,泛型类型不确定,可以通过通配符<?>表示。但是一旦使用泛型的通配符后,只能使用Object类中的共性方法,集合中元素自身方法无法使用。

4.4.1、基本使用

泛型的通配符:不知道使用什么类型来接收的时候,此时可以使用??表示未知通配符。此时只能接受数据,不能往该集合中存储数据。

举个栗子大家理解使用即可:

public static void main(String[] args) {
    Collection<Intger> list1 = new ArrayList<Integer>();
    getElement(list1);
    Collection<String> list2 = new ArrayList<String>();
    getElement(list2);
}
public static void getElement(Collection<?> coll){}
//?代表可以接收任意类型

Tips:泛型不存在继承关系 Collection<Object> list = new ArrayList<String>();这种是错误的。

4.4.2、受限泛型

  之前设置泛型的时候,实际上是可以任意设置的,只要是类就可以设置。但是在JAVA的泛型中可以指定一个泛型的上限下限

泛型的上限
  格式: 类型名称 <? extends 类 > 对象名称
  意义: 只能接收该类型及其子类

泛型的下限
  格式: 类型名称 <? super 类 > 对象名称
  意义: 只能接收该类型及其父类型

  比如:现已知Object类,String 类,Number类,Integer类,其中Number是Integer的父类。

public static void main(String[] args) {
    Collection<Integer> list1 = new ArrayList<Integer>();
    Collection<String> list2 = new ArrayList<String>();
    Collection<Number> list3 = new ArrayList<Number>();
    Collection<Object> list4 = new ArrayList<Object>();
    
    getElement(list1);
    getElement(list2);//报错
    getElement(list3);
    getElement(list4);//报错
  
    getElement2(list1);//报错
    getElement2(list2);//报错
    getElement2(list3);
    getElement2(list4);
}
// 泛型的上限:此时的泛型?,必须是Number类型或者Number类型的子类
public static void getElement1(Collection<? extends Number> coll){}
// 泛型的下限:此时的泛型?,必须是Number类型或者Number类型的父类
public static void getElement2(Collection<? super Number> coll){}

5、Collections

5.1、常用功能

java.utils.Collections是集合工具类,用来对集合进行操作。部分方法如下:

方法描述
public static <T> boolean addAll(Collection<T> c,T... elements)往集合中添加一些元素
public static void shuffle(List<?> list)打乱顺序 :打乱集合顺序
public static <T> void sort(List<T> list)将集合中元素按照默认规则排序
public static <T> void sort(List<T> list,Comparator<? super T>)将集合中元素按照指定规则排序

代码演示:

public class CollectionsDemo {
    public static void main(String[] args) {
        ArrayList<Integer> list = new ArrayList<Integer>();
        //原来写法
        //list.add(5);
        //list.add(20);
        //list.add(7);
        //list.add(16);
        //采用工具类 完成 往集合中添加元素 
        Collections.addAll(list, 5, 20, 716);
        System.out.println(list);
        //排序方法
        Collections.sort(list);
        System.out.println(list);
    }
}
结果:
[5, 20, 7, 16]
[5, 7, 16, 20]

  代码演示之后 ,发现我们的集合按照顺序进行了排列,可是这样的顺序是采用默认的顺序,如果想要指定顺序那该怎么办呢?public static <T> void sort(List<T> list,Comparator<? super T>);方法就可以将集合中元素按照指定规则排序。
  接下来讲解一下指定规则的排列。

5.2、Comparator比较器

  public static <T> void sort(List<T> list,Comparator<? super T>);方法是将集合中元素按照指定规则排序,不过这次存储的字符串类型。

public class CollectionsDemo {
    public static void main(String[] args) {
        ArrayList<String>  list = new ArrayList<String>();
        list.add("cba");
        list.add("aba");
        list.add("sba");
        list.add("nba");
        //排序方法
        Collections.sort(list);
        System.out.println(list);
    }
}
结果:
[aba, cba, nba, sba]

  我们使用的是默认的规则完成字符串的排序,那么默认规则是怎么定义出来的呢?

  说到排序了,简单的说就是两个对象之间比较大小,那么在JAVA中提供了两种比较实现的方式,一种是比较死板的,采用java.lang.Comparable接口去实现;一种是灵活的,当需要做排序的时候在去选择的java.util.Comparator 接口完成。

  那么我们采用的public static <T> void sort(List<T> list)这个方法完成的排序,实际上要求了被排序的类型。需要实现Comparable接口完成比较的功能,在String类型上如下:

public final class String implements java.io.Serializable, Comparable<String>, CharSequence {
}

  String类实现了这个接口,并完成了比较规则的定义,但是这样就把这种规则写死了,那比如我想要字符串按照第一个字符降序排列,那么这样就要修改String的源代码,这是不可能的了,那么这个时候我们可以使用public static <T> void sort(List<T> list,Comparator<? super T>) 方法灵活的完成,这个里面就涉及到了Comparator这个接口,位于java.util包下。

  排序是comparator能实现的功能之一,该接口代表一个比较器,比较器具有可比性!顾名思义就是做排序的,通俗地讲需要比较两个对象谁排在前谁排在后,那么比较的方法就是:

  • public int compare(String o1, String o2) :比较其两个参数的顺序。
    两个对象比较的结果有三种:大于,等于,小于。
    如果按照升序排序,则o1 小于o2,返回(负数),相等返回0,o1大于o2返回(正数)
    如果按照降序排序,则o1 小于o2,返回(正数),相等返回0,o1大于o2返回(负数)

代码演示如下:

public class CollectionsDemo {
    public static void main(String[] args) {
        ArrayList<String> list = new ArrayList<String>();
        list.add("cba");
        list.add("aba");
        list.add("sba");
        list.add("nba");
        //排序方法  按照第一个单词的降序
        Collections.sort(list, new Comparator<String>() {
        	@Override
            public int compare(String o1, String o2) {
                return o2.charAt(0) ‐ o1.charAt(0);
            }
        });
        System.out.println(list);
    }
}
结果:
[sba, nba, cba, aba]

5.3、Comparable和Comparator的区别

  Comparable:强行对实现它的每个类的对象进行整体排序。这种排序被称为类的自然排序,类的compareTo()方法被称为它的自然比较方法。只能在类中实现compareTo()一次,不能经常修改类的代码实现自己想要的排序。实现此接口的对象列表(和数组)可以通过Collections.sort(和Arrays.sort)进行自动排序,对象可以用作有序映射中的键或有序集合中的元素,无需指定比较器。

  Comparator强行对某个对象进行整体排序。可以将Comparator 传递给sort方法(如Collections.sortArrays.sort),从而允许在排序顺序上实现精确控制。还可以使用Comparator来控制某些数据结构(如有序set或有序映射)的顺序,或者为那些没有自然顺序的对象collection提供排序。

5.4、练习案例

**需求:**创建一个学生类,存储到ArrayList集合中完成指定的排序操作。

  Student学生类代码:

public class Student{
    private String name;
    private int age;
    
    // 省略无参构造方法
    // 省略get、set和toString方法
}

  测试类:

public class Demo {
    public static void main(String[] args) {
        // 创建四个学生对象 存储到集合中
        ArrayList<Student> list = new ArrayList<Student>();
        list.add(new Student("rose",18));
        list.add(new Student("jack",16));
        list.add(new Student("abc",16));
        list.add(new Student("ace",17));
        list.add(new Student("mark",16));
        /* 学生按照年龄排序 - 升序 */
        // Collections.sort(list); // 要求该list中元素类型必须实现比较器Comparable接口
        for (Student student : list) {
            System.out.println(student);
        }
    }
}

  此时我们会发现,当调用Collections.sort()方法的时候程序报错了。原因很简单,如果想要集合中的元素完成排序,那么必须要实现比较器Comparable接口。于是我们修改了Student类的代码,如下:

public class Student implements Comparable<Student>{
    ....
    @Override
    public int compareTo(Student o) {
        return this.age‐o.age;//升序
    }
}
结果:
Student{name='jack', age=16}
Student{name='abc', age=16}
Student{name='mark', age=16}
Student{name='ace', age=17}
Student{name='rose', age=18


扩展一下,如果在使用的时候,想要独立的定义规则去使用可以采用Collections.sort(List list,Comparetor c)方式,自己定义规则:

Collections.sort(list, new Comparator<Student>() {
    @Override
    public int compare(Student o1, Student o2) {
        return o2.getAge()‐o1.getAge(); // 以学生的年龄降序
    }
});

结果:
Student{name='rose', age=18}
Student{name='ace', age=17}
Student{name='jack', age=16}
Student{name='abc', age=16}
Student{name='mark', age=16}

如果想要规则更多一些,可以参考下面代码:

		Collections.sort(list, new Comparator<Student>() {
            @Override
            public int compare(Student o1, Student o2) {
                // 年龄降序
                int result = o2.getAge()‐o1.getAge(); //年龄降序
                if(result==0){ //第一个规则判断结束,下一个规则姓名的首字母-升序
                    result = o1.getName().charAt(0)‐o2.getName().charAt(0);
                }
                return result;
            }
        });

结果:
Student{name='rose', age=18}
Student{name='ace', age=17}
Student{name='abc', age=16}
Student{name='jack', age=16}
Student{name='mark', age=16}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值