写在前面

  1. 面向对象编程中,多态算是一种泛化机制。你可以将方法的参数类型设置为基类,那么该方法就可以接受从这个基类中导出的任何类作为参数,这样的方法将会更具有通用性。此外,如果将方法参数声明为接口,将会更加灵活。<br /> Java 增加泛型类型之前,通用程序的设计就是利用继承实现的,例如:ArrayList 类只维护一个 Object 引用的数组,Object 为所有类基类。
  1. public class BeforeGeneric {
  2. /**
  3. * 泛型之前的通用程序设计
  4. */
  5. static class ArrayList{
  6. private Object[] elements=new Object[0];
  7. public Object get(int i){
  8. return elements[i];
  9. }
  10. /**
  11. * 这里的实现,只是为了演示,不具有任何参考价值
  12. * */
  13. public void add(Object o){
  14. int length = elements.length;
  15. Object[] newElements = new Object[length+1];
  16. for(int i=0; i<length; i++){
  17. newElements[i] = elements[i];
  18. }
  19. newElements[length] = o;
  20. elements = newElements;
  21. }
  22. }
  23. public static void main(String[] args) {
  24. ArrayList stringValues = new ArrayList();
  25. //可以向数组中添加任何类型的对象
  26. stringValues.add(1);
  27. //问题1——获取值时必须强制转换
  28. String str = (String) stringValues.get(0);
  29. //问题2——上述强制转型编译时不会出错,而运行时报异常java.lang.ClassCastException
  30. System.out.println(str);
  31. }
  32. }

面临的问题

1、当我们获取一个值的时候,必须进行强制类型转换。
2、当我们插入一个值的时候,无法约束预期的类型。假定我们预想的是利用 stringValues 来存放 String 集合,因为 ArrayList 只是维护一个 Object 引用的数组,我们无法阻止将 Integer 类型(Object 子类)的数据加入 stringValues。然而,当我们使用数据的时候,需要将获取的 Object 对象转换为我们期望的类型(String),如果向集合中添加了非预期的类型(如 Integer),编译时我们不会收到任何的错误提示。但当我们运行程序时却会报异常:

Exception in thread “main” java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String at generic.BeforeGeneric.main(BeforeGeneric.java:24)

这显然不是我们所期望的,如果程序有潜在的错误,我们更期望在编译时被告知错误,而不是在运行时报异常。

什么是泛型

泛型,即“参数化类型”。一提到参数,最熟悉的就是定义方法时有形参,然后调用此方法时传递实参。那么参数化类型怎么理解呢?顾名思义,就是将类型由原来的具体的类型参数化,类似于方法中的变量参数,此时类型也定义成参数形式(可以称之为类型形参),然后在使用/调用时传入具体的类型(类型实参)。

注意:一般在创建对象时,将未知的类型确定具体的类型。当没有指定泛型时,默认类型为 Object 类型。

使用泛型的好处

1、避免了类型强转的麻烦。
2、它提供了编译期的类型安全,确保在泛型类型(通常为泛型集合)上只能使用正确类型的对象,避免了在运行时出现 ClassCastException。

泛型的使用

泛型可以用在类、接口和方法上。

泛型方法

你可以写一个泛型方法,该方法在调用时可以接收不同类型的参数。根据传递给泛型方法的参数类型,编译器适当地处理每一个方法调用。

下面是定义泛型方法的规则:

  1. 所有泛型方法声明都有一个类型参数声明部分(由尖括号分隔),该类型参数声明部分在方法返回类型之前(在下面例子中的 )。
  2. 每一个类型参数声明部分包含一个或多个类型参数,参数间用逗号隔开。一个泛型参数,也被称为一个类型变量,是用于指定一个泛型类型名称的标识符。
  3. 类型参数能被用来声明返回值类型,并且能作为泛型方法得到的实际参数类型的占位符。
  4. 泛型方法体的声明和其他方法一样。注意类型参数只能代表引用型类型,不能是原始类型(像 int、double、char 等)。

定义格式:

  1. 修饰符 <代表泛型的变量> 返回值类型 方法名(参数){}

示例:

  1. public class GenericMethodTest {
  2. // 泛型方法 printArray
  3. public static < E > void printArray( E[] inputArray ) {
  4. // 输出数组元素
  5. for ( E element : inputArray ){
  6. System.out.printf( "%s ", element );
  7. }
  8. System.out.println();
  9. }
  10. public static void main( String args[] ) {
  11. // 创建不同类型数组: Integer, Double 和 Character
  12. Integer[] intArray = { 1, 2, 3, 4, 5 };
  13. Double[] doubleArray = { 1.1, 2.2, 3.3, 4.4 };
  14. Character[] charArray = { 'H', 'E', 'L', 'L', 'O' };
  15. System.out.println( "整型数组元素为:" );
  16. printArray( intArray ); // 传递一个整型数组
  17. System.out.println( "\n双精度型数组元素为:" );
  18. printArray( doubleArray ); // 传递一个双精度型数组
  19. System.out.println( "\n字符型数组元素为:" );
  20. printArray( charArray ); // 传递一个字符型数组
  21. }
  22. }

