为什么要使用泛型和迭代器

为什么要使用泛型和迭代器 + 面试题

泛型

1)为什么要用泛型?

在泛型没有诞生之前,我们经常会遇到这样的问题,如以下代码所示:

ArrayList arrayList = new ArrayList();
arrayList.add("Java");
arrayList.add(24);
for (int i = 0; i < arrayList.size(); i++) {
    String str = (String) arrayList.get(i);
    System.out.println(str);
}

看起来好像没有什么大问题,也能正常编译,但真正运行起来就会报错:

Exception in thread "main" java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String

at xxx(xxx.java:12)

类型转换出错,当我们给 ArrayList 放入不同类型的数据,却使用一种类型进行接收的时候,就会出现很多类似的错误,可能更多的时候,是因为开发人员的不小心导致的。那有没有好的办法可以杜绝此类问题的发生呢?这个时候 Java 语言提供了一个很好的解决方案——“泛型”。

2)泛型介绍

泛型:泛型本质上是类型参数化,解决了不确定对象的类型问题。
泛型的使用,请参考以下代码:

ArrayList<String> arrayList = new ArrayList();
arrayList.add("Java");

这个时候如果给 arrayList 添加非 String 类型的元素,编译器就会报错,提醒开发人员插入相同类型的元素。

这样就可以避免开头示例中,类型不一致导致程序运行过程中报错的问题了。

3)泛型的优点

泛型的优点主要体现在以下三个方面。

  • 安全:不用担心程序运行过程中出现类型转换的错误。
  • 避免了类型转换:如果是非泛型,获取到的元素是 Object 类型的,需要强制类型转换。
  • 可读性高:编码阶段就明确的知道集合中元素的类型。

迭代器(Iterator)

1)为什么要用迭代器?

我们回想一下,在迭代器(Iterator)没有出现之前,如果要遍历数组和集合,需要使用方法。

数组遍历,代码如下:

String[] arr = new String[]{"Java", "Java虚拟机", "Java中文社群"};
for (int i = 0; i < arr.length; i++) {
    String item = arr[i];
}

集合遍历,代码如下:

List<String> list = new ArrayList<String>() {{
    add("Java");
    add("Java虚拟机");
    add("Java中文社群");
}};
for (int i = 0; i < list.size(); i++) {
    String item = list.get(i);
}

而迭代器的产生,就是为不同类型的容器遍历,提供标准统一的方法。

迭代器遍历,代码如下:

Iterator iterator = list.iterator();
while (iterator.hasNext()) {
    Object object = iterator.next();
    // do something
}

总结:使用了迭代器就可以不用关注容器的内部细节,用同样的方式遍历不同类型的容器。

2)迭代器介绍

迭代器是用来遍历容器内所有元素对象的,也是一种常见的设计模式。

迭代器包含以下四个方法。

  • hasNext():boolean —— 容器内是否还有可以访问的元素。
  • next():E —— 返回下一个元素。
  • remove():void —— 删除当前元素。
  • forEachRemaining(Consumer):void —— JDK 8 中添加的,提供一个 lambda 表达式遍历容器元素。

迭代器使用如下:

List<String> list = new ArrayList<String>() {{
    add("Java");
    add("Java虚拟机");
    add("Java中文社群");
}};
Iterator iterator =  list.iterator();
// 遍历
while (iterator.hasNext()){
    String str = (String) iterator.next();
    if (str.equals("Java中文社群")){
        iterator.remove();
    }
}
System.out.println(list);

程序执行结果:

[Java, Java虚拟机]

forEachRemaining 使用如下:

List<String> list = new ArrayList<String>() {{
    add("Java");
    add("Java虚拟机");
    add("Java中文社群");
}};
// forEachRemaining 使用
list.iterator().forEachRemaining(item -> System.out.println(item));

相关面试题

1.为什么迭代器的 next() 返回的是 Object 类型?

答:因为迭代器不需要关注容器的内部细节,所以 next() 返回 Object 类型就可以接收任何类型的对象。

2.HashMap 的遍历方式都有几种?

答:HashMap 的遍历分为以下四种方式。

  • 方式一:entrySet 遍历
  • 方式二:iterator 遍历
  • 方式三:遍历所有的 key 和 value
  • 方式四:通过 key 值遍历

以上方式的代码实现如下:

Map<String, String> hashMap = new HashMap();
hashMap.put("name", "老王");
hashMap.put("sex", "你猜");
// 方式一:entrySet 遍历
for (Map.Entry item : hashMap.entrySet()) {
  System.out.println(item.getKey() + ":" + item.getValue());
}
// 方式二:iterator 遍历
Iterator<Map.Entry<String, String>> iterator = hashMap.entrySet().iterator();
while (iterator.hasNext()) {
  Map.Entry<String, String> entry = iterator.next();
  System.out.println(entry.getKey() + ":" + entry.getValue());
}
// 方式三:遍历所有的 key 和 value
for (Object k : hashMap.keySet()) {
  // 循环所有的 key
  System.out.println(k);
}
for (Object v : hashMap.values()) {
  // 循环所有的值
  System.out.println(v);
}
// 方式四:通过 key 值遍历
for (Object k : hashMap.keySet()) {
  System.out.println(k + ":" + hashMap.get(k));
}

3.以下关于泛型说法错误的是?

A:泛型可以修饰类
B:泛型可以修饰方法
C:泛型不可以修饰接口
D:以上说法全错

答:选 C,泛型可以修饰类、方法、接口、变量。
例如:

public interface Iterable\<T\> {
}

4.以下程序执行的结果是什么?

List<String> list = new ArrayList<>();
List<Integer> list2 = new ArrayList<>();
System.out.println(list.getClass() == list2.getClass());

答:程序的执行结果是 true
题目解析:Java 中泛型在编译时会进行类型擦除,因此 List<String> listList<Integer> list2 类型擦除后的结果都是 java.util.ArrayLis ,进而 list.getClass() == list2.getClass() 的结果也一定是 true。

5. List<Object>List<?> 有什么区别?

答:List<?> 可以容纳任意类型,只不过 List<?> 被赋值之后,就不允许添加和修改操作了;而 List<Object>List<?> 不同的是它在赋值之后,可以进行添加和修改操作,如下图所示:

6.可以把 List<String> 赋值给 List<Object> 吗?

答:不可以,编译器会报错,如下图所示:

List 和 List<Object> 的区别是什么?

答: ListList<Object> 都能存储任意类型的数据,但 ListList<Object> 的唯一区别就是,List 不会触发编译器的类型安全检查,比如把 List<String> 赋值给 List 是没有任何问题的,但赋值给 List<Object> 就不行,如下图所示:

List<String> list = new ArrayList<>();
list.add("Java");
list.add("Java虚拟机");
list.add("Java中文社群");
Iterator iterator = list.iterator();
while (iterator.hasNext()) {
    String str = (String) iterator.next();
    if (str.equals("Java中文社群")) {
        iterator.remove();
    }
}
while (iterator.hasNext()) {
    System.out.println(iterator.next());
}
System.out.println("Over");

答:程序打印结果是 Over
题目解析:因为第一个 while 循环之后,iterator.hasNext() 返回值就为 false 了,所以不会进入第二个循环,之后打印最后的 Over。

9.泛型的工作原理是什么?为什么要有类型擦除?

答:泛型是通过类型擦除来实现的,类型擦除指的是编译器在编译时,会擦除了所有类型相关的信息,比如 List<String> 在编译后就会变成 List 类型,这样做的目的就是确保能和 Java 5 之前的版本(二进制类库)进行兼容。

总结

通过本文知道了泛型的优点:安全性、避免类型转换、提高了代码的可读性。泛型的本质是类型参数化,但编译之后会执行类型擦除,这样就可以和 Java 5 之前的二进制类库进行兼容。本文也介绍了迭代器(Iterator)的使用,使用迭代器的好处是不用关注容器的内部细节,用同样的方式遍历不同类型的容器。



欢迎关注我的公众号,回复关键字“Java” ,将会有大礼相送!!! 祝各位面试成功!!!

%97%E5%8F%B7%E4%BA%8C%E7%BB%B4%E7%A0%81.png)

原文地址:https://www.cnblogs.com/dailyprogrammer/p/12272743.html

时间: 2024-11-02 17:04:20

为什么要使用泛型和迭代器的相关文章

泛型,迭代器,LinkedList&lt;E&gt;

1 <e>里面只能填类,不能用基本数据类型,不过integer 这样的的也行 2在模板类(泛型类中)class demo<e>由于不知道e是那个,所有通常都是重写大家都有的toString()方法来调用 泛型的作用主要是为了建立具有类型安全的数据结构 如果没有泛型 LinkedList 使用add方法的时候,由于所有类都是object的子类,所以都可以添加 String s=(String) linkedList1.get(i)需要类型转换 然而有了泛型就没有上面的问题,Linke

