问题:
- 为什么需要泛型?有哪些好处
- 泛型有哪些使用方式?
为什么使用泛型
在写代码的过程中,通常会有如下的需求,比如两个数相加,如果是整数我们通常会这样写:
public int addInteger(int a,int b){
returen a+b;
}
但是如果希望两个浮点数相加呢?是不是需要重新定义一个重载的方法来进行实现:
public float addFloat(float a,float b){
return a+b;
}
但是如果再有double型的数据进行相加呢?是不是也需要再定义一个新的方法,这样会严重增加无用代码量,那此时就会想,如果参数不规定类型的话,是不是只需要定义一个方法就基本上可以实现所有类型数据的相加了呢?所以此时需要使用泛型.
当我们使用list时,可以通过add方法向里面添加元素:
List lists=new ArrayList();
lists.add("111");
lists.add("222");
lists.add(33);
for (Object list : lists) {
String str=(String)list;
System.out.println(str);
}
add时可以添加任何元素,但是get时需要强转,如果类型不匹配,运行时才会报错,但是如果使用了泛型进行限制,可在编译阶段就会报错,并且取元素时不需要强转,此时代码可写成
List<String> lists=new ArrayList();
lists.add("111");
lists.add("222");
for (String list : lists) {
System.out.println(list);
}
所以由此可得出,使用泛型有如下好处:
1.适用于多种数据类型,执行相同代码
2.泛型中的类型在使用中指定,不需要强制转换
泛型使用方式
- 泛型类
- 泛型接口
- 泛型方法
泛型类和泛型接口
泛型格式:
class A <T>{
private T data;
public T getData() {
return data;
}
public void setData(T data) {
this.data = data;
}
}
interface B<T>{
T method();
}
其中类可以实现泛型接口,有两种方式:
- 泛型类实现泛型接口
class C<T> implements B<T>{
@Override
public T method() {
return null;
}
}
- 普通类实现泛型接口
class D implements B<String>{
@Override
public String method() {
return null;
}
}
泛型方法
public <T> T method(T a){
//TODO
}
其中 < T > 是泛型方法的标志,缺少后就不是一个泛型方法了,泛型方法可以用在泛型类和普通类中,用在泛型类中的泛型T和泛型类中的没有关系,泛型方法中的参数T是独立的,泛型参数可以有多个
class C<T,E> implements B<T,E>{
@Override
public T method() {
return null;
}
}
泛型参数没有规定命名,可以随意命名,但一般常规使用 T E K V这些
限定符和通配符
- 限定符: T extends X
- 通配符: ? extends X 或者 ? super X
限定符
当使用使用泛型使用共有代码时,比如进行比较操作时,可能传入的参数不支持compareTo方法,所以需要对传入的参数进行限定,例如:
public <T extends Comparable> int compare(T a,T b){
int result= a.compareTo(b);
return result;
}
通配符
- ? extends X 表示类型参数的上界,参数是X的子类或者X本身
- ? super X 表示类型参数的下界,参数是X的父类或者X本身
1. ? extends X
假设有如下类:
class Real<T>{}
class A{}
class B {}
class C extends A{}
class D extends C{}
class E extends D{}
public void method(Real<A> value){}
那么如果调用 method时,只能传入 泛型参数只能传入A,其他的均不可以
//正确
Real<A> realA=new Real<>();
method(realA);
//错误
Real<C> realC=new Real<>();
method(realC);
但是,如果使用 ? extends X 通配符方式,则,参数可以传入 A,以及 A的所有子类,例如:
public void method(Real<? extends A> value){}
//正确
Real<A> realA=new Real<>();
method(realA);
//正确
Real<C> realC=new Real<>();
method(realC);
对于 泛型类Real 如果包含,get和set类型参数变量,set方法不允许调用,会出现编译错误
class Real<T>{
T data;
public T getData() {
return data;
}
public void setData(T data) {
this.data = data;
}
}
Real<A> realA=new Real<>();
method(realA);
//正确 ,参数一定是A的子类或者A本身
A a=realA.getData();
//错误 ,不知道设置的参数是谁的子类
realA.setData(realA);
2. ? super X
假设有如下方法:
public void method(Real<D> value){ }
//错误
Real<A> realA=new Real<>();
method(realA);
//正确
Real<D> realD=new Real<>();
method(realD);
但如果使用 ? super X 则可以传入D以及D的所有父类
public void method(Real<? super D> value){ }
//正确
Real<A> realA=new Real<>();
method(realA);
//正确
Real<C> realC=new Real<>();
method(realC);
//正确
Real<D> realD=new Real<>();
method(realD);
对于泛型类Real来说,如果提供了get和set类型变量的话,set可以被调用,但是只能传入 X以及X的子类,get方法会返回一个Object的值
Real<D> realD=new Real<>();
method(realD);
//正确
realD.setData(new D());
//正确
realD.setData(new E());
//错误
realD.setData(new C());
Object obj=realD.getData();
泛型中的约束和局限性
- 泛型参数只能是对象类型
- 静态域或方法里不能引用类型变量,静态方法本身是类型方法可以
private static T instance; //不允许,因为会在构造函数之前执行,不能确定T的类型
private static <T> T getInstance(){} //可以
- 运行时类型查询只适用于原始类型
if(realA instanceof Real<A>) //不允许
虚拟机中是如何实现泛型的
java中的泛型,只在程序源码中存在,编译后的字节码文件中,就已经替换为原来的原生类型了,并且在相应的地方插入了强制类型转换代码,因此对于运行期的java语言来说,ArrayList< String > 与 ArrayList< Integer > 就是同一个类,所以泛型技术实际上是java语言的一颗语法糖,java语言中的泛型实现方式称为类型擦除,基于这种方式实现的泛型称为伪泛型
以下两个方法同时出现会报错
public String method(List<String> list) {
return null;
}
public String method(List<Integer> list) {
return null;
}