Java泛型

你可以将方法的参数类型设为基类,那么该方法就可以接受从这个基类中导出的任何类作为参数。
如果方法的参数是一个接口,那么该方法就可以接受任何实现了该接口的类作为参数。
泛型实现了参数化类型的概念,使代码可以应用于多种类型。
在你创建参数化类型的一个实例时,编译器会为你负责转型操作,并且保证类型的正确性。

推荐阅读:Java 泛型,你了解类型擦除吗?

1.泛型类

我们可以这样定义一个泛型类。

public class Test<T> {
    T field1;
}

尖括号 <> 中的 T 被称作是类型参数,用于指代任何类型。事实上,T 只是一种习惯性写法,如果你愿意。你可以这样写。

public class Test<Hello> {
    Hello field1;
}

但出于规范的目的,Java 还是建议我们用单个大写字母来代表类型参数。常见的如:

  1. T 代表一般的任何类。
  2. E 代表 Element 的意思,或者 Exception 异常的意思。
  3. K 代表 Key 的意思。
  4. V 代表 Value 的意思,通常与 K 一起配合使用。
  5. S 代表 Subtype 的意思,文章后面部分会讲解示意。

如果一个类被 <T> 的形式定义,那么它就被称为是泛型类。
当然,泛型类不至接受一个类型参数,它还可以这样接受多个类型参数。

public class MultiType <E,T>{
    E value1;
    T value2;

    public E getValue1(){
        return value1;
    }

    public T getValue2(){
        return value2;
    }
}

2.泛型方法

public class Test1 {

    public <T> void testMethod(T t){

    }
}

泛型方法与泛型类稍有不同的地方是,类型参数也就是尖括号那一部分是写在返回值前面的。 中的 T 被称为类型参数,而方法中的 T 被称为参数化类型,它不是运行时真正的参数。

当然,声明的类型参数,其实也是可以当作返回值的类型的。

public  <T> T testMethod1(T t){
        return null;
}

2.1 泛型类与泛型方法的共存现象

public class Test1<T>{

    public  void testMethod(T t){
        System.out.println(t.getClass().getName());
    }
    public  <T> T testMethod1(T t){
        return t;
    }
}

上面代码中,Test1<T> 是泛型类,testMethod 是泛型类中的普通方法,而 testMethod1 是一个泛型方法。而泛型类中的类型参数与泛型方法中的类型参数是没有相应的联系的,泛型方法始终以自己定义的类型参数为准

所以,针对上面的代码,我们可以这样编写测试代码。

Test1<String> t = new Test1();
t.testMethod("generic");
Integer i = t.testMethod1(new Integer(1));

泛型类的实际类型参数是 String,而传递给泛型方法的类型参数是 Integer,两者不相干。

但是,为了避免混淆,如果在一个泛型类中存在泛型方法,那么两者的类型参数最好不要同名。比如,Test1<T> 代码可以更改为这样

public class Test1<T>{

    public  void testMethod(T t){
        System.out.println(t.getClass().getName());
    }
    public  <E> E testMethod1(E e){
        return e;
    }
}

3.泛型接口

泛型接口和泛型类差不多,所以一笔带过。

public interface Iterable<T> {}

4.通配符

除了用 <T> 表示泛型外,还有 <?> 这种形式。被称为通配符。

可能有同学会想,已经有了 <T> 的形式了,为什么还要引进<?>这样的概念呢?

class Base{}

class Sub extends Base{}

Sub sub = new Sub();
Base base = sub;    

上面代码显示,Base 是 Sub 的父类,它们之间是继承关系,所以 Sub 的实例可以给一个 Base 引用赋值,那么

List<Sub> lsub = new ArrayList<>();
List<Base> lbase = lsub;

最后一行代码成立吗?编译会通过吗?

答案是否定的。

编译器不会让它通过的。Sub 是 Base 的子类,不代表 List<Sub>List<Base> 有继承关系。

但是,在现实编码中,确实有这样的需求,希望泛型能够处理某一范围内的数据类型,比如某个类和它的子类,对此 Java 引入了通配符这个概念。

所以,通配符的出现是为了指定泛型中的类型范围

通配符有 3 种形式。

  1. <?> 被称作无限定的通配符。
  2. <? extends T> 被称作有上限的通配符。
  3. <? super T> 被称作有下限的通配符。

5.类型擦除

泛型是 Java 1.5 版本才引进的概念,在这之前是没有泛型的概念的,但显然,泛型代码能够很好地和之前版本的代码很好地兼容。

这是因为,泛型信息只存在于代码编译阶段,在进入 JVM 之前,与泛型相关的信息会被擦除掉,专业术语叫做类型擦除

通俗地讲,泛型类和普通类在 java 虚拟机内是没有什么特别的地方。

List<String> l1 = new ArrayList<String>();
List<Integer> l2 = new ArrayList<Integer>();

System.out.println(l1.getClass() == l2.getClass());

打印的结果为 true 是因为List<String>List<Integer> 在 jvm 中的 Class 都是 List.class。

泛型信息被擦除了。

在泛型类被类型擦除的时候,之前泛型类中的类型参数部分如果没有指定上限,如 <T> 则会被转译成普通的 Object 类型,如果指定了上限如 <T extends String> 则类型参数就被替换成类型上限。

6.泛型中值得注意的地方

6.1 泛型类或者泛型方法中,不接受 8 种基本数据类型。

所以,你没有办法进行这样的编码。

List<int> li = new ArrayList<>();
List<boolean> li = new ArrayList<>();

需要使用它们对应的包装类。

List<Integer> li = new ArrayList<>();
List<Boolean> li1 = new ArrayList<>();

6.2 对泛型方法的困惑

public <T> T test(T t){
    return null;
}

有的同学可能对于连续的两个 T 感到困惑,其实 <T> 是为了说明类型参数,是声明,而后面的不带尖括号的 T 是方法的返回值类型。

posted @ 2018-05-05 21:52  gzhjj  阅读(255)  评论(0编辑  收藏  举报