Java进阶之Collection&迭代器&泛型

一、Collection

1.1 Collection概述

之前我们已经了解学过ArrayList这个集合,ArrayList就是Collection的实现类。今天我们就来学习Collection这个类。

集合就是一个容器,这个容器可以存放多个数据;Collection是所有单列集合的根接口,所有的单列集合都会实现这个接口。
在这里插入图片描述

1.2 集合和数组中的区别

  • 集合的长度可变,而数组的长度是固定的
  • 集合只能存放引用类型的数据,而数组可以存放任意类型的数据

1.3 Collection常用方法

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

Collection是一个接口,如果要用,需要使用实现类,最常用的实现类是ArrayList;

Collection中没有索引有关的方法,因为不是所有的集合都有索引。

public static void main(String[] args) {
        //创建集合
        Collection<String> c = new ArrayList<>();
        //public boolean add(E e):向集合中添加元素
        c.add("张三");
        c.add("李四");
        c.add("王五");
        //输出集合
        System.out.println("操作前:" + c);//[张三, 李四, 王五]
        //public boolean contains(Object obj):判断集合中是否包含指定的元素
        System.out.println("是否包含:" + c.contains("haha"));
        //public boolean isEmpty():判断集合是否为空,如果集合中没有任何元素,那么这个集合就是一个空集合
        //c.clear();
        System.out.println("是否为空:" + c.isEmpty());
        //public int size():获取集合的大小,集合中有几个元素,那么集合的大小就是几
        System.out.println("集合的大小:" + c.size());
        //public Object[] toArray():将集合转成一个数组并返回。
        Object[] strArr = c.toArray();
        //遍历数组
        for (int i = 0; i < strArr.length; i++) {
            System.out.println(strArr[i]);
        }

        //public boolean remove(E e):直接删除指定的元素
        boolean flag = c.remove("李四");
        System.out.println("操作后:" + c);//[张三, 王叔叔]
        System.out.println("flag:" + flag);//true


        //public void clear() :清空集合中所有的元素。
        c.clear();
        System.out.println("操作后:" + c);

    }

1.4 List接口

List是Collection下面的一个子接口,有三个特点:

  • 有序 (按照什么顺序存,就按照什么顺序取)
  • 有索引 (可以根据索引获取元素)
  • 可以重复 (可以存放重复元素)

List是一个接口,如果要用,需要使用实现类。

1.4.1 常用方法

  • public void add(int index, E element) 将指定的元素,添加到该集合中的指定位置上
  • public E get(int index) 返回集合中指定位置的元素
  • public E remove(int index) 移除列表中指定位置的元素, 返回的是被移除的元素
  • public E set(int index, E element) 用指定元素替换集合中指定位置的元素,返回被替换的元素

1.4.2 LinkedList简介以及特有方法

LinkedList是List接口下面的实现类,内部使用双向链表保存数据

LinkedList特有方法:

  • public void addFirst(E e) 将指定元素插入此列表的开头。
  • public void addLast(E e) 将指定元素添加到此列表的结尾。
  • public E getFirst() 返回此列表的第一个元素。
  • public E getLast() 返回此列表的最后一个元素。
  • public E removeFirst() 移除并返回此列表的第一个元素。
  • public E removeLast() 移除并返回此列表的最后一个元素。
  • public E pop() 从此列表所表示的堆栈处弹出一个元素。
  • public void push(E e) 将元素推入此列表所表示的堆栈

public E pop()调用的public E removeFirst()方法;public void push(E e)调用的是 public void addFirst(E e)方法,这两个方法是为了模拟入栈和出栈的操作,定义这两个方法的目的是为了达到见名知意的效果。

1.4.3 LinkedList和ArrayList的区别

  • ArrayList的内部使用数组保存数据,LinkedList内部使用双向链表保存数据
  • ArrayList是List接口下面最常用的实现类,LinkedList不常用
  • LinkedList特点是查询慢,增删快;ArrayList底层用数组实现,数组特点是查询快,增删快(但ArrayList经过多次优化后,增删不一定比LinkedList慢)

二、Iterator迭代器

我们之前都是使用for循环结合索引的方式遍历集合,但是这种遍历方式并不适用于所有的集合,因为不是所有的集合都有索引。

有一种通用的遍历集合的方式,可以遍历任何的集合,这个遍历方式就叫做迭代器遍历。

迭代器是遍历集合的一个工具,内部有一个光标,指向了集合的最开始的位置,每执行一次,光标向后移动一位。

Iterator<E> iterator() 获取集合的迭代器
Iterator是一个接口,表示迭代器,里面的方法:

  • boolean hasNext():判断是否还有元素可以获取。
  • E next():获取光标位置的元素,将光标向后移动

迭代器的遍历步骤;

  • 1.通过集合调用iterator方法获取迭代器对象
  • 2.通过迭代器对象调用hasNext方法判断是否有元素可以获取
  • 3.如果有元素可以获取,那么就调用next方法获取元素,并将光标向后移动。
