欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

【 Collection集合、Iterator迭代器、泛型、泛型通配符 】

程序员文章站 2022-05-23 15:21:29
...

Collection集合

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

集合和数组的区别:

  • 数组的长度固定,集合的长度不可变。
  • 数组存储的都是同一类型的基本数据类型。而集合存储的都是对象,对象的类型可以不一致。

Collection:集合层次的顶层接口,没有直接的实现类,有不同的子接口继承Collection。而每一个子接口都有自己的特点。每一个子接口都有自己的实现类。
【 Collection集合、Iterator迭代器、泛型、泛型通配符 】
java.util.Collection接口中常用的集合方法:

public boolean add(E e):把指定的参数元素,添加到集合中;返回添加动作是否成功。
public boolean remove(Object e):根据参数指定的元素内容,从集合当中删除元素;返回删除是否成功。
public boolean contains(Object e):判断集合是否包含指定的元素。
public boolean isEmpty():判断集合是否为空白(一个元素都没有)。
public void clear():清空集合当中所有的元素内容。
public int size():获取集合的长度尺寸。
public Object[] toArray():将集合转换成为数组形式。

注意事项
对于remove方法contains方法来说,参数是Object类型。这里是如何得知两个对象是否“相同”的呢?
由equals和hashCode两个方法共同决定。

Collection<String> coll = new ArrayList<>();
coll.add("迪丽热巴");
coll.add("古力娜扎");
coll.add("玛尔扎哈");
System.out.println(coll); // [迪丽热巴, 古力娜扎, 玛尔扎哈]

boolean success = coll.remove("玛尔扎哈");
System.out.println("删除玛尔扎哈是否成功:" + success); // true
System.out.println(coll); // [迪丽热巴, 古力娜扎]

success = coll.remove("高圆圆");
System.out.println("删除高圆圆是否成功:" + success); // false
System.out.println(coll); // [迪丽热巴, 古力娜扎]

coll.add("鹿晗");
coll.add("马晗");
coll.add("牛晗");
coll.add("杨晗");
coll.add("于晗");
coll.add("朱晗");
coll.add("侯晗");
System.out.println(coll); // [迪丽热巴, 古力娜扎, 鹿晗, 马晗, 牛晗, 杨晗, 于晗, 朱晗, 侯晗]

System.out.println(coll.contains("马晗")); // true
System.out.println(coll.contains("狗晗")); // false
System.out.println("=============");

System.out.println("集合是否为空:" + coll.isEmpty()); // false

coll.clear();
System.out.println("集合是否为空:" + coll.isEmpty()); // true
System.out.println("集合的尺寸:" + coll.size()); // 0

coll.add("XXX");
coll.add("YYY");
coll.add("ZZZ");
System.out.println("集合的尺寸:" + coll.size()); // 3
System.out.println("=============");

Object[] array = coll.toArray();
for (int i = 0; i < array.length; i++) {
    System.out.println(array[i]);
}

Iterator迭代器

由于集合中并不是所有的集合都有索引值,所以遍历Collection集合,就要获取迭代器完成迭代操作。

迭代器的方法
public Iterator iterator():获取对应集合的迭代器,用来遍历集合当中的元素。

概念
对于一个Collection集合来说,集合元素的获取方式,在取元素的时候要先判断集合当中有没有元素,如果有,把这个元素取出来,继续判断,如果有就取出。一直到把集合当中的所有元素都取出来了。这种取出的方法称为迭代。

java.util.Iterator<T>接口代表迭代器

public boolean hasNext:判断集合中有没有元素,有就返回true。
public E next():返回迭代的下一个元素。
【 Collection集合、Iterator迭代器、泛型、泛型通配符 】

注意
在使用迭代器遍历集合的过程当中,一定要避免直接通过集合改变其中元素的个数。
如果不听话,不乖,那么将会发生ConcurrentModificationException并发修改异常。

Collection<String> coll = new ArrayList<>();
coll.add("迪丽热巴");
coll.add("古力娜扎");
coll.add("泷泽萝拉");

Iterator<String> iter = coll.iterator();
while (iter.hasNext()) {
    System.out.println(iter.next());
// coll.add("阿里巴巴"); // ConcurrentModificationException
}

