这次介绍集合中的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遍历集合 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 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){ System.out.println(i); } System.out.println("--------" ); Iterator it=col.iterator(); while (it.hasNext()){ String coll=(String) it.next(); System.out.println(coll); if (coll.equals("zhangsan" )){ it.remove(); } coll="zhaoliu" ; } System.out.println(col); } }
运行结果
1 2 3 4 5 6 7 8 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 示例 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 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()){ String coll=(String) it.next(); System.out.println(coll); if (coll.equals("zhangsan" )){ col.remove(coll); } } } }
运行结果
1 2 3 4 5 6 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类(图书编号、图书名称、价格)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 public class Book { private int id; private String name; private int price; public Book () { } public Book (int id,String name,int price) { this .id=id; this .name=name; this .price=price; } public String toString () { return this .id+" " +this .name+" " +this .price; } public int getId () { 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 两个泛型集合,然后向集合中添加图书元素,最后输出集合中的内容
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 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 <>(); books.put(1001 ,book1); books.put(1002 , book2); books.put(1003 , book3); System.out.println("泛型Map存储的图书信息如下:" ); for (Integer id : books.keySet()) { System.out.print(id + "——" ); System.out.println(books.get(id)); } List<Book> bookList = new ArrayList <>(); bookList.add(book1); bookList.add(book2); bookList.add(book3); System.out.println("泛型List存储的图书信息如下:" ); for (int i = 0 ; i < bookList.size(); i++) { System.out.println(bookList.get(i)); } } }
运行结果
1 2 3 4 5 6 7 8 泛型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)创建一个学生的泛型类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 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 () { return this .name+" " +this .age+" " +this .sex; } public N getName () { 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)创建测试类
1 2 3 4 5 6 7 8 9 10 11 12 public class StuTest { public static void main (String[] args) { Stu<String,Integer,Character> s=new Stu <>("zhangsan" ,20 ,'男' ); String name=s.getName(); Integer age=s.getAge(); Character sex=s.getSex(); 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”来代替。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 public class BookDemo { public static <T> void List (T book) { if (book!=null ){ System.out.println(book); } } public static void main (String[] args) { Book b=new Book (1 ,"java编程" ,20 ); List(b); } }
6、泛型高级用法 除在集合、类和方法中使用,泛型还有如下高级用法
6.1 限制泛型可用类型
语法:class 类名称,anyClass指某个接口或类,使用泛型限制后,泛型类的类型必须实现或继承 anyClass 这个接口或类,且在进行泛型限制时必须使用 extends 关键字(否则默认是Object类型,即其所有子类都可以实例化泛型类对象,这样就没有意义了)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 import java.util.ArrayList;import java.util.LinkedList;import java.util.List;public class ListClass <T extends List >{ public static void main (String[] args) { ListClass<ArrayList> lc1 = new ListClass <ArrayList>(); ListClass<LinkedList> lc2 = new ListClass <LinkedList>(); } }
6.2 使用类型通配符<?> 类型通配符作用
在创建一个泛型类对象时限制这个泛型类的类型必须实现或继承某个接口或类。
list<?>
表示元素类型未知的list,其元素可以匹配任何的类型。表示它是各种泛型list的父类,并不能把元素添加到其中
类型通配符上限 :<? extends 类型>
List<? extends Number>:表示的类型是Number或其子类型
类型通配符下限 :<?super 类型>
List<? super Number>:表示的类型是Number或其父类型
语法
泛型类名称<? extends List>a = null;
1 2 3 4 A<? extends List >a = null ; a = new A <ArrayList> (); b = new A <LinkedList> ();
继承泛型类和实现泛型接口
1 2 3 4 5 6 7 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){}
注意
这里的变量其实是一个数组。
如果一个方法有多个参数,包含可变参数,可变参数要放在后面。