Java泛型详解

1、什么是泛型

泛型的本质是参数化类型,也就是说所操作的数据类型被指定为一个参数

比如常见的ArrayList:

public class ArrayList<E> extends AbstractList<E>

这个就是泛型,可以表示任何对象类型(不可以是基本数据类型),具体表示哪种类型,是在实例化ArrayList时决定的:

// 此时E为Integer类型,表示该ArrayList中只能存放Integer类型的数据
new ArrayList<Integer>
// 此时E为String类型,表示该ArrayList中只能存放String类型的数据
new ArrayList<String>

2、为什么要有泛型

泛型是在JDK1.5版本中加入的,泛型的出现就是为了创建容器类时,使用泛型来保存指定类型的元素

一个被举了无数次的例子

List list = new ArrayList();
list.add(123);
list.add("HelloWorld");
for (Object o : list) {
    String s = (String) o;
    System.out.println(s);
}

第5行代码在编译时不会报错,但是在运行时报错:

java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String

实例化ArrayList时,并没有指定泛型,那么这个ArrayList实例中可以保存的数据类型就是Object,也就是无论哪种对象类型,都可以被添加到这个ArrayList中,当从ArrayList中取出元素使用的时候,就必须要进行强制类型转换,这个转换就很可能出现类型转换异常,即上述示例:java.lang.Integer cannot be cast to java.lang.String。

使用Object来实现通用的处理,会有两个缺点:

  1. 使用时要强制类型转换成想要的类型
  2. 编译阶段编译器不会到类型转换是否正常,只有在运行阶段才会知道,有安全隐患

所以,针对上边两个缺点,可总结出使用泛型的优点:

  1. 使用时直接得到目标类型,消除强制类型转换
  2. 编译阶段即可发现类型转换异常,用sun公司的说法就是:使用了泛型,则不会再发生ClassCastException

3、泛型的使用方式

泛型可以用在类、接口、方法中,分别被称为泛型类、泛型接口、泛型方法

3.1、在接口定义中用到泛型的接口,叫做泛型接口

public interface MyList<E> {
    void add(E e);
}

在MyList接口中用到了泛型E,那么MyList就是泛型接口,E在该接口中可以直接使用,代表某一种对象类型

3.2、在类的定义中用到泛型的类,叫做泛型类

public class MyArrayList<E> {
    public void add(E e){
      
    }
}

泛型类是在实例化类的时候指明泛型的具体类型,所以泛型类中的静态方法中不能使用泛型:

public class MyArrayList<E> {
    public void add(E e) {}

  	// 会报编译错误,因为静态方法是属于类的,未实例化之前,是不能确定E的类型的
    public static E get(){
        return null;
    }
}

3.3、泛型在继承中的应用

/**
 * 实现了MyList接口,并且指定了泛型类型为Integer,此时MyArrayList不再是泛型类,而是普通类
 * 只能存放Integer类型的数据
 */
public class MyArrayList implements MyList<Integer> {
    @Override
    public void add(Integer integer) {}
}
/**
 * 实现了MyList接口,并且没有指定了泛型类型,此时MyArrayList依然是泛型类
 */
public class MyArrayList<E> implements MyList<E> {
    @Override
    public void add(E e) {}
}

3.4、泛型方法

泛型方法可被定义在普通类中,也可被定义在泛型类中,只有被声明为泛型方法的方法才被称为泛型方法

public class MyArrayList<E> {
    /*
      这不是泛型方法,而是普通方法,因为他没有被声明为泛型方法
     */
    public void add(E e) {}

    /*
      这是泛型方法,因为它通过<T>被声明为泛型方法,如果不加<T>会产生编译错误,编译错误的任务T是某个具体的类,因找不到而报错
      只有声明了<T>的方法才是泛型方法,泛型类中的使用了泛型的成员方法并不是泛型方法
      <T>表明该方法将使用泛型类型T,此时可以在方法中使用泛型类型T
      与泛型类的定义一样,此处T可以随便写为任意标识,常见的如T、E、K、V等形式的参数常用于表示泛型
     */
    public <T> void test(T t){
        Class clazz = t.getClass();
    }
  
