Java泛型Generic

Java泛型Generic


1. 引言

  • 泛型是指参数化类型的能力, Java可以定义带泛型的类, 泛型借口或泛型方法,随后编译器会在编译器用具体的类型来替换.
  • 泛型主要的优点是能够在编译器而非运行时检测出错误.
  • 本文将介绍定义和使用泛型类, 泛型接口, 泛型方法.

2.动机和优点

从JDK1.5开始, Java允许使用泛型类, 泛型接口, 泛型方法, 并且Java API中的很多接口和类都做了修改, 如java.lang.Comparable接口使用了泛型类重新定义了

// JDK1.5之前版本的Compareble接口
package java.lang;
public interface Comparable{
    public int compareTo(Object o);
}
// JDK1.5版本的Compareble接口
package java.lang;
public interface Comparable<T>{
    public int compareTo(T o);
}

这里的表示形式泛型类型(formal gen eric type), 编译器会用实际具体类型(actual concrete type)来替换. 这个过程是泛型实例化(generic instantiation)

创建字符串的线性表
ArrayList<String> list = new ArrayList<String>
list.add("Red");

如果试图向其中添加非字符串对象, 会产生编译错误, 如
list.add(new Integer(1)); //编译报错

泛型类型必须是引用类型, 不能使int , double 或char等基本类型来替换泛型类型
ArrayList<int> intList = new ArrayList<int>(); //编译报错

此时必须使用
ArrayList<Integer> intList = new ArrayList<Integer>();
intList.add(5); //自动将5装箱为new Integer(5)


ArrayList<Double> list = new ArrayList<Double>();
list.add(3.5); // 5.5自动装箱为 new Double(5.5);
list.add(3.0); // 5.5自动装箱为 new Double(3.0);
Double doubleObject = list.get(0);
double d = list.get(1); // 自动拆箱转换为基本类型


3. 泛型类和接口

泛型栈类的实现

package wz.list01;

import java.util.ArrayList;

public class GenericStack<E> {
    private ArrayList<E> list = new ArrayList<E>();

    public int getSize() {
        return list.size();
    }

    public E peek() {
        return list.get(list.size() - 1);
    }

    public void push(E elem) {
        list.add(elem);
    }

    public E pop() {
        E elem = list.get(list.size() - 1);
        list.remove(list.size() - 1);
        return elem;
    }

    public boolean isEmpty() {
        return list.size() == 0;
    }
}

测试代码

GenericStack<String> stack = new GenericStack<>();
stack.push("London");
stack.push("Paris");
stack.push("Berlin");

GenericStack<Integer> stack2 = new GenericStack<>();
stack2.push(3);
stack2.push(2);
stack2.push(1);

为了创建一个字符串堆栈, 可以使用 new GenericStack<String>().
但是 GenericStack 的构造方法应该是 public GenericStack()
而不是 public GenericStack<E>()

可以定义一个类或接口继承或实现泛型的类, 接口.
Java API 中 java.lang.String 类被定义为实现 Comparable 接口, 如下所示
public final String extends Object implements Serializable, Comparable<String>, CharSequence


4. 泛型方法

程序定义了一个泛型方法 print 来打印对象数组

package wz.list02;

public class GenericMethodDemo {
    public static void main(String[] args) {
        Integer[] integers = { 1, 2, 3, 4, 5 };
        String[] strings = { "London", "Paris", "Austin", "New York" };
        GenericMethodDemo.<Integer>print(integers);
        GenericMethodDemo.<String>print(strings);
    }

    public static <E> void print(E[] list) {
        for (int i = 0; i < list.length; i++) {
            System.out.print(list[i] + "  ");
        }
        System.out.println();
    }
}

为了调用泛型方法, 需要将实际类型放在尖括号内作为方法名的前缀.
GenericMethodDemo.<Integer>print(integers);
GenericMethodDemo.<String>print(strings);

