Java进阶-集合(3)与泛型

2024-03-01 13:06:43 浏览数 (1)

这次介绍集合中的Iterator迭代器,以及泛型。简单来说,泛型对集合的元素类型进行了限制,使用泛型可以在编译时检查类型安全,提高代码的重用率。内容如下

一、Iterator迭代器

1、概念

Iterator迭代器是一个接口,作用是遍历容器的所有元素,也是 Java 集合框架的成员。

注:与 Collection 和 Map 系列的集合不同,Collection 和 Map 系列集合主要用于盛装其他对象,而 Iterator 则主要用于遍历Collection 集合中的元素。

2、Iterator接口定义的方法

可通过在IDEA中选中Iterator,ctrl B查看源码的方式查看对应方法。

boolean hasNext():如果被迭代的集合元素还没有被遍历完,则返回 true。

next():返回集合里的下一个元素。返回类型为Object(可能涉及强转)

void remove():删除集合里上一次 next 方法返回的元素。

void forEachRemaining(Consumer action):这是 Java 8 为 Iterator 新增的默认方法,该方法可使用 Lambda 表达式来遍历集合元素。

3、案例
3.1 Iterator遍历集合
代码语言:javascript复制
import java.util.Collection; //导包
import java.util.HashSet;
import java.util.Iterator;

public class IteratorTest {
    public static void main(String[] args){
        Collection col=new HashSet(); //向上转型,把子类对象直接赋给父类引用(不用强转)
        col.add("zhangsan"); //添加元素
        col.add("lishi");
        col.add("wangwu");
        for(Object i:col){ //使用forEach()方法遍历集合
            System.out.println(i);
        }
        System.out.println("--------");
        Iterator it=col.iterator(); //获取迭代器遍历集合
        while (it.hasNext()){ //it.next()方法返回的数据类型是Object类型(需要强转)
            String coll=(String) it.next();
            //不强转直接用it.next()也能遍历出结果,但不能进行下一步的比较移除操作,规范类型,养成好的习惯很重要
            System.out.println(coll);
            if (coll.equals("zhangsan")){ //字符串比较
                it.remove(); //从集合中删除上一次next()方法返回的元素
            }
            coll="zhaoliu"; //对coll变量赋值,不会改变集合元素本身
        }
        System.out.println(col); //打印集合
    }
}

运行结果

代码语言:javascript复制
lishi
zhangsan
wangwu
--------
lishi
zhangsan
wangwu
[lishi, wangwu]

注:上面程序中对迭代变量 coll赋值,但当再次输岀 col 集合时,集合里的元素不变。所以当使用 Iterator 对集合元素进行迭代时,Iterator 并不是把集合元素本身传给了迭代变量,而是把集合元素的值传给了迭代变量,所以修改迭代变量的值对集合元素本身没有任何影响

3.2 要点总结

1)Iterator 仅用于遍历集合,如果需要创建 Iterator 对象,则必须有一个被迭代的集合,否则Iterator 没有存在的价值。 2)Iterator 必须依附于 Collection 对象,若有一个 Iterator 对象,则必然有一个与之关联的 Collection 对象。(与上同理) 3)Iterator 提供了两个方法来迭代访问 Collection 集合里的元素,Collection 集合里的元素不能被改变,只有通过 Iterator 的 remove() 方法删除上一次 next() 方法返回的集合元素才可以,否则将会引发“java.util.ConcurrentModificationException”异常。

3.3 示例
代码语言:javascript复制
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;

public class IteratorTest {
    public static void main(String[] args){
        Collection col=new HashSet(); //向上转型,把子类对象直接赋给父类引用(不用强转)
        col.add("zhangsan"); //添加元素
        col.add("lishi");
        col.add("wangwu");
        Iterator it=col.iterator(); //获取迭代器遍历集合
        while (it.hasNext()){ //it.next()方法返回的数据类型是Object类型(需要强转)
            String coll=(String) it.next();
            //不强转直接用it.next()也能遍历出结果,但不能进行下一步的比较移除操作,规范类型,养成好的习惯很重要
            System.out.println(coll);
            if (coll.equals("zhangsan")){ //字符串比较
                //使用Iterator迭代过程中,不可修改集合元素,下面代码引发异常
                col.remove(coll);
            }
        }
    }
}

