什么是泛型?
泛型的本质是将数据的类型参数化。即将原本的类类型作为参数,如:String、Number等包括自己写的类,注意,不可以将基本类型作为泛型参数。泛型是在定义类、接口或方法时通过为其增加 “类型参数” 来实现的,分别称为泛型类、泛型接口、泛型方法。
泛型,又称为参数多态。
如何定义泛型?
其定义的格式是在一般类、一般接口和一般方法定义的基础上加一个或多个用尖括号括起来的 “类型参数” 来实现的。
泛型类的定义:
[修饰符] class 类名<T>
泛型接口的定义:
[public] interface 接口名<T>
泛型方法的定义:
[public] [static] <T> 返回值类型 方法名(T 参数)
注意:
(1.)T参数在此表示任意类类型。可以用该类型来声明类成员变量(例如:public T t;)、成员方法的参数或返回值等。
(2.)每个类型参数在该泛型中都是唯一的(不可以写为Map<K,K>形式,但可以写为Map<K,T>)。
以上的泛型类、泛型接口、泛型方法在实例化、实现、调用时,类型参数T,表示的是某一种数据的类型而非数据的值。通过泛型类创建的对象叫做泛型对象,这个过程也叫作泛型实例化。
java“自动包装”和“自动解包”在泛型中的体现
如果编译器发现程序在该使用包装类对象的地方却使用基本数据类型的数据,编译器将自动把该数据包装为该基本类型对应的包装类的对象,这个过程称为自动装包。如果类型参数T所接受的是int、double或char等基本类型时,T所代表的类型会自动包装成Integer、Double、String等类型。
如果编译器发现程序在该使基本类型数据的地方却使用包装类对象,则会把包装类对象解包,从中取出所包含的基本类型数据,这个过程称为自动解包。
泛型方法
声明!一个方法是否是泛型方法与其所在的类是否是泛型类 没有关系!想要定义泛型方法,只需要将泛型的类型参数置于返回值类型前面即可。在Java中任何方法(包括静态方法和构造方法)都可以声明为泛型方法。
上图13、14行可以看出,在调用泛型方法时,可以将实际类型放在尖括号内作为方法名的前缀,以强调这是一个泛型方法。
由14、15行可看出,java编译器具有类型参数推断的功能,它会根据调用方法时实参的类型,推断得出被调用方法类型参数的具体类型,并据此检查方法调用中类型的正确性。
编写java泛型方法时,返回值类型和至少一个参数类型应该是泛型,而且类型应该是一致的(或者说返回值类型必须包含于参数类型)。
如果泛型方法的多个形式参数使用了相同的类型参数,并且对应的多个实参具有不同的类型,则编译器会将该类型参数指定为这多个实参所具有的“最近”共同父类直至Object。
如上图所示,假如调用该方法时,两个参数分别为Object类型的和String类型的,此时类型参数为Object类型。
static方法访问泛型类的类型参数
一个static方法,无法访问泛型类的类型参数,所以如果static方法需要使用泛型能力,必须使其成为泛型方法。
如图所示,如果main()方法想要访问泛型类中的类型参数,必须把该方法改造为泛型方法。相反,一个普通方法想要访问泛型类中的类型参数,则可以直接访问。
但需要注意的是,类型参数声明的变量默认为final类型,也只能用final修饰。
泛型方法和泛型类有哪些区别?
对于泛型方法而言,不需要把实际的类型传递给泛型方法;但泛型类却恰恰相反,即必须把实际的类型参数传递给泛型类。
如图所示,第十二行对比第13行,出现编译错误: “Incorrect number of arguments for type Demo_05<T>; it cannot be parameterized with arguments <>”
如何限制泛型的可用类型?
在定义泛型类时,默认可以使用任何类型来实例化一个泛型类对象,但在Java语言中,也可以在用泛型类创建对象时对数据类型作出限制。具体语法如下:
class ClassName <T extends anyClass>。在此,anyClass是指某个类或接口。由此可以看出,ClassName类的类型参数T必须为anyClass类、anyClass的子类、anyClass接口的实现类。在此,无论anyClass是类还是接口,在进行泛型限制时都必须使用extends关键字。
注意:
(1.)在定义泛型类时,如果没有进行泛型限制,泛型的类型参数默认是Object类下的所有子类。即<T>和<T extends Object>等价。
(2.)在利用泛型进行实例化时,若泛型的实际参数的类之间有父子关系时,参数实例化后得到的泛型类之间并不会具有同样的父子类关系。
泛型的类型通配符
泛型通配符“?”的主要作用:一是用在泛型类创建泛型对象的时候;二是用在方法的参数中。
使用通配符“?”创建泛型类对象——此时通配符的主要作用是在创建一个泛型类对象时,限制这个泛型类的类型为某个类或是继承该类的子类或是实现某个接口的类。
泛型类名 <? extends 类型1> 对象名 = new 泛型类名<类型2>();
其中“?”指的就是“类型2”。“类型1”表示一个类或接口。而类型2要么是类型1、要么是类型1的子类、要么是类型1的实现类(当类型1为接口时)。
由上可知,只知道通配符表示的是某个类或是某个类的子类或是某个接口的实现类,但具体是什么类型不知道(突然有点this的感觉了)。
注意:
(1.)在创建泛型类对象时,如果只使用了通配符,则默认为“? extends Object”。所以“?”也被称为非受限通配。
(2.)对于一个泛型类来说,在创建相应的泛型对象时,类型参数T除了用某个实例类型替换外,还可以用通配符(实例化时必须用具体类型充当参数)。
(3.)用通配符创建的对象,只能获取或删除其中的信息,但不能对其加入新的信息。
通配符在泛型方法中的应用——通配符除了在创建泛型类对象时限制泛型类的类型之外,还可以将由通配符限制的泛型类对象用在方法的参数中。具体用法同使用通配符“?”创建泛型类对象。
在泛型通配符“? extends T”中,由于T被认为是类型参数“?”的上限,所以“?extends T”也被称为上限通配;既然有了上限通配,自然也就有下限通配。对类型参数进行下限限制只需将extends改为super即可。此时,“? super T”表示是T或T的一个未知父类型,T表示类型参数“?”的下限,所以被称为下限通配符。
继承泛型类与实现泛型接口
被定义为泛型的类或接口可被继承与实现。
如果父类继承子类时,保留父类的类型参数,需要在继承时指明,如果没有指明,直接使用extends 类名 进行继承声明,则子类中的类型参数都会自动变成Object。
class Son<T1,T2,T3> extends Father<T1>{ }
如果Father后面不跟<T1>,则T1,T2,T3都会自动变为Object。
注意事项
(1.)不能使用泛型的参数类型T创建对象。如:T obj = new T(); 是错误的。
(2.)泛型方法的参数类型E,只能用来声明常量。
(3.)在泛型中可以用类型参数T声明一个数组,但不能使用类型参数T创建数组对象。如:“public T[] arr;”是正确的,但“public T[] arr = new T[8];”是错误的。
(4.)不能在静态环境(只要是用static修饰的,包括静态代码块)中使用泛型类的的类型参数T。
(5.)异常类不能是泛型的,即泛型类不能继承java.lang.Throwable类。
网友评论