为了定义一个类为泛型类型, 需要将泛型类型放在类名之后, 例如 GenericStack<E>
而为了定义泛型方法,需要将泛型类型放在方法返回类型之前, 例如 <E> void max(E o1, E o2)


5. 原始类型和向后兼容

可以使用泛型类而无需指定具体类型, 如下所示:
GenericStack stack = new GenericStack(); // raw type

这大体上等价于下面语句
GenericStack<Object> stack = new GenericStack<Object>();

像 GenericStack 和 ArrayList 这样不使用类型参数的泛型类称为原始类型.

在Java1.5开始, 允许使用原始类型向后兼容. 虽然API中很多类接口都改为了反省类型, 但仍可以使用原始类型, 如可以使用原始类型Comparable接口而不是必须加上类型参数. 编译器会在编译时发出warnings, 使用原始类型在执行某些操作是不安全的.


6. 通配泛型

  1. < ?> 称为非受限通配, 和 < ? extends Object> 相同

  2. < ? extends T >称为受限通配, 表示T或T的一个未知子类型

  3. < ? super T >称为下限通配, 表示T或T的一个未知父类型


7. 泛型擦除

Java中的泛型是使用类型擦除(type erasure)的方法实现的, 这和C++中的泛型机制不同. 编译器使用泛型类型信息来编译代码, 但在编译期间会擦除掉, 因此泛型信息在运行时是不可用的

泛型存在于编译时, 一旦编译器确认泛型类型可以安全使用, 就会将它转换为原始类型, 例如:

ArrayList<String> list = new ArrayList<String>();
list.add("OK");
String state = list.get(0);

会转换的等价代码

ArrayList list = new ArrayList();
list.add("OK");
String state = (String)(list.get(0));

编译期间会用Object类型代替泛型类型
如果泛型类型是受限的, 那么编译器会用受限的类型来替换, 如< E extends GenericStack>会将代码中的E类型替换为 GenericStack

不管实际类型是什么, 泛型类是被所有的实力共享的
如按定以下方式创建list1 和 list2:

ArrayList<String> list1 = new ArrayList<String>();
ArrayList<Integer> list2 = new ArrayList<Integer>();

尽管编译时 ArrayList 和 ArrayList 是两种不同的类型, 但是运行时只有一个 ArrayList类被加载到JVM中, list1 和 list2都是 ArrayList 的实例

list1 instanceof ArrayList == true
list2 instanceof ArrayList == true

但是表达式 list1 instanceof ArrayList<String> 是错误的.
编译期间所有的泛型信息都已经被擦除了, 不存在 ArrayList类


8. 泛型的使用限制

由于泛型类型信息在运行时是不存在的, 因此对使用泛型类型是存在一些限制的

限制1 : 不能使用 new E()

不能使用泛型参数类型创建实例, 如 下列语句是会编译报错的:
E object = new E()
出错的原因是运行时执行的是new E(), 但是运行时泛型类型是不可用的


限制2: 不能使用 new E[]

不能使用泛型参数类型创建数组 如下列代码是错误的:
E[] elems = new E[capacity]

可以通过创建Object类型数组来转换为E[]避免次规则:
E[] elems = (E[])new Object[capacity];
但是类型转换到(E[])会产生编译警告, 可能存在不安全的行为

不允许使用泛型类创建泛型类的组, 如下列代码是错误的:
ArrayList<String>[] lists = new ArrayList<String>[10];
可以通过
ArrayList<String>[] lists = (ArrayList<String>[])new ArrayList[10];
来避免这个错误, 但会产生编译警告


限制3: 在静态环境下不允许类的参数是泛型类型

如下面代码是非法的:

public class Test<E>{
    public static void m(E o1){ // Illegal
    }

    public static E o1; // Illegal

    public {
        E 02; // Illegal
    }
}

限制4: 异常类不能是泛型的

泛型类不能扩展 java.lang.Throwable
因为异常中匹配catch语句块时需要出现具体的类型信息,
而运行时泛型类型信息是不存在的

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值