Java基础:泛型及其擦除性、不可协变性

转载请注明出处:jiq?钦‘s
technical Blog

1泛型语法:

泛型类: class ClassName<T>{}

泛型方法:public <T> void f(T x){}

基本指导原则:如果使用泛型方法可以取代将整个类泛型化,那么就应该使用泛型方法,因为它可以让事情更加清楚。

2为什么使用泛型?

在Java SE1.5之前,没有泛型的情况的下,通过对类型Object的引用来实现参数的“任意化”,“任意化”带来的缺点是要做显式的强制类型转换,而这种转换是要求开发者对实际参数类型可以预知的情况下进行的。对于强制类型转换错误的情况,编译器可能不提示错误,在运行的时候才出现异常,这是一个安全隐患。

泛型的好处是:

(1)在编译时检查类型安全;

(2)并且所有的强制转换都是自动和隐式的,提高代码的重用率。

促成泛型出现最引人注目的一个原因就是为了创造容器类。

优先考虑泛型!!!

使用泛型比使用需要在客户端代码中进行转换的类型来的更加安全,也更加容易。

在设计新类型的时候,要确保它们不需要这种转换就可以使用。这通常意味着要把类做成是泛型的。

比如

客户端代码使用Stack的时候,如果不使用泛型,在取用Stack中的对象时还需要进行不安全的类型判断,类型转换等,而且代码还比较复杂。如果使用带泛型的Stack,客户端代码中就不需要进行类型转换了,直接使用而且不会复杂。

3泛型数组:

无法创建泛型数组,一般的解决方式是在任何想要使用泛型数组的地方使用ArrayList。

public <T> voidtest()
{
//Cannotcreate a generic array of T
T[]tList = new T[10];
int[]iList = new int[10];

//useArrayList<T>
ArrayList<T>sList = new ArrayList<T>();
}

4类型擦除:

一、概念

类型擦除:将泛型类型中类型参数信息去除,只映射为一份字节码,在必要时进行类型检查和类型转换。

编译时通过两个步骤来对泛型类型的类型进行擦除:

1.将所有的泛型参数用其最左边界(最顶级的父类型)类型替换。

2.移除所有的类型参数。

备注:擦除也是为什么泛型必须编译时进行类型检查的原因,因为运行时类型信息被擦除了。

二、擦除举例

(1)容器泛型类型擦除:

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

System.out.println(l1.getClass().getName());
System.out.println(l2.getClass().getName());
System.out.println(l3.getClass().getName());
System.out.println(l4.getClass().getName());

//output
java.util.ArrayList
java.util.ArrayList
java.util.LinkedList
java.util.LinkedList

可以看到上面四个泛型类型的类型信息均被擦出掉了,比如ArrayList<Integer>和ArrayList<String>编译后生成的字节码是一份。

(2)自定义泛型类型擦除:

再看一个自定义的泛型类:

class TObject<T>
{
privateT obj;
publicvoid Set(T object)
{
this.obj= object;
System.out.println("T:"+object.getClass().getName());
}
}

编译擦除后,生成的类是这样:

class TObject
{
privateObject obj;
publicvoid Set(Object object)
{
this.obj= object;
}
}

首先泛型参数T被向上替换为自身的顶级父类Object,然后将类型参数T去除。

(3)自定义继承关系泛型类型擦除:

class Manipulator<Textends SuperClass>
{

  private T obj;
  public Manipulator(T x){
    obj = x;
  }
  public void doSomething(){
    obj.f();
   System.out.println(obj.getClass().getName());
  }
}

首先将泛型参数T向上替换为上边界,然后去除泛型参数:

class Manipulator
{
  private SuperClass obj;
  public Manipulator(SuperClass x){
    obj = x;
  }
  public void doSomething(){
    obj.f();
   System.out.println(obj.getClass().getName());
  }
}

三、擦除原因:

泛型不是在java一开始就有的,擦除是java中泛型实现的一种折中手段

具体来说两个原因使得泛型代码需要类型擦除:

(1)引入泛型代码不能对现有代码类库产生影响,所以需要将泛型代码擦除为非泛型代码;

