Java泛型的学习
学习Java也有一段时间了,现在想要深入的了解一下泛型的相关知识,总结自己所得的泛型知识。
开始的时候,我对泛型的理解是这样的:泛型机制就是为了确保类型的安全,防止操作的类型不当而引起的错误。我们可以看看泛型在百度百科中的定义:泛型的本质是参数化类型,也就是说所操作的数据类型被指定为一个参数,这种参数类型可以在类、接口以及方法中创建分别叫做泛型类、接口以及泛型方法,Java引入泛型的好处就是简单安全。
看完定义之后,解决第一个问题——什么是参数化类型。
参数化类型就是编译器可以自动定制作用于特定类型上的类。参数化类型说到头还是一个类,只不过编译器可以对他做一些事情。那我们看看编译器对参数化类型可以做什么。
在集合中我们经常会使用泛型,比如Map的实现接口Hashmap。如果我们不使用泛型,则可以有下面这段代码。
Object a = null;
Map m = new HashMap();
m.put("test", 1);
m.put(1, "test");
//input a para a
String s =(String)m.get(a);//这里必须进行强制转换
这段代码是可以通过编译的,甚至运行都不会报错。首先我们会发现这种存储方式是不合适的。K和V的值就没有选好。接下来,我们得到的a是一个Object类型的参数,那么 实际情况中,我们到底会得到怎么样的一个参数是没有保证的,这样就会造成运行中的错误。甚至运行之后貌似都没有错误。(而我们却没有察觉)
接下来看看引入泛型的情况。将上面new操作变成下面这样:
Map <String,String>m = new HashMap <String,String>();
如果还是像上面一样put是通不过编译的,编译器会直接报错:put的参数必须和<>里面的参数类型相一致才可以通过编译。也就是说,编译器自动定制作用于该类Hashmap。将它的参数由Object属性自动定制作于为String属性(和<>号里面的类型保持一致)。这里的Hashmap就是一个参数化的类型。
使用泛型之后,则不用强制转换也可以得到String。
Object a = null;
Map <String,String>m = new HashMap <String,String>();
m.put("test", "test");
//input a para a
String s = m.get(a);
经过上面的测试我们可以得出:Java泛型可以将String的键映射到String的值,从而相当于一个中间检查站(编译器),不是等你上路之后发生事故才发现错误,而是在你要进高速的时候检查你的类型是否正确。不仅可以消除强制转化,还可以防止程序猿将错误的类型保存在K或者V之中,这就是泛型所做的工作。
除了内部匿名类,异常类以及枚举任何类都可以有类型参数,在声明类型参数(泛型)在声明类型参数的时候要用<>,且需要在<>中指定参数类型。
假设我们有People和Child两个类,那么Child是People是父子类的关系,那么List <People>和List<Child> 是什么关系,让我们看下面一组测试吧:
List <Integer> m1 = new ArrayList<Integer>();
List <Number> m2 = m1;//这里是会报错的,显然编译通不过。
由此可见:泛型是没有父子关系的。。。
简单分析,如果第二句话不报错,那么传入其中的参数有可能是个属于Number但是却不是Integer的。显然,这样违背了泛型的初衷。虽然泛型没有了和Java其他类类似的父子类,但是每一组泛型类都有以一个牛逼的爸爸类,那就是用<?>表示的泛型类。将上面的代码变化如下则可以通过编译。
List <Integer> m1 = new ArrayList<Integer>();
List <?> m2 = m1;
不过这个牛逼的爸爸类也和一般情况的父类是不一样的。我们做如下测试:
List <Integer> m1 = new ArrayList<Integer>();
List <?> m2 =m1;
m1.add(0);
m2.add(0);//这里会报错,但是如果这句话是 m2.get(0);的话就没有问题。
上面的m2.add(1);是会报错的,但换成get()方法却可以用。而我们把一个父类声明指向子类的时候是可以调用子类的所有方法的。之所以涉及到添加修改内容的各种方法不能用,而get内容的方法可以用也是有编译器决定的,因为编译器可以推断get方法肯定能得到一个Object对象,但是添加和修改的对象却不一定和泛型设置的类型参数一致。
使用通配符?有什么具体的用处。
public class fanxing <T>{
public static void main(String[] args) {
fanxing <String> fa1 = new fanxing <String>();
fanxing <Integer> fa2 = new fanxing <Integer>();
fa1.output("test");
fa2.output(1);
fanxing <?> fa3 = new fanxing<>();
fa3.output(2);
}
//这个fanxing类中有下面两个方法
void output(int i){
System.out.println(i);
}
void output(String str){
System.out.println("hahaha");
}
}
利用通配符,我们还可以不必对省去对所有类型都要实例化一次的麻烦,提高程序复用性。
说了这么半天,那泛型有什么好处:总结了以下几点:
1:在程序编译的时候检查错误而不是运行出现错误才反馈,提高程序的可靠性。
2:泛型可以减少强制转换的使用,增加可读性,可以提高效率。。。
此外,基本数据类型是没有泛型的。
以上是今天了解的内容,做了一个小结,如果有什么不对的地方,请不吝赐教,后面我还会做修改和补充。