最近真是 多事之秋,内心空荡荡的,可能接收知识的速度还是太慢了,感觉时间越来越不够用了,马上就要三月份了,多少有些紧张,唉不管了,先抓紧现在的每一分每一秒再说了
本来想直接看完后面的Java集合那点东西然后就可以结束了,但是似乎如果没有泛型的思想,Java集合的东西理解起来不得要领,故而先探究Java泛型。
泛型的定义
泛型的本质是参数化类型,参数化类型是指用来声明数据本身的类型,可以根据需求改变类型。这种参数类型可以用在接口、类和方法的创建中,分别称为泛型接口、泛型类、泛型方法。泛型可以定义安全的类型,故引入泛型的好处是让编程变得安全简单
领教一下没有泛型的不安全示例
Java提供了对Object类型的引用“任意化”操作,即类型转换,在某些强制类型转换时的异常都不被编译器捕捉,而在运行时才会出现(抛出ClassCastException异常)。(我当时就碰到了这种情况,原来如此!)
正常的类型转换
class Gen {
private Object obj;
public void setObj(Object obj){
this.obj=obj;
}
public Object getObj() {
return obj;
}
}
public class GenDemo{
public static void main (String []args){
Gen g=new Gen();
g.setObj(new Integer(5));
Integer in=(Integer)g.getObj();
System.out.println("the number: "+in);
g.setObj(new Float(1.23f));
Float f=(Float)g.getObj();
System.out.println("the number: "+f);
}
}
错误的类型转换(编译不报错,运行时抛出ClassCastException异常)
以上原因就是因为Object可以接收String类型,但无法将其转换为Float类型,这种错误只能出现在运行时,故而采用泛型,可以使得错误出现在编译时就可以被检测出来
使用泛型还有一个好处,例如,就是不需要为上面的String和Integer分别创建一个类,而是创建一个模板,即真正的实现”Object“类
泛型的应用
- 创建泛型类
[public] class 类名 <泛型类型标识>{
[访问控制符] 泛型类型标识 变量名;
[访问控制符] 泛型类型标识 方法名(){};
[访问控制符] 返回值类型 方法名(泛型类型标识 参数名){};
}
其中,泛型类型标识只能是引用数据类型。泛型方法的返回值类型则可以是泛型类型标识,也可以是其他数据类型。
泛型中统一默认的类型参数命名(只是一个方便统一的名称,无实际意义)
- 声明泛型类
class Gen<T>{
private T obj; //定义Object类型的属性
public void setObj(T obj) { //设置obj属性
this.obj = obj;
}
public T getObj(){ //取得obj属性
return obj;
}
}
- 定义泛型对象
类名<泛型类型>对象名=new 类名<泛型类型>();
- 使用泛型
package javaDemo;
class Genn<T>{
private T obj; //定义Object类型的属性
public void setObj(T obj) { //设置obj属性
this.obj = obj;
}
public T getObj(){ //取得obj属性
return obj;
}
}
public class GenDemo1 {
public static void main(String []args){
Genn<Integer> gd1=new Genn<Integer>();
gd1.setObj(new Integer(5));
Integer in=gd1.getObj();
System.out.println("the number: "+in);
Genn<Float> gd2=new Genn<>();
gd2.setObj(new Float(1.23f));
Float f=gd2.getObj();
System.out.println("the number: "+f);
}
}
现在重复上面的错误类型转换,可在编译期查出
这就是泛型在编译期可检查错误的好处
注意:
规定:泛型类型标识 不能是基本数据类型,且只能是引用数据类型
不能直接使用参数类型来构造一个对象,即:
class Genn<T>{
T t=new T();
}
以上是错误的写法,在通常情况下,泛型类型参数(也就是泛型类型标识)所指定的对象是不会在泛型类中创建的,而是由外部传入的。但是,若一定要在泛型类中创建泛型类型参数所指定的对象,可以通过反射机制中的Class.newInstance()方法。
- 泛型类的构造方法
[访问控制符] 类名 ([泛型类型] 参数列表){
//构造方法语句
}
定义泛型类构造函数
运行结果
未完待续。。。。
- 定义泛型类时声明多个类型
在泛型类中,可以声明多个泛型类型,只需要在这些类型之间用逗号隔开。
例如:设置两个泛型类型
class gen3<T,S>{
private T obj1;
private S obj2;
public gen3(){}
public gen3(T obj1,S obj2){
setObj1(obj1);
setObj2(obj2);
}
public void setObj1(T obj1){
this.obj1=obj1; }
public void setObj2(S obj2){
this.obj2=obj2;
}
public T getObj1(){
return obj1;
}
public S getObj2(){
return obj2;
}
public void show(){
System.out.println("name:"+getObj1()+", age:"+getObj2());
}
}
public class GenDemo3 {
public static void main(String []args){
gen3<String,Integer> gd3=new gen3<>("小强",20);
gd3.show();
}
}
以上程序中,泛型类型T和S可以是相同类型,也可以是不同的类型,这是由外部指定的。
在创建泛型对象时最好为其指定类型,如果没有指定类型时,系统会默认以Object类型来接收,即在定义时进行泛型擦拭,当Java源代码被编译时,全部的泛型类型信息会被删除(擦拭)
通配符
在前面的泛型类操作时都设置了一个固定的类型,若要接收任意指定的泛型类型时,需要使用无界通配符?
运行结果
以上程序使用了 无界通配符? 表示可以接收任意的泛型类型,但是值得注意的是:使用无界通配符?设置对象时,不能通过set()方法设置被泛型指定的内容。但是可以设置为null
示例如下:
设置泛型类型的上限(extends)
<泛型类型标识 extends 泛型类型1& 泛型类型2........>
一个类型参数(也就是泛型类型标识)可以用多个限界类型。限界类之间用& 分隔,逗号是用来分隔类型参数的(也就是泛型类型标识)。
注意: 泛型类型可以用多个接口,但只能用一个类,且如果类作为限界类型,则必须放在限界列表中的第一个。
例子
class Gen4<T extends Number>{
private T obj;
public void setObj(T obj){
this.obj=obj;
}
public T getObj(){
return obj;
}
public void show (Gen4<?> t){
System.out.println("show: "+getObj());
}
public static void info(Gen4<? extends Number> t){
System.out.println("info():"+t.getObj());
}
}
public class gen4Demo {
public static void main(String []args){
Gen4<Integer> gd4=new Gen4<Integer>();
gd4.setObj(3);
gd4.show(gd4);
Gen4.info(gd4);
Gen4<Double> gd5=new Gen4<Double>();
gd5.setObj(5.5);
gd5.show(gd5);
Gen4.info(gd5);
}
}
从创建泛型类的声明就可以看出,所有的泛型类对象只能是Number类的泛型对象或其子类的泛型对象。当然啦上面的public static void info(Gen4 < ? extends Number> t) 也是同样的只能是Number类的泛型对象或其子类的泛型对象,而且它的定义万万不可定义成Gen4< T extends Number> t ,因为T的作用域并不在这里。
注意: 创建泛型类时不可将无界通配符?用上,因为不具有唯一性。
设置泛型下限
使用super关键字可以设置泛型类型通配符的下限,使其只能是某些类型或该类型的父类。
<? super 泛型类型>
例子:
注意 此时只能传入Integer类型或其父类才行,若传入其他类型,则编译出错。
static 与泛型
对于static方法,泛型应该是使用无界通配符才行
泛型与子类之间的关系
当一个类的父类是泛型类时,该类也必定是泛型类,因为子类要将类型参数传递给父类。
例子:
package javaDemo;
class Gen5<T>{
private T obj;
public Gen5(){
}
public Gen5(T obj){
setObj(obj);
}
public void setObj(T obj){
this.obj=obj;
}
public T getObj(){
return obj;
}
}
public class GenDemo5<T> extends Gen5<T> {
public GenDemo5(){
}
public GenDemo5(T obj){
super(obj);
}
public String toString(){
return getObj().toString();
}
public static void main(String []args){
GenDemo5<Integer> in =new GenDemo5<>(20);
System.out.println("the age: "+in);
}
}
在上面的程序中,子类GenDemo5类的泛型标识也是T,这意味着传递给子类GenDemo5类的实际类型也将会被传递给父类Gen5,当然了,子类也可以根据自己的需要增加泛型类型。
比如:修改如下
运行结果:
以非泛型类为父类
运行结果:
由此可以发现,泛型类和非泛型类在继承的主要区别在于,泛型类的子类必须将泛型父类所需要的类型参数沿着继承链向上传递。
泛型接口
和普通的类实现接口没啥区别,
- 定义泛型接口
[访问控制符] interface 接口名称 <泛型类型标识>{
}
例子
实现泛型接口:
interface InterG<T>{
public T show();
}
实现:
结果:
当然啦,如果你在实现的时候就已经指定了泛型类型,则在创建的时候就不需要再去指定具体的类型了。(个人感觉还是不指定的好,毕竟少打那么多个字,而且兼容性也很好233)
比如:
泛型方法
- 定义泛型方法
[访问控制符] [static|final] <泛型类型标识> 返回值类型 方法名 ([泛型类型标识 参数名])
注意:以上格式固定好了,万不可乱动。
泛型方法可以写在泛型类或泛型接口中,也可以卸载普通类或普通接口中。在泛型类或泛型接口中的方法本质上都是泛型方法。
泛型方法可以被定义成实例方法或静态方法,这是与泛型类中的方法的不同。
- 使用泛型方法
格式一:
[对象名|类名.]方法名(实参列表)
格式二:
[对象名|类名.]<实际泛型类型>方法名(实参列表)
例子
package javaDemo;
class Gen8{
public <T> T show(T t){
return t;
}
public static <T> T staticShow(String s,T t){
return t;
}
}
public class genDemo8 extends Gen8{
public static void main(String []args){
genDemo8 gen=new genDemo8();
System.out.println("name: "+gen.show("程序员"));
System.out.println("age; "+genDemo8.staticShow("程序员",20));
System.out.println("name: "+gen.<String>show("攻城狮"));
System.out.println("age: "+genDemo8.<Integer>staticShow("攻城狮",20));
}
}
总结
1、 泛型的本质是参数化类型,参数化类型是指用来声明的类型,可以根据需求改变类型。Java语言引入泛型的好处是让编程更加安全(编译期间即可查处错误)
2、 使用通配符? 就可以接收全部的泛型类型对象
3、 可以通过< ? extends 类型>设置泛型的上限,使用< ? super 类型>可以设置泛型的下限,此外记住,通配符是用来声明一个泛型类型的变量,而不是用来创建一个泛型类的。
4、 泛型类可以作为父类,也可以作为子类。泛型类与非泛型类在继承时的主要差别是:泛型类的子类必须将泛型父类所需要的类型参数沿着继承链向上传递。
5、 泛型方法可以写在泛型类或泛型接口中,也可以写在普通类或普通接口中,在泛型类或泛型接口中的方法本质上都是泛型方法。
6、 泛型方法可以定义成实例方法,也可以定义成静态方法。
7、 大多数情况下,泛型主要出现在源码、集合中。。
小结
这篇博文,更新的速度有点慢,主要原因是中途断过一次网导致更新的数据都丢失了,而我又没有在本地保存的习惯,结果只得从头来过,不过有个好处就是对泛型的理解又加深了一遍。。。找个时间还是得抽看一下那本《格蠹汇编》,毕竟作者在第一篇就写下了我的这种类似的情况,然后到内存中去抢救缓存,结果还真的可行。我记得前两个月我就推荐过,唉希望忙完之后可以去看看吧,先mark一下。