(2)当泛型代码被当做类库使用时,为了兼容性,不需要知道泛型代码是否使用泛型,所以需要擦除;

5不可协变:

(1)数组和泛型对比

数组是可协变的、泛型是不可协变的。

什么是可协变性?举个例子说明:

数组可协变(covariant)是指如果类Base是类Sub的基类,那么Base[]就是Sub[]的基类。

泛型不可变的(invariant)是指List<Base>不会是List<Sub>的基类,两者压根没关系。

(2)泛型为什么不可协变

泛型“编译时进行类型检查(类型安全)”特性决定了其不可协变。


ArrayList<Object> objList = new ArrayList<Long>();


//can‘t compile pass


类型安全检查


Object[] objArray = new Long[10];


//compile OK

如果ArrayList<Long>类型对象可以赋值给ArrayList<Object>类型引用,那么就违反了泛型类型安全的原则。

因为如果编译器你这样做,会导致可以往容器中放置非Long型的对象。但是数组就无所谓,他不是类型安全的。

再看看下面代码,第一行编译错误是因为不可协变性,那么为什么第二行可以呢?


List<Type> listt = new ArrayList<SubType>();


//can‘t compile pass


List<? extends Type> listt = new ArrayList<SubType>();


//OK


参考泛型通配符,这就是其作用

不可协变并不代表不能在泛型代码中将父类出现的地方使用子类代替,如下面代码是合法的:


ArrayList<Type> list = new ArrayList<Type>();


//Type is SuperClass


list.add(new SubType());


//SubType is SubClass


Type[] tt = new Type[3];


tt[0] = new SubType();

(3)数组可协变带来的问题:

数组的协变性可能会导致一些错误,比如下面的代码:

public static voidmain(String[] args) {
   Object[] array = new String[10];
    array[0] = 10;
}     

它是可以编译通过的,因为数组是协变的,Object[]类型的引用可以指向一个String[]类型的对象。但是运行的时候是会报出如下异常的:

Exception in thread"main" java.lang.ArrayStoreException: java.lang.Integer

但是对于泛型就不会出现这种情况了:

public static voidmain(String[] args) {
    List< Object> list = newArrayList< String>();
    list.add(10);
} 

这段代码连编译都不能通过。

6通配符:

通配符在类型系统中的作用部分来自其不会发生协变(covariant)这一特性。即通配符产生一部分原因来自突破不可协变的限制。

可以认为通配符使得List<?>是List<AnyType>的基类,List<? extends  Type>是List<SubType>的基类。

// collection1可以存放任何类型
Collection<?>collection1 = new ArrayList<String>();
collection1 = newArrayList<Integer>();
collection1 = newArrayList<Object>();

//collection3表示它可以存放Number或Number的子类
Collection<?extends Number> collection3 = null;
collection3 = newArrayList<Number>();
collection3 = newArrayList<Double>();
collection3 = newArrayList<Long>();

//collection4表示它可以存放Integer或Integer的父类
Collection<? superInteger> collection4 = null;
collection4 = newArrayList<Object>();
时间: 2024-08-05 07:09:34

Java基础:泛型及其擦除性、不可协变性的相关文章

JAVA基础—泛型小结

概念: 泛型是Java SE 1.5的新特性,泛型的本质是参数化类型,也就是说所操作的数据类型被指定为一个参数.这种参数类型可以用在类.接口和方法的创建中,分别称为泛型类.泛型接口.泛型方法. Java语言引入泛型的好处是安全简单. 泛型的常用字符代表意思: ? 表示不确定的java类型. T  表示java类型. K V 分别代表java键值中的Key Value. E 代表Element. 下面转载于cnblog上一个写的很好的例子 真的很好,我写了半天,当看到他这个后,立即删除~ 普通泛型

一天一个Java基础——泛型