增强for循环:

增强for循环(昵称也叫做for-each循环)是JDK 1.5添加的特性之一。

for (数据类型 变量名称 : 数组) {
    // ...
}

注意:其中的数据类型并不一定是int,不代表索引值。
含义:用左边的变量,分别得到右侧数组当中的每一个数据值。

备注
这其实只是一个语法糖。对于数组来说,增强for循环底层其实就是一个普通的for循环。

double[] array = {1.5, 2.5, 3.5};
// 变量num将会分别得到数组array当中的每一个元素
for (double num : array) {
    System.out.println(num);
}

增强for循环照样也可以支持集合。

for (数据类型 变量名称 : 集合) {
    // ...
}

备注
这其实也是一种语法糖,对于集合来说,底层其实就是迭代器,只是表面上增强for循环的写法简单而已。

Collection<String> coll = new ArrayList<>();
coll.add("迪丽热巴");
coll.add("古力娜扎");
coll.add("阿里巴巴");

// name将会分别获取集合coll当中的每一个元素
for (String name : coll) { // iter.next()
    System.out.println(name);
}

使用增强for循环的时候,注意事项

  1. 支持数组,其实是一个语法糖,底层就是普通的fori循环。
  2. 支持java.lang.Iterable接口对象,其中就包含了集合。因为这个接口规定了一项能力:public Iterator<T> iterator():获取迭代器的方法。
  3. 支持集合,也是一个语法糖。底层就是在使用迭代器。
  4. 增强for循环当中没有索引值,所以就无法直接修改数组或集合中的内容。【重点】
// 外侧还定义了一个Person类。有name和age两个属性值。
// 增强for循环没有索引值,所以基本数据类型的数据值不能变,同时引用数据类型的地址值不能变。
Collection<Person> coll = new ArrayList<>();
coll.add(new Person("赵丽颖", 18));
coll.add(new Person("鹿晗", 73));
coll.add(new Person("王宝强", 84));

// [Person{name='赵丽颖', age=18}, Person{name='鹿晗', age=73}, Person{name='王宝强', age=84}]
System.out.println(coll);

// 换一个别的对象是做不到的。
// 但是通过对象名称地址值调用方法仍然是可以的,从而对象的成员变量内容发生了改变。
for (Person person : coll) { // Person person = iter.next();
    person.setAge(40);
}

// [Person{name='赵丽颖', age=40}, Person{name='鹿晗', age=40}, Person{name='王宝强', age=40}]
System.out.println(coll);

泛型

在集合中可以存取任意类型的对象,在把对象存储到集合中后,他们都会被提升成Object类型。每取出一个对象,进行相关操作,就必须进行类型转换。

Collection<String> coll = new ArrayList<>();
coll.add("abc");
coll.add("Hello");
// coll.add(100); // 错误写法!

// for (Object o : coll) {
//     String str = (String) o;
//     System.out.println(str.length());
//  }

for (String str : coll) {
   System.out.println(str.toUpperCase()); // 转成大写
}
  • 泛型:
    tips:一般在创建对象时,将未知的类型确定具体的类型。当没有指定泛型时,默认类型为Object类型。

使用泛型的好处

  1. 保证类型统一,确保类型安全。
  2. 将可能发生的类型安全问题,从运行期提前到编译期。(有问题尽量在javac的时候就暴露出来最好,别等到java运行时再暴露)
  3. 省去向下转型的麻烦。
  4. 让代码模板化。

自定义泛型的三种用法:

  1. 泛型类
  2. 泛型接口
  3. 泛型方法

如何自定义一个泛型类?

修饰符 class 类名称<泛型> {
    // ...
}

泛型的名称用什么都可以,一般推荐使用大写字母。
泛型代表一种尚不确定的类型,所有本类范围之内都能用这个泛型当做不确定的类型进行使用。

什么时候才能确定这个泛型?
当创建泛型类对象的时候,就可以确定。

// 定义一个泛型类:
public class Factory<A> {
  public A makePhone(A param) {
     // 进行组装的若干动作
     return param;
  }
}
// 测试类:
Factory<IPhone> factory1 = new Factory<>();
IPhone result1 = factory1.makePhone(new IPhone());