运行结果

代码语言:javascript复制
lishi
zhangsan
Exception in thread "main" java.util.ConcurrentModificationException
    at java.base/java.util.HashMap$HashIterator.nextNode(HashMap.java:1510)
    at java.base/java.util.HashMap$KeyIterator.next(HashMap.java:1533)
    at test3.IteratorTest.main(IteratorTest.java:15)
4、Iterator错误检查机制
4.1 概述

Iterator 迭代器采用的是快速失败(fail-fast)机制一旦在迭代过程中检测到该集合已经被修改(通常是程序中的其他线程修改),程序立即引发 ConcurrentModificationException 异常,而不是显示修改后的结果,这样可以避免共享资源而引发的潜在问题

注:快速失败(fail-fast)机制,是 Java Collection 集合中的一种错误检测机制。

用户可以自行验证,当3.3 示例中改为删除“wangwu”字符串即将上面的coll.equals(“zhangsan”)改为coll.equals(“wangwu”)时,则不会引发异常,因为王五是最后添加的,相当于所有都迭代完成后才删除,故不会引发ConcurrentModificationException 异常。

总结:所以说尽量不要在迭代时删除集合元素,防止引发异常。

二、泛型

1、集合的设计角度

把集合看成容器,将对象“丢进”集合,集合不会记住对象的数据类型(即丢失了对象的状态信息),再次取出时,对象的编译类型变为Object(运行时类型不变)

1.1 优点

具有很好的通用性,能保存任何类型的对象(因为Object类是所有类的父类,即创建对象时都能向上转型,不用强转)

1.2 问题(若无泛型)

1)集合对元素类型没有任何限制,如想创建一个只保存 Dog 对象的集合,但程序也可以轻易地将 Cat 对象“丢”进去,可能引发异常。 2)把对象“丢进”集合时,集合丢失了对象的状态信息,只知道它盛装的是 Object,因此取出集合元素后通常还需要进行强制类型转换。(这样既增加了编程的复杂度,也可能引发 ClassCastException即类型转换异常)

1.3 解决

为了解决上述问题,从 Java 1.5 开始提供了泛型。

2、泛型
2.1 几点注意

1)抽象地说,泛型是一种“代码模板”,可以用一套代码套用各种类型(泛型编程) 2)具体而言,泛型本质上是提供类型的“类型参数”(参数化类型)。可以为类、接口或方法指定一个类型参数,通过这个参数限制操作的数据类型,从而保证类型转换的绝对安全。

泛型可以在编译时检查类型安全,并且所有的强制转换都是自动和隐式的,提高了代码的重用率。

3、泛型集合

示例:结合泛型与集合编写一个案例实现图书信息输出 1)创建一个Book类(图书编号、图书名称、价格)

代码语言:javascript复制
public class Book { // 定义Book类 (完整javabean)
    private  int id; // 封装成员变量
    private String name;
    private int price;

    public Book(){ // 无参构造方法
    }
    public Book(int id,String name,int price){ // 带全部参数的构造方法
        this.id=id;  // this指向当前变量
        this.name=name;
        this.price=price;
    }
    public String toString(){ //重写父类(Object类)的toString()方法,返回对象的字符串表示
        return this.id " " this.name " " this.price;
    }
    public int getId() { // 提供get和set方法,习惯,虽然这里用不到
        return id;
    }
    public void setId(int id) {
        this.id = id;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public int getPrice() {
        return price;
    }
    public void setPrice(int price) {
        this.price = price;
    }
}

2)使用 Book 作为类型创建 Map 和 List 两个泛型集合,然后向集合中添加图书元素,最后输出集合中的内容