输出如下所示:

  1. 整型数组元素为:
  2. 1 2 3 4 5
  3. 双精度型数组元素为:
  4. 1.1 2.2 3.3 4.4
  5. 字符型数组元素为:
  6. H E L L O

泛型类

  1. 泛型类的声明和非泛型类的声明类似,除了在类名后面添加了类型参数声明部分。<br /> 和泛型方法一样,泛型类的类型参数声明部分也包含一个或多个类型参数,参数间用逗号隔开。一个泛型参数,也被称为一个类型变量,是用于指定一个泛型类型名称的标识符。因为他们接受一个或多个参数,这些类被称为参数化的类或参数化的类型。

定义格式:

  1. public class 类名 <泛型类型> {
  2. }

示例:

  1. public class Box<T> {
  2. private T t;
  3. public void add(T t) {
  4. this.t = t;
  5. }
  6. public T get() {
  7. return t;
  8. }
  9. public static void main(String[] args) {
  10. Box<Integer> integerBox = new Box<Integer>();
  11. Box<String> stringBox = new Box<String>();
  12. integerBox.add(new Integer(10));
  13. stringBox.add(new String("菜鸟教程"));
  14. System.out.printf("整型值为 :%d\n\n", integerBox.get());
  15. System.out.printf("字符串为 :%s\n", stringBox.get());
  16. }
  17. }

输出如下所示:

  1. 整型值为 :10
  2. 字符串为 :菜鸟教程

泛型 K、T、V、E、N、? 的含义

如果你点开 JDK 中一些泛型类的源码,我们会看到下面这些代码:

  1. public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable {
  2. ...
  3. }
  4. public class HashMap<K,V> extends AbstractMap<K,V> implements Map<K,V>, Cloneable, Serializable {
  5. ...
  6. }

上面这些泛型类定义中的泛型参数 E、K 和 V 都是什么意思呢?其实这些参数名称是可以任意指定,就想方法的参数名一样可以任意指定,但是我们通常会起一个有意义的名称,让别人一看就知道是什么意思。泛型参数也一样,E 一般是指元素,用来集合类中。

常见泛型参数名称有如下:

T Type 标识类型。
K,V 代表键值对中的 key 和 value。
E 代表 Element 通常在集合中使用。
N 代表 Number 数字。
? 无限通配符,表示的是未知类型,表示不关心或者不能确定实际操作的类型,一般配合容器类使用。

? 和 Object 的区别?

? 表示未知类型。Object 表示任意类型。 Object 在使用时需要强制转换为想要的类型。编译器只有在运行时才知道类型转换是否出现异常。

边界

当我们不指定或者不关心操作类型,但是又想进行一定范围限制的时候,我们可以通过添加上限或下限来起到限制作用。

上边界<? extends T>

  • 定义了上限,只有读的能力。此方式表示参数化的类型可能是所指定的类型,或者是此类型的子类。如果传入的类型不是 T 或者 T 的子类,编辑不成功。
  • 用于灵活读取,使得方法可以读取T 或 T 的任意子类型的容器对象。

示例:

  1. public class Test {
  2. public static void printIntValue(List<? extends Number> list) {
  3. for (Number number : list) {
  4. System.out.print(number.intValue()+" ");
  5. }
  6. System.out.println();
  7. }
  8. public static void main(String[] args) {
  9. List<Integer> integerList=new ArrayList<Integer>();
  10. integerList.add(2);
  11. integerList.add(2);
  12. printIntValue(integerList);
  13. List<Float> floatList=new ArrayList<Float>();
  14. floatList.add((float) 3.3);
  15. floatList.add((float) 0.3);
  16. printIntValue(floatList);
  17. }
  18. }

下边界<? super T>

  • 定义了下限,有读的能力以及部分写的能力,子类可以写入父类。此方式表示参数化的类型可能是指定的类型,或者是此类型的父类。如果传入的类型不是 T 或者 T 的父类,编辑不成功。
  • 用于灵活写入或比较,使得对象可以写入父类型的容器,使得父类型的比较方法可以应用于子类对象。

示例:

  1. public class Test {
  2. public static void fillNumberList(List<? super Number> list) {
  3. list.add(new Integer(0));
  4. list.add(new Float(1.0));
  5. }
  6. public static void main(String[] args) {
  7. List<? super Number> list=new ArrayList();
  8. list.add(new Integer(1));
  9. list.add(new Float(1.1));
  10. }
  11. }

注意

  • 要从泛型类取数据时,用 ? extends 通配符。
  • 要往泛型类写数据时,用 ? super 通配符。
  • 既要取又要写,就不用通配符(即 extends 与 super 都不用)。
  • 不能同时声明泛型通配符上界和下界。

泛型擦除

泛型值存在于编译期,代码在进入虚拟机后泛型就会被擦除掉,这个特性叫做类型擦除。

  1. 如果泛型没有设置类型上限,那么将泛型转化成Object类型。
  2. 如果设置了类型上限,那么将泛型转化成他的类型上限。
  3. 如果有多个类型上限。使用第一个作为原始类型(T extends Demo1 & Demo2)原始类型为Demo1。

最后

这篇文章就到这里了,如果这篇文章对你也有所帮助,就点个赞再走呗!!!