Factory<Nokia> factory2 = new Factory<>();
Nokia result2 = factory2.makePhone(new Nokia());

如何定义一个泛型接口呢?

修饰符 interface 接口名称<泛型> {
    // ...
}

含义和泛型类是完全相同的:本接口之内泛型通用。

什么时候才能确定接口的泛型?

  1. 实现类在实现接口的时候,直接指定具体泛型。
  2. 实现类仍然不指定具体泛型,那么实现类也必须是一个泛型类。
// 定义一个泛型接口
public interface MyInterface<T> {
    void method(T param); // 抽象方法的参数类型,跟着泛型走。
}

// 定义一个泛型的实现接口
public class MyInterfaceImpl<T> implements MyInterface<T> {
    @Override
    public void method(T param) { /* 这个泛型仍然不确定,
    本类的泛型是谁,它就是谁*/
    }
}

// 测试类
MyInterfaceImpl<String> impl = new MyInterfaceImpl<>();
impl.method("abc");

// 也可以用普通的实现类实现泛型接口,需要指定泛型的类型
public class MyInterfaceImplA implements MyInterface<String> {
    @Override
    public void method(String param) { // 这个参数就是具体泛型String类型
    }
}

对于泛型类/泛型接口来说,泛型是在本类/本接口当中全局通用。

泛型方法

修饰符 <泛型> 返回值类型 方法名称(参数类型 参数名称) {
    方法体
}

备注
这个泛型定义在方法上,所以只有当前这个方法自己专用。别人不能用。

// 定义一个泛型的类,在类中定义个泛型的方法
public class MyClass<A> {

    public void method1(A param) {

    }

    public void method2(A param) {

    }

    // 定义一个泛型的方法            
    public <B> B methodSpecial(B param) {
        // ...
        return param;
    }
}
// 测试类:
MyClass<Integer> obj = new MyClass<>();
obj.method1(100);
obj.method2(200);
// obj.method1("abc"); // 错误

String str = obj.methodSpecial("abc");
Person person = obj.methodSpecial(new Person());

泛型通配符
泛型的通配符其实就是一个问号:【?】
作用:用来被动匹配任何一种泛型。

注意事项

  1. 一旦使用了泛型?通配符进行匹配接收,那么遍历的时候就只能当做Object,因为不确定泛型到底传进来的是谁。
  2. 这个通配符问号,只能在匹配接收的时候使用,不能在定义泛型的时候使用。
public static void main(String[] args) {
    ArrayList<String> list1 = new ArrayList<>();
    ArrayList<Integer> list2 = new ArrayList<>();

    methodA(list1); // 正确
    methodB(list2); // 正确

//        methodA(list2); // 错误
//        methodB(list1); // 错误

//        methodObject(list1); // 错误
//        methodObject(list2); // 错误

    method(list1); // 正确
    method(list2); // 正确
}

private static void method(ArrayList<?> list) {
    for (Object obj : list) {
        System.out.println(obj);
    }
}

泛型的上下限

对于泛型的通配符问号,有两种特殊写法:上下限,上下界。

<?>:随便,谁都行。
<? extends 类>:可以是指定的类,或者其子类。(上限,最高不能超过这个类。)
<? super 类>:可以是指定的类,或者其父类。(下限,最低不能超过这个类。)
public static void main(String[] args) {
    ArrayList<Animal> animalList = new ArrayList<>();
    ArrayList<Dog> dogList = new ArrayList<>();
    ArrayList<Cat> catList = new ArrayList<>();

    ArrayList<String> strList = new ArrayList<>();

    methodExtends(animalList); // OK
    methodExtends(dogList); // OK
    methodExtends(catList); // OK

//  methodExtends(strList); // 错误!
    System.out.println("==============");

    methodSuper(dogList); // OK
    methodSuper(animalList); // OK
//  methodSuper(catList); // 错误!
//  methodSuper(strList); // 更错!
}

// 这个泛型只能匹配Animal或者是Animal的子类
private static void methodExtends(ArrayList<? extends Animal> list) {

}

// 这个泛型只能匹配Dog或者是Dog的父类
private static void methodSuper(ArrayList<? super Dog> list) {

}