集合 泛型 迭代器 增强for 集合唯一性判定

1 集合 1.1 集合概述集合是一个存储容器,只能存储引用类型对象,不存储基本数据类型,8种基本类型对应有8种引用类型,转化后可存入集合集合长度可变,数组长度固定 1.2 集合继承体系 ArrayList类继承了抽象类AbstractList同时实现List接口,而List接口又继承了Collection接口.这说明我们在使用ArrayList类时,该类已经把所有抽象方法进行了重写.那么,实现Collection接口的所有子类都会进行方法重写. 1.3 List接口List:可存储重复元素,存取

Java泛型学习笔记--Java泛型和C#泛型比较学习(一)

总结Java的泛型前,先简单的介绍下C#的泛型,通过对比,比较学习Java泛型的目的和设计意图.C#泛型是C#语言2.0和通用语言运行时(CLR)同时支持的一个特性(这一点是导致C#泛型和Java泛型区别的最大原因,后面会介绍).C#泛型在.NET CLR支持为.NET框架引入参数化变量支持.C#泛型更类似C++模板,可以理解,C#泛型实际上可以理解为类的模板类.我们通过代码实例来看C# 2.0泛型解决的问题,首先,我们通过一个没有泛型的迭代器的代码示例说起,代码实现如下: interface

玩转迭代器

迭代器概述 迭代器是可以返回相同类型的值的有序序列的一段代码. 迭代器可用作方法.运算符或 get 访问器的代码体. 迭代器代码使用 yield return 语句依次返回每个元素.yield break 将终止迭代.有关更多信息,请参见 yield. 可以在类中实现多个迭代器.每个迭代器都必须像任何类成员一样有唯一的名称,并且可以在 foreach 语句中被客户端代码调用,如下所示:foreach(int x in SampleClass.Iterator2){} 迭代器的返回类型必须为 IE

雷林鹏分享:Lua 迭代器

迭代器(iterator)是一种对象,它能够用来遍历标准模板库容器中的部分或全部元素,每个迭代器对象代表容器中的确定的地址 在Lua中迭代器是一种支持指针类型的结构,它可以遍历集合的每一个元素. 泛型 for 迭代器 泛型 for 在自己内部保存迭代函数,实际上它保存三个值:迭代函数.状态常量.控制变量. 泛型 for 迭代器提供了集合的 key/value 对,语法格式如下: for k, v in pairs(t) do print(k, v) end 上面代码中,k, v为变量列表;pai

Java学习:迭代器简介

迭代器 java.util.Iterator接口:迭代器(对集合进行遍历) 有两个常用的方法 boolean hasNext() 如果仍有元素可以迭代,则返回 true. 判断集合中还有没有下一个元素,有就返回true,没有就返回false. E next() 返回迭代的下一个元素 取出集合中的下一个元素 Iterator迭代器,是一个接口,我们无法直接使用,需要使用Iterator接口的实现类对象,获取实现类的方式比较特殊 Collection接口中有一个方法,叫iterator(),这个方法

Java集合类: Set、List、Map、Queue使用

目录 1. Java集合类基本概念 2. Java集合类架构层次关系 3. Java集合类的应用场景代码 1. Java集合类基本概念 在编程中,常常需要集中存放多个数据.从传统意义上讲,数组是我们的一个很好的选择,前提是我们事先已经明确知道我们将要保存的对象的数量.一旦在数组初始化时指定了这个数组长度,这个数组长度就是不可变的,如果我们需要保存一个可以动态增长的数据(在编译时无法确定具体的数量),java的集合类就是一个很好的设计方案了. 集合类主要负责保存.盛装其他数据,因此集合类也被称为容

.net 发展史

2002年年初 -Visual Studio 2002 & .Net Framework 1.0 2003年春天 -Visual Studio 2003 & .Net Framework 1.1 对移动设备的支持(精简版.NET).对ODBC/Oracle数据库的支持 2005年年底 -Visual Studio 2005 & .Net Framework 2.0 & Sql Server 2005 泛型.迭代器.可空类型.匿名方法.分部类等 2006年年底 -.Net F

STL组件

空间配置器: 隐藏在容器的背后,负责空间的配置与管理 一级空间配置器(malloc_alloc)和二级空间配置器(default_alloc) SGI标准空间配置器std::allocator    ||未考虑效率 SGI特殊空间配置器std::alloc       ||allocate()  construct()  destroy()  deallocate() 二级空间配置器free lists.memory pool 内存处理工具 construct()  destroy()  uni