  	/*
  		泛型方法可被声明为静态的,因为泛型方法中的泛型类型是在方法被调用时确定的
  		所以如果static方法要使用泛型能力,就必须使其成为泛型方法
  	*/
  	public static <T> void test2(T t){}
}

普通类中也可定义泛型方法:

public class MyArrayList {
    public <T> void test(T t){
        Class clazz = t.getClass();
    }
}

3.5、泛型通配符

List< Object>与List< String>存在子类父类的关系吗?

答案是:不存在

public static <T> void test2(T t){
    List<String> list1 = new ArrayList<>();
    List<Object> list2 = new ArrayList<>();
    // 会报编译错误
    list2 = list1;
}
public static void test3(List<Object> list){}

public static void main(String[] args) {
    List<Object> list = new ArrayList<>();
    test3(list);
    List<String> list2 = new ArrayList<>();
  	// 会报编译错误,因为List<Object>与List<String>不存爱上下级关系
    test3(list2);
}

那么如何做到方法通用呢?即无论形式参数传List< String>或者List< Object>都是合法的,此时就要用到泛型通配符<?>

public static void test3(List<?> list){}

public static void main(String[] args) {
    List<Object> list = new ArrayList<>();
    test3(list);
    List<String> list2 = new ArrayList<>();
    // 编译无报错
    test3(list2);
}

3.6、泛型通配符上下边界

在使用泛型的时候,我们还可以为传入的泛型类型实参进行上下边界的限制

如:类型实参只准传入某种类型的父类或某种类型的子类。

3.6.1、为泛型添加上边界,即传入的类型实参必须是指定类型的子类型或本身类型
public static void test4(List<? extends Number> list){}

public static void main(String[] args) {
    List<Integer> list = new ArrayList<>();
    test4(list);
    List<Number> list2 = new ArrayList<>();
    test4(list2);
    List<String> list3 = new ArrayList<>();
    // 会报编译错误,因为String不是Number的子类
    test4(list3);
}

/*
	编译报错Unexpected bound
*/
public <T> void test4(List<T extends Number> list){}

/*
	在泛型方法中添加上下边界限制的时候,必须在权限声明与返回值之间的<T>上添加上下边界,即在泛型声明的时候添加
*/
public <T extends Number> void test5(List<T> list){}
3.6.2、为泛型添加下边界,即传入的类型实参必须是指定类型的父类型或本身类型
public static void test6(List<? super Number> list){}

public static void main(String[] args) {
    List<Integer> list = new ArrayList<>();
  	// 报编译错误,因为Integer不是Number的父类
    test6(list);
    List<Number> list2 = new ArrayList<>();
    test6(list2);
    List<Object> list3 = new ArrayList<>();
    test6(list3);
}

4、泛型的类型擦除

Java 中的泛型和 C++ 中的模板有一个很大的不同:

  • C++ 中模板的实例化会为每一种类型都产生一套不同的代码,这就是所谓的代码膨胀。

  • Java 中并不会产生这个问题。虚拟机中并没有泛型类型对象,所有的对象都是普通类。

在 Java 中,泛型是 Java 编译器的概念,用泛型编写的 Java 程序和普通的 Java 程序基本相同,只是多了一些参数化的类型同时少了一些类型转换。

实际上泛型程序也是首先被转化成一般的、不带泛型的 Java 程序后再进行处理的,编译器自动完成了从 Generic Java 到普通 Java 的翻译,Java 虚拟机运行时对泛型基本一无所知。

当编译器对带有泛型的java代码进行编译时,它会去执行类型检查和类型推断,然后生成普通的不带泛型的字节码,这种普通的字节码可以被一般的 Java 虚拟机接收并执行,这在就叫做 类型擦除(type erasure)。

public static void test7() {
    List<String> strings = new ArrayList<>();
    List<Integer> integers = new ArrayList<>();
  	// true
    System.out.println(strings.getClass() == integers.getClass());
}
  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值