Bootstrap

第5关:泛型

任务描述

本关任务:使用泛型创建一个储存元素类型为String的ArrayList,完成:

1.添加String类型内容为educode的元素

2.遍历输出ArrayList

相关知识

1.泛型概述

泛型是提供给javac编译器使用的,可以限定集合中的输入类型,让编译器挡住源程序中的非法输入,编译器编译带类型说明的集合时会去除掉“类型”信息,使程序运行效率不受影响,对于参数化的泛型类型,getClass()方法的返回值和原始类型完全一样。由于编译生成的字节码会去掉泛型的类型信息,只要能跳过编译器,就可以往某个泛型集合中加入其它类型的数据,例如,用反射得到集合,再调用其add方法即可。

泛型引用和创建两端,给出的泛型变量必须相同。

泛型(Generic type 或者 generics)是对 Java 语言的类型系统的一种扩展,以支持创建可以按类型进行参数化的类。可以把类型参数看作是使用参数化类型时指定的类型的一个占位符,就像方法的形式参数是运行时传递的值的占位符一样。

泛型是Java SE 1.5的新特性,泛型的本质是参数化类型,也就是说所操作的数据类型被指定为一个参数。这种参数类型可以用在类、接口和方法的创建中,分别称为泛型类、泛型接口、泛型方法。 Java语言引入泛型的好处是安全简单。

在Java SE 1.5之前,没有泛型的情况的下,通过对类型Object的引用来实现参数的“任意化”,“任意化”带来的缺点是要做显式的强制类型转换,而这种转换是要求开发者对实际参数类型可以预知的情况下进行的。对于强制类型转换错误的情况,编译器可能不提示错误,在运行的时候才出现异常,这是一个安全隐患。

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

可以在集合框架(Collection framework)中看到泛型的动机。例如,Map 类允许您向一个 Map添加任意类的对象,即使最常见的情况是在给定映射(map)中保存某个特定类型(比如 String)的对象。

因为 Map.get() 被定义为返回 Object,所以一般必须将 Map.get() 的结果强制类型转换为期望的类型,如下面的代码所示:

 
  1. Map m = new HashMap();
  2. m.put("key", "blarg");
  3. String s = (String) m.get("key");

要让程序通过编译,必须将 get() 的结果强制类型转换为 String,并且希望结果真的是一个 String。但是有可能某人已经在该映射中保存了不是 String 的东西,这样的话,上面的代码将会抛出 ClassCastException。

理想情况下,您可能会得出这样一个观点,即 m 是一个 Map,它将 String 键映射到 String 值。这可以让您消除代码中的强制类型转换,同时获得一个附加的类型检查层,该检查层可以防止有人将错误类型的键或值保存在集合中。这就是泛型所做的工作。

泛型:是一种把类型明确的工作推迟到创建对象或者调用方法的时候才去明确的特殊的类型。参数化类型,把类型当作参数一样的传递。

 
  1. package cn.itcast_01;
  2. import java.util.ArrayList;
  3. import java.util.Iterator;
  4. /*
  5. * ArrayList存储字符串并遍历
  6. *
  7. * 我们按照正常的写法来写这个程序, 结果确出错了。
  8. * 为什么呢?
  9. * 因为我们开始存储的时候,存储了String和Integer两种类型的数据。
  10. * 而在遍历的时候,我们把它们都当作String类型处理的,做了转换,所以就报错了。
  11. * 但是呢,它在编译期间却没有告诉我们。
  12. * 所以,我就觉得这个设计的不好。
  13. * 回想一下,我们的数组
  14. * String[] strArray = new String[3];
  15. * strArray[0] = "hello";
  16. * strArray[1] = "world";
  17. * strArray[2] = 10;
  18. * 集合也模仿着数组的这种做法,在创建对象的时候明确元素的数据类型。这样就不会在有问题了。
  19. * 而这种技术被称为:泛型。
  20. *
  21. * 泛型:是一种把类型明确的工作推迟到创建对象或者调用方法的时候才去明确的特殊的类型。参数化类型,把类型当作参数一样的传递。
  22. * 格式:
  23. * <数据类型>
  24. * 此处的数据类型只能是引用类型。
  25. * 好处:
  26. * A:把运行时期的问题提前到了编译期间
  27. * B:避免了强制类型转换
  28. * C:优化了程序设计,解决了黄色警告线
  29. */
  30. public class GenericDemo {
  31. public static void main(String[] args) {
  32. // 创建
  33. ArrayList<String> array = new ArrayList<String>();
  34. // 添加元素
  35. array.add("hello");
  36. array.add("world");
  37. array.add("java");
  38. // array.add(new Integer(100));
  39. //array.add(10); // JDK5以后的自动装箱
  40. // 等价于:array.add(Integer.valueOf(10));
  41. // 遍历
  42. Iterator<String> it = array.iterator();
  43. while (it.hasNext()) {
  44. // ClassCastException
  45. // String s = (String) it.next();
  46. String s = it.next();
  47. System.out.println(s);
  48. }
  49. // 看下面这个代码
  50. // String[] strArray = new String[3];
  51. // strArray[0] = "hello";
  52. // strArray[1] = "world";
  53. // strArray[2] = 10;
  54. }
  55. }

2. 泛型的好处

简言之,泛型能够使类型(类和接口)在定义类,接口和方法的时候参数化。非常像方法定义时用到的形式参数(formal parameters),类型参数提供了一种你可以通过不同的输入来复用同一段代码的方法。不同点是,形式参数输入的是,而类型参数输入的是类型

