概述

在复杂的项目中,bugs总是相伴其一生。通过详细的计划,编码和测试可以减少bug的存在,但是,他们还是会以某种形式,在某个地方溜进你的程序中,而且随着项目越来越复杂和庞大,这种情况就越明显。

好在,一些bug更容易检测出来,这就是编译时bug。如果在编译时就会报错,这时你可以根据报错信息找到代码到位置并修复。但运行时bug,就比较麻烦。因为运行时bug不会立即出问题,而是等程序上线跑起来之后才出问题,而且往往出问题的地方和造成bug的地方相去甚远。

所以,说白了,泛型就是要给你的代码增加健壮性,尽可能在编译时就可以检测出来一些bug来。

为什么用泛型

简单说,泛型就是定义类时,让类型变成参数。

类型参数让你可以重复利用相同的代码,但可以有不同的输入类型。

与普通方法中的参数不同的是,方法中的是输入是变量的值,这里的输入是参数是类型。

使用泛型的代码可以带来以下的好处

  • 强类型检查

编译器会做强类型检查,修复编译错误可比修复运行时错误要更容易。

  • 省去类型转换

下面没用泛型的代码,需要显示的类型转换

List list = new ArrayList();list.add("hello");String s = (String) list.get(0);

而使用泛型重写后,就不需要自己类型转换(cast)了

List list = new ArrayList();list.add("hello");String s = list.get(0);   // no cast
  • 可以写出泛型的算法

例如Java集合框架,在使用泛型后,集合就可以应用在不同的类型上了。

如何定义泛型类型

泛型类型generic type就是一个泛型的类或接口,通过类型参数化了。

下面用Box类来说明这个概念。

简单的box类

Box是一个类,为了可以操作任何类型的对象,所用的属性的类型是Object。

public class Box {    private Object object;    public void set(Object object) { this.object = object; }    public Object get() { return object; }}

由于它的方法接受和返回的是Object对象,你可以想传什么就传什么,传原始类型都可以。

这样就没有办法在编译阶段做检查。

这时有可能set了一个Integer,而get时以为是一个String,出现运行时错误。

Box box = new Box();        box.setObject(123);                Object object =  (String) box.getObject();// Exception in thread "main" java.lang.ClassCastException: // java.lang.Integer cannot be cast to java.lang.String        System.out.println(object);

泛型版本的Box类

下面祭出泛型版本的Box类,看是如何解决这个问题的。

泛型类定义的形式如下

class name { /* ... */ }

类型参数的部分跟在类名后面,用尖括号括起来,这表示类型参数(也叫类型变量)就是T1,... Tn,类型变量可以在类中的任何地方使用。

下面是用泛型重写后的Box类

public class BoxT {    // T 代表 Type的意思    T object;    public T getObject() {        return object;    }    public void setObject(T object) {        this.object = object;    }}

可以看到,以前Object出现的地方被T所代替了。 类型变量可以是任何的非原始类型,包括任何类,任何接口,任何数组,甚至是另一个类型变量。

这里演示一下使用泛型后和上面的区别

public static void main(String[] args) {        BoxT b = new BoxT();        b.setObject(123);   // 变化一,由于实例化时,已经指定了 类型的实际参数是Integer,                            // 因此这里就只能set一个Integer,不能传入其他的类型,                            // 编译器会帮助检查错误        Integer integer = b.getObject();  // 变化二,这里不需要强转了,因为编译器能判断这里类型就是Integer。        System.out.println(integer);    }

类型参数名的习惯叫法

通常类型参数名是单个的大写字母,这样做的原因就是为了区分类型变量和普通的类名和接口名。

常用的参数名有

  • E - Element (used extensively by the Java Collections Framework) 广泛的用于集合框架。
  • K - Key
  • N - Number
  • T - Type
  • V - Value
  • S,U,V etc. - 2nd, 3rd, 4th types

如何实例化一个泛型类

需要将类定义中的T替换成一个具体类型,如Integer

Box integerBox;

就像是传了一个参数一样。

一般在术语上,type parameter是类中定义的形式参数,type argument是实例化时传的实际参数。

Box也叫参数化的类型。

实例化的代码

Box integerBox = new Box();

钻石操作符 Diamond




java object 改成list java中object转list_java object 改成list


JDK7以后,只要编译器可以根据上下文来推断出类型参数,在初始化时,就不必要将类型传给构造函数,使用空的尖括号就可以,这种尖括号也称为“钻石操作符”,如此,上面的实例化代码可以写成下面这样。

Box integerBox = new Box<>();

在new后面的类上,就不用指定T的类型了,但是<>还是保留着,这个非常必要,如果<>也去掉的话,就会变成下面要讲到的raw类型了。

多个类型参数

上面的类中只有一个类型参数,其实泛型类型可以有多个类型参数,如下面的代码

public class OrderedPair implements Pair {    private K key;    private V value;    public OrderedPair(K key, V value) {        this.key = key;        this.value = value;    }    @Override    public K getKey() {        return key;    }    @Override    public V getValue() {        return value;    }}

下面实例化了两个OrderedPair类。

Pair p1 = new OrderedPair("Even", 8);Pair  p2 = new OrderedPair("hello", "world");

同样由于编译器可以推断类型,同样可以使用钻石操作符,上面的实例化可简化为:

Pair p1 = new OrderedPair<>("Even", 8);Pair  p2 = new OrderedPair<>("hello", "world");

参数化的类型

泛型类的类型参数本身可以是另一个参数化的泛型类型,如

OrderedPair> p = new OrderedPair<>("primes", new Box());

泛型的raw类型

像上面说的,如果在实例化时,丢掉了<>,不向泛型类传递实际的类型会怎么样呢?

如果对于泛型的类,没有提供类型的实际参数,这就是泛型类的raw类型(raw type),使用raw类型,本质上退化成了泛型出现之前的代码,也就是Object的情况。

例如

Box rawBox = new Box();List box = new ArrayList();

注意,普通的类,即非泛型的类不是raw类型。

raw type 返回的是Object,这里为了向前兼容,将参数化的类型赋值给 raw 类型是合法的。

Box stringBox = new Box<>();Box rawBox = stringBox;               // OK

但如果将一个raw类型,赋值给一个参数化的类型,将会收到一个警告。

List box = new ArrayList();   // warning: unchecked conversion

另外 如果使用raw type调用泛型方法,也会得到一个警告。

List box = new ArrayList();        box.add("1"); warning: unchecked invocation to add(T)

Unchecked Error Messages

那这些警告是什么意思呢?还记得编译器可以通过泛型来做强类型检查吗?

但当使用了raw type时,编译器没有足够的信息来做类型检查,所以就会报一个unchecked 的警告。

所以尽量不要使用raw type。

本文就介绍到这里,下一篇Java中的泛型generics从0到1(中)的内容包括 泛型的方法,有界的类型参数,泛型通配符,有上界的通配符,类型擦除等。

本文为《Java中的泛型系列》第一篇,其他还有包括

Java中的泛型generics从0到1(上)

Java中的泛型generics从0到1(中)

Java中的泛型generics从0到1(下)

Java中的泛型generics从0到1(甲)

Java中的泛型generics从0到1(乙)

Java中的泛型generics从0到1(丙)

Java中的泛型generics从0到1(丁)


java object 改成list java中object转list_Pair_02