代码语言:javascript复制
import java.util.ArrayList; //导包
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class BookTest { // 创建集合测试类
    public static void main(String[] args){
        Book book1=new Book(1,"唐诗三百首",18); // 创建对象并实例化
        Book book2 = new Book(2, "小星星", 12);
        Book book3 = new Book(3, "成语大全", 22);
        Map<Integer,Book> books=new HashMap<>(); // 创建泛型的Map集合
        //定义Interger类型的键,Book类型对象整体作为值,通过get()方法得到键对应的值打印输出即为全部图书信息
        books.put(1001,book1);  // 将Book对象存储到Map中
        books.put(1002, book2);
        books.put(1003, book3);
        System.out.println("泛型Map存储的图书信息如下:");
        for (Integer id : books.keySet()) {  // for-each循环 遍历键
            System.out.print(id   "——"); //获取键
            System.out.println(books.get(id)); //获取值(Book对象整体,即为全部图书信息)
            //不需要将books.get(id)获取的值强制转换为Book类型,程序会隐式转换(泛型功能)
        }
        List<Book> bookList = new ArrayList<>(); // 定义泛型的List集合
        bookList.add(book1); //添加book对象元素
        bookList.add(book2);
        bookList.add(book3);
        System.out.println("泛型List存储的图书信息如下:");
        for (int i = 0; i < bookList.size(); i  ) { //for循环遍历输出
            System.out.println(bookList.get(i)); //get()方法得到索引对应的元素
            //不需要将bookList.get(i)强制转换为Book类型,程序会隐式转换(泛型功能)
        }
    }
}

运行结果

代码语言:javascript复制
泛型Map存储的图书信息如下:
1001——1 唐诗三百首 18
1002——2 小星星 12
1003——3 成语大全 22
泛型List存储的图书信息如下:
1 唐诗三百首 18
2 小星星 12
3 成语大全 22
4、泛型类
4.1 几点注意

public class class_name<data_type1,data_type2,…>{}:data_type为类型参数(Java 泛型支持声明一个以上的类型参数,逗号隔开)。属性声明:如private data_type1 property_name1;

一般用于类中的属性类型不确定的情况下

在实例化泛型类时,需要指明泛型类中的类型参数,并赋予泛型类属性相应类型的值。

4.2 示例

创建一个学生的泛型类,包含姓名、年龄和性别3个属性 1)创建一个学生的泛型类

代码语言:javascript复制
public class Stu<N,A,S> { //定义学生泛型类
    private N name; //封装成员变量
    private A age;
    private S sex;
    public Stu(){ //无参构造方法
    }
    public Stu(N name,A age,S sex){ //带全部参数的构造方法
        this.name=name;
        this.age=age;
        this.sex=sex;
    }
    public String toString(){ //toString()方法返回对象的字符串表示
        return this.name " " this.age " " this.sex;
    }
    public N getName() { //提供整套get和set方法
        return name;
    }
    public void setName(N name) {
        this.name = name;
    }
    public A getAge() {
        return age;
    }
    public void setAge(A age) {
        this.age = age;
    }
    public S getSex() {
        return sex;
    }
    public void setSex(S sex) {
        this.sex = sex;
    }
}

2)创建测试类

代码语言:javascript复制
public class StuTest { // 创建学生泛型类的测试类
    public static void main(String[] args){
        // 实例化泛型对象,直接在类后面加上限定泛型类的类型参数
        Stu<String,Integer,Character> s=new Stu<>("zhangsan",20,'男');
        String name=s.getName(); //调用get方法
        Integer age=s.getAge();
        Character sex=s.getSex();
        //以上获取时不用类型转换,程序隐式地将Object类型的数据转换为相应的数据类型
        System.out.println("----------学生信息----------");
        System.out.println("学生姓名:" name " 年龄:" age " 性别:" sex);
    }
}
5、泛型方法
5.1 注意与说明