Java 语言中引入泛型是一个较大的功能增强。不仅语言、类型系统和编译器有了较大的变化,以支持泛型,而且类库也进行了大翻修,所以许多重要的类,比如集合框架,都已经成为泛型化的了。这带来了很多好处:

  • 编译时更强大的类型检测
  • 消除类型转换(Elimination of casts)
  • 使开发者实现泛型算法

2.1 类型安全

泛型的主要目标是提高 Java 程序的类型安全。通过知道使用泛型定义的变量的类型限制,编译器可以在一个高得多的程度上验证类型假设。没有泛型,这些假设就只存在于程序员的头脑中(或者如果幸运的话,还存在于代码注释中)。

Java 程序中的一种流行技术是定义这样的集合,即它的元素或键是公共类型的,比如“String 列表”或者“String 到 String 的映射”。通过在变量声明中捕获这一附加的类型信息,泛型允许编译器实施这些附加的类型约束。类型错误现在就可以在编译时被捕获了,而不是在运行时当作 ClassCastException 展示出来。将类型检查从运行时挪到编译时有助于您更容易找到错误,并可提高程序的可靠性。

2.2 消除强制类型转换

泛型的一个附带好处是,消除源代码中的许多强制类型转换。这使得代码更加可读,并且减少了出错机会。 尽管减少强制类型转换可以降低使用泛型类的代码的罗嗦程度,但是声明泛型变量会带来相应的罗嗦。

2.3 优化了程序设计,解决了黄色警告线

3 泛型的内部原理

泛型是提供给javac编译器使用的,可以限定集合中的输入类型,让编译器挡住源程序中的非法输入。但是,编译器编译带类型说明的集合时会去除掉“类型”信息,目的就是使程序运行效率不受影响。因此,对于参数化的泛型类型,getClass()方法的返回值和原始类型完全一样。

 
  1. package com.itheima.day2;
  2. import java.util.ArrayList;
  3. public class GenericTest {
  4. public static void main(String[] args) {
  5. ArrayList<String> collection1 = new ArrayList<String>();
  6. ArrayList collection2 = new ArrayList();
  7. System. out.println(collection1.getClass() == collection2.getClass());
  8. //结果:true
  9. }
  10. }

由于编译生成的字节码会去掉泛型的类型信息,只要能跳过编译器,就可以往某个泛型集合中加入其它类型的数据,例如,用反射得到集合,再调用其add方法即可。

 
  1. package com.itheima.day2;
  2. import java.util.ArrayList;
  3. public class GenericTest {
  4. public static void main(String[] args) throws Exception {
  5. ArrayList<Integer> collection1 = new ArrayList<Integer>();
  6. collection1.getClass().getMethod( "add",Object.class).invoke(collection1, "abc");
  7. System. out.println(collection1.get(0));
  8. }
  9. }

ArrayList类定义和ArrayList类引用中涉及如下术语:

  • 整个称为ArrayList泛型类型
  • ArrayList中的E称为类型变量或类型参数
  • 整个ArrayList称为参数化的类型
  • ArrayList中的Integer称为类型参数的实例或实际类型参数
  • ArrayList中的<>念着typeof
  • ArrayList称为原始类型

参数化类型与原始类型的兼容性:参数化类型可以引用一个原始类型的对象,编译报告警告,例如

 
  1. Collection<String> c = new Vector();//考虑到对以前代码的兼容性,编译器是可以通过的

原始类型可以引用一个参数化类型的对象,编译报告警告,例如

 
  1. Collection c = new Vector<String>();//原来的方法接受一个集合参数,新的类型也要能传进去

参数化类型不考虑类型参数的继承关系:

 
  1. Vector<String> v = new Vector<Object>(); //错误!不写<Object>没错,写了就是明知故犯
  2. Vector<Object> v = new Vector<String>(); //也错误!

注意:

假设Vector v = new Vector();可以的话,那么以后从v中取出的对象当作String用,而v实际指向的对象中可以加入任意的类型对象;

假设Vectorv = new Vector();可以的话,那么以后可以向v中加入任意的类型对象,而v实际指向的集合中只能装String类型的对象。

编译器不允许创建泛型变量的数组。即在创建数组实例时,数组的元素不能使用参数化的类型。

例如,下面语句有错误:

 
  1. Vector<Integer> vectorList[] = new Vector<Integer>[10];

思考题:

下面的代码会报错误吗?

 
  1. Vector v1 = new Vector<String>();
  2. Vector<Object> v = v1;

答案:编译的时候是不会报错的,因为编译器是一行一行按照语法检查代码的,因此不会出错。

编程要求

根据提示,在右侧编辑器补充代码,完成一下任务:

1.添加String类型内容为educode的元素

2.遍历输出ArrayList

测试说明

补充完代码后,点击测评,平台会对你编写的代码进行测试,你的结果与预期输出一致时,即为通过。

测试输入:

张三

李四

王五

预期输出:

第1个元素为:张三

第2个元素为:李四

第3个元素为:王五

第4个元素为:educode


开始你的任务吧,祝你成功!

package step5;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.Scanner;

public class Genericity {


		public static void main(String[] args) {

			// ---------------------Begin------------------------

			Scanner scanner = new Scanner(System.in);
			ArrayList<String> stringArrayList = new ArrayList<>();
			while (scanner.hasNext()) {
				stringArrayList.add(scanner.next());
			}
			stringArrayList.add("educode");
			Iterator<String> iterator = stringArrayList.iterator();
			int i = 1;
			while (iterator.hasNext()) {
				System.out.println(String.format("第%d个元素为:%s", i, iterator.next()));
				i++;
			}
			
			// ---------------------End------------------------
			
		}
}

;