public static void main(String[] args) {
        //创建集合
        Collection<String> c = new ArrayList<>();
        //添加元素
        c.add("张三");
        c.add("李四");
        c.add("王五");
        //使用迭代器遍历集合。
        //获取迭代器
        Iterator<String> itr = c.iterator();
        //因为只有集合中有元素可以获取时我们才进行遍历操作,条件可以判断集合中是否还有元素可以获取
        while(itr.hasNext()) {
            //如果条件成立,表示有元素可以获取,那么我们就获取元素
            String str = itr.next();
            System.out.println(str);
    	}
}

2.1 并发修改异常

如果迭代器遍历的过程中使用集合的方法对集合进行增删操作,再次进行遍历会引发并发修改异常。

public static void main(String[] args) {
        //创建集合
        ArrayList<String> list = new ArrayList<>();
        //添加元素
        list.add("hello");
        list.add("world");
        list.add("java");
        list.add("php");
        //遍历删除
        Iterator<String> iterator = list.iterator();
        //循环遍历
        while (iterator.hasNext()) {
            String str = iterator.next();
            if (str.equals("hello")) {
                list.remove(str);
                break;
            }
        }
    }

删除这一步没有报错,是下次进入循环遍历时报错,因为用集合的方法增删操作元素,迭代器本身并不知道,会影响光标的位置判断,因此报错。

解决方法:使用迭代器的子接口 ListIteator的增删功能

	public static void main(String[] args) {
        //创建集合
        ArrayList<String> list = new ArrayList<>();
        //添加元素
        list.add("hello");
        list.add("world");
        list.add("java");
        list.add("php");
        //遍历删除
        ListIterator<String> iterator = list.listIterator();
        //循环遍历
        while (iterator.hasNext()) {
            String str = iterator.next();
            if (str.equals("hello")) {
                iterator.remove();
                break;
            }
        }
        System.out.println(list);//[world, java, php]
    }

三、增强for循环

在JDK5的时候,多了一个新的遍历方式叫做增强for循环(foreach),可以遍历数组或集合。

格式:
        for (数据类型 变量名 : 容器) {
            ...
        }
格式解释:
数据类型:要遍历的容器中保存的是什么类型的数据,这个数据类型就写什么
变量名: 表示容器中的每一个元素
容器: 要遍历的容器,可以是数组,也可以是集合

增强for是一种语法糖,语法糖指的是本质没有变,只不过写法更加的简洁了。

  • 增强for遍历数组,本质还是使用的普通for循环
  • 增强for遍历集合,本质上是使用的迭代器遍历集合
	public static void main(String[] args) {
        //创建数组
        int[] intArr = {11, 22, 33, 44, 55};
        //使用增强for遍历
        for (int num : intArr) {
            //变量num表示数组中的每一个元素
            System.out.println(num);
        }
        System.out.println("======================================");
        //创建集合,保存字符串
        Collection<String> c = new ArrayList<>();
        //添加元素
        c.add("hello");
        c.add("world");
        c.add("java");
        //使用增强for遍历
        for (String s : c) {
            System.out.println(s);
        }
    }

增强for的好处和缺点:

  • 好处: 写法简洁,省去了对索引的操作
  • 缺点: 因为省略了索引,所以无法再遍历的过程中操作索引,如果要在遍历过程中涉及到索引操作,还是需要使用普通for循环

四、泛型

4.1泛型概述

泛型是一种未知的,不确定的数据类型,如ArrayList<E>中的E就是一个泛型,是一个未知的数据类型。

泛型虽然是未知的数据类型,但是并不是一直未知,一直不确定,在我们使用一个类的时候,这个泛型表示的数据类型会被确定下来,如ArrayList<Student>,此时这个泛型类型E表示的就是Student类型,且在之后的使用中一直是Student类型,不能改变。

	public static void main(String[] args) {
        //创建集合
        ArrayList list = new ArrayList();
        //添加数据
        //list.add(100); 运行时报错
        list.add("hello");
        list.add("world");
        list.add("java");
        //遍历集合,输出集合中每一个字符串的长度
        for (Object obj : list) {
            //将obj向下转型成String类型
            String str = (String) obj;
            System.out.println(str.length());
        }
        System.out.println("============================");
        //创建集合,使用泛型
        ArrayList<String> list2 = new ArrayList<>();
        //添加数据
        //list2.add(100); 编译时期报错
        list2.add("hello");
        list2.add("world");
        list2.add("java");
        //遍历集合,输出集合中每一个字符串的长度
        for (String str : list2) {
            System.out.println(str.length());
        }
    }

泛型也可以省略,如果省略泛型相当于泛型是Object

泛型好处:

  • 1.省略了向下转型的代码
  • 2.将运行时期的问题提前到了编译时期

4.2 泛型擦除

Java中的泛型都是伪泛型,泛型只在源代码阶段有效,一旦编译,泛型就会消失。

	public static void main(String[] args) {
        //创建集合
        ArrayList<String> list = new ArrayList<>();
        //添加元素
        list.add("hello");
        list.add("world");
        list.add("java");
        //使用增强for遍历(快捷键:集合.for)
        for (String s : list) {
            System.out.println(s);
        }
    }

经过反编译的源码如下:

public class Test
{
	public Test()
	{
	}

	public static void main(String args[])
	{
		ArrayList list = new ArrayList();
		list.add("hello");
		list.add("world");
		list.add("java");
		String s;
		for (Iterator iterator = list.iterator(); iterator.hasNext(); System.out.println(s))
			s = (String)iterator.next();

	}
}

从上面代码可以看出,编译之后,泛型消失,还是通过向下转型实现。

4.3 泛型类

如果在定义类的时候,类名后面写上,就表示在类的范围内定义了一个【泛型类型T】,这个T可以是其他任何字母(一般是大写字母)。

当使用这个类创建对象时,泛型类型T(不确定的类型T)就会被指定出来。

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

public class Factory<T> {
	//下面的T分别是使用这种不确定的类型当作参数类型和返回值类型
    public T method(T t){
        return t;
    }
}

/*
    泛型类的测试类
 */
public class Demo03Generic {
    public static void main(String[] args) {
        //尖括号中的Phone表示指定了这个Factory中的泛型是Phone类型
        Factory<Phone> f = new Factory<>();
        //调用method方法
        Phone p = f.method(new Phone());
        p.call();
    }
}

4.4 泛型方法

如果想要调用方法的时候指定泛型的类型,可以使用泛型方法
泛型方法的定义格式:

修饰符 <泛型> 返回值类型 方法名(参数列表) {
		方法体;
		return 返回值;
	}
public class Factory<T> {
	/*
        定义方法,接收什么类型的参数,就得到什么类型的结果
        <E>: 表示在方法上定义泛型
		后面两个E:在使用泛型类型E当做返回值类型以及形参类型
		E与T不同,表示泛型方法的泛型与泛型类的泛型类型不同
     */
    public <E> E getSame(E e) {
        return e;
    }
}
	/*
	泛型方法的测试类
	*/
	public static void main(String[] args) {
        //创建Factory对象
        Factory<Phone> f = new Factory<>();
        //调用方法
        Phone phone = f.getSame(new Phone());
        Pad pad = f.getSame(new Pad());
        String str = f.getSame("aa");
    }

在方法上定义的泛型,需要等到调用方法的时候才能确定泛型的类型。

4.5 泛型接口

如果在定义接口的时候在接口后面加上,这个接口就是一个泛型接口。

泛型接口的使用:

  • 1.实现类实现接口的时候,可以直接明确接口中的泛型类型。
  • 2.实现类实现接口的时候,可以不指定接口中的泛型,等到使用实现类的时候再指定
public interface MyInterface<T> {//表示定义了一个未知的,不确定的类型T
    //定义抽象方法,使用泛型类型T当做参数类型和返回值类型
    T method(T t);
}
/*
    实现类实现接口的时候,可以在接口名字后面写上尖括号并指定泛型
 */
public class MyClassA implements MyInterface<Phone>{

    @Override
    public Phone method(Phone phone) {
        return null;
    }
}

在定义类或接口的时候,在当前类或接口的名字后面写的尖括号才是在定义泛型,其他地方都是在使用泛型。

/*
    在定义类或接口的时候,在当前类或接口的名字后面写的尖括号才是在定义泛型。
 */
public class MyClassB<T> implements MyInterface<T>{

    @Override
    public T method(T t) {
        return null;
    }
}
/*
    泛型接口的测试类
 */
public class Demo05Generic {
    public static void main(String[] args) {
        MyClassA ma = new MyClassA();
        Phone phone = ma.method(new Phone());

        MyClassB<Pad> mb = new MyClassB<>();
        Pad pad = mb.method(new Pad());
    }
}

4.6 泛型通配符

泛型之间没有继承关系,如ArrayList<String>并不是ArrayList<Object>的子类,如果想要接收匹配任何类型的泛型,可以使用泛型通配符。

泛型通配符要放在方法参数位置被动匹配(比如方法参数位置), 不要主动使用(创建对象时泛型用通配符)。

public static void main(String[] args) {
        //定义集合
        ArrayList<String> strList = new ArrayList<>();
        //添加元素
        strList.add("hello");
        strList.add("world");
        strList.add("java");
        //调用printArrayList遍历
        printArrayList(strList);

        //定义集合
        ArrayList<Integer> intList = new ArrayList<>();
        intList.add(100);
        intList.add(200);
        intList.add(300);
        printArrayList(intList);

        //定义集合
        ArrayList<?> list = new ArrayList<>();
        //list.add("aa");
    }

    /*
        定义方法,可以遍历保存任何数据的集合。
     */
    public static void printArrayList(ArrayList<?> list) {
        for (Object obj : list) {
            System.out.println(obj);
        }
    }

4.7 泛型限定

为了安全考虑,我们不能让所有类型的数据通过泛型通配符进入程序,此时就需要对泛型通配符?的取值范围进行限制,就需要用到泛型限定。

  • <? extends A>:泛型要么是A类,要么是A的子类(上限)
  • <? super A>:泛型要么是A类,要么是A的父类 (下限)

泛型的作用主要用于让代码写的更加简洁,实现代码重构,优化代码设计。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值