泛型可以在类中包含参数化的方法,而方法所在的类可以是泛型类,也可以不是泛型类(即是否拥有泛型方法,与其所在的类是不是泛型没有关系)。

泛型方法使得该方法能够独立于类而产生变化。如果使用泛型方法可以取代类泛型化,那么就应该只使用泛型方法。

一个 static 方法无法访问泛型类的类型参数。因此,如果 static 方法需要使用泛型能力,就必须使其成为泛型方法。

格式:[访问权限修饰符] [static] [final] <类型参数列表> 返回值类型 方法名([形式参数列表]),如

如:public static <T> List find(Class<T> cs,int userId){}

一般来说编写 Java 泛型方法,其返回值类型至少有一个参数类型是泛型,且类型应该是一致的,如果只有返回值类型或参数类型之一使用了泛型,那么这个泛型方法的使用就被限制了。

5.2 示例

使用泛型方法打印图书信息。定义泛型方法,参数类型使用“T”来代替。

代码语言:javascript复制
// 1) 定义一个Book类,代码同3、泛型集合示例第一个
// 2) 定义Book泛型方法的测试类
public class BookDemo { //创建book泛型方法的实现类
    public static <T> void List(T book){ //定义泛型方法,T为参数类型
        if(book!=null){ //若不为空,输出图书信息
            System.out.println(book);
        }
    }
    public static void main(String[] args){
        //实例化Book对象
        Book b=new Book(1,"java编程",20);
        List(b); //调用List泛型方法
    }
}
//result
//1 java编程 20
6、泛型高级用法

除在集合、类和方法中使用,泛型还有如下高级用法

6.1 限制泛型可用类型

语法:class 类名称,anyClass指某个接口或类,使用泛型限制后,泛型类的类型必须实现或继承 anyClass 这个接口或类,且在进行泛型限制时必须使用 extends 关键字(否则默认是Object类型,即其所有子类都可以实例化泛型类对象,这样就没有意义了)

代码语言:javascript复制
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
// 限制ListClass的泛型类型必须实现List接口
public class ListClass <T extends List>{
    public static void main(String[] args) {
        // 实例化使用ArrayList的泛型类ListClass,正确
        ListClass<ArrayList> lc1 = new ListClass<ArrayList>();
        // 实例化使用LinkedList的泛型类LlstClass,正确
        ListClass<LinkedList> lc2 = new ListClass<LinkedList>();
        // 实例化使用HashMap的泛型类ListClass,错误,因为HasMap没有实现List接口
        // ListClass<HashMap> lc3=new ListClass<HashMap>();
    }
}
6.2 使用类型通配符<?>

类型通配符作用

在创建一个泛型类对象时限制这个泛型类的类型必须实现或继承某个接口或类。

list<?>

表示元素类型未知的list,其元素可以匹配任何的类型。表示它是各种泛型list的父类,并不能把元素添加到其中

类型通配符上限:<? extends 类型>

  • List<? extends Number>:表示的类型是Number或其子类型

类型通配符下限:<?super 类型>

  • List<? super Number>:表示的类型是Number或其父类型

语法

泛型类名称<? extends List>a = null;

代码语言:javascript复制
A<? extends List>a = null;
a = new A<ArrayList> ();    // 正确
b = new A<LinkedList> ();    // 正确
// 错误 HashMap类没有实现List接口

继承泛型类和实现泛型接口

代码语言:javascript复制
//继承泛型类
public class FatherClass<T1>{}
public class SonClass<T1,T2,T3> extents FatherClass<T1>{}
//在泛型中实现接口
interface interface1<T1>{}
interface SubClass<T1,T2,T3> implements
Interface1<T2>{}

扩展: 可变参数(即参数个数可变)

格式:修饰符 返回值类型 方法名(数据类型…变量名){}

范例

public static int sum(int…a){}

注意

这里的变量其实是一个数组。

如果一个方法有多个参数,包含可变参数,可变参数要放在后面。

0 人点赞