这学期的新课——设计模式,由我仰慕已久的老师传授,可惜思维过快,第一节就被老师挑中上去敲代码,自此在心里烙下了阴影,都是Java基础欠下的债 这学期的新课——算法设计与分析,虽老师不爱与同学互动式的讲课,但老师讲的挺好,不过由于数据结构欠缺课听的有点烧脑,都是数据结构欠下的债 这学期的新课——英语口语,虽外教老师风骚逗趣浪荡不羁爱自由,但我辈词汇量欠缺,表明淡定说yeah,但心中一万匹草泥马策马奔腾,都是英语欠下的债 1.泛型类 实体类(容器类),经常重用的类,下面是一个没有用泛型的实体类: 1

Java中泛型 类型擦除

转自:Java中泛型是类型擦除的 Java 泛型(Generic)的引入加强了参数类型的安全性,减少了类型的转换,但有一点需要注意:Java 的泛型在编译器有效,在运行期被删除,也就是说所有泛型参数类型在编译后都会被清除掉,看下面一个列子,代码如下: public class Foo { public void listMethod(List<String> stringList){ } public void listMethod(List<Integer> intList) {

java基础 泛型

1.泛型集合中的元素是存在继承关系的 public class Main { public static void main(String[] args) { List<Shape> list=new ArrayList<Shape>(); list.add(new Circle()); list.add(new Rectangle()); } } class Canva { public void drawAll(List<? extends Shape> list)

Java基础 -- 泛型之泛型参数

泛型机制常用的参数有3个: “?”代表任意类型.如果只指定了<?>,而没有extends,则默认是允许任意类. extends关键字声明了类型的上界,表示参数化的类型可能是所指定的类型,或者是此类型的子类. super关键字声明了类型的下界,表示参数化的类型可能是所指定的类型,或者是此类型的父类型,直至Object 前提 Fruit是Apple和Orange的超类. 本章通过java代码逐一分析泛型参数的意义和区别 extends参数: [java] public void extend(Li

Java基础---泛型、集合框架工具类:collections和Arrays

第一讲     泛型(Generic) 一.概述 1.JDK1.5版本以后出现的新特性.用于解决安全问题,是一个类型安全机制. 2.JDK1.5的集合类希望在定义集合时,明确表明你要向集合中装入那种类型的数据,无法加入指定类型以外的数据. 3.泛型是提供给javac编译器使用的可以限定集合中的输入类型说明的集合时,会去掉“类型”信息,使程序运行效率不受影响,对参数化的泛型类型,getClass()方法的返回值和原始类型完全一样. 4.由于编译生成的字节码会去掉泛型的类型信息,只要能跳过编译器,就

Java基础——泛型

转http://www.cnblogs.com/1693977889zz/p/7095460.html 一.定义 泛型(generic)是指参数化类型的能力.可以定义带泛型类型的类或方法,随后编译器会用具体的类型来替换它(泛型实例化). 使用泛型的主要优点是能够在编译时,而不是在运行时检测出错误. 它是jdk1.5之后出现的特性,为了增强安全性.我的理解是,它更像一种特殊规范,比如程序员在调用的时候,或者客户端在引入的时候,总不能鱼龙混杂,想怎样就怎样啊?!前面定义说输入一个String型的,这

java基础-泛型2

6 类型推测 java编译器能够检查所有的方法调用和对应的声明来决定类型的实参,即类型推测,类型的推测算法推测满足所有参数的最具体类型,如下例所示: //泛型方法的声明 static <T> T pick(T a1, T a2) { return a2; } //调用该方法,根据赋值对象的类型,推测泛型方法的类型参数为Serializable //String和ArrayList<T>都实现接口Serializable,后者是最具体的类型 Serializable s = pick

java基础-泛型的优点

1.性能 对值类型使用非泛型集合类,在把值类型转换为引用类型,和把引用类型转换为值类型时,需要进行装箱和拆箱操作.装箱和拆箱的操作很容易实现,但是性能损失较大.假如使用泛型,就可以避免装箱和拆箱操作. 1 ArrayList list=new ArrayList(); 2 list.Add(20); //装箱,list存放的是object类型元素,须将值类型转化为引用类型 3 int i=(int)list[0]; //拆箱,list[0]的类型是object,要赋值就得把引用类型转化为值类型