文章目录
泛型
泛型是 JDK5.0 增加的新特性。泛型的本质是参数化类型,即所操作的数据类型(例如String、Interger)被指定为参数。可以用在类、接口和方法的创建,分别成为泛型类、泛型接口、泛型方法。
一、 为什么使用泛型
在 JDK5.0之前,在没有泛型的情况下,通过对类型 Object 的引用来实现参数的 “任意化”,带来的缺点是:将所有类型数据都当作 object 类型存放:① 使用具体数据类型时需要强制类型转换;② 容易造成类型转换错误。
对于强制类型转换错误的情况,编译器可能不提示错误,但在运行时会出现异常,是一个安全隐患。
-
示例一
List list = new ArrayList (); list.add("hello"); list.add(6); for (object o : list) { System.out.println((String) o); // 转换错误: Integer 不能转换成 String 类型 }
-
示例二
public class NoType { private Object obj; // 定义 Object 通用类型变量 public Notype(Object obj){ // 构造函数 this.obj = obj; } public void set(Object obj) { // set 方法 this.obj = obj; } public Object getO(){ // get 方法 return obj; } }
public class Test { public static void main(String[] args){ NoType intObj = new NoType(new Integer(66)); // 创建一个传入整数的对象 NoType strObj = new NoType("Hello world"); // 创建一个传入字符串的对象 int i = (Integer)intObj.get(); // 1. 执行此句必须强制类型转换,否则报错 intObj = strObj; // 2. 此句是对象赋值,语法上正确,但语义上错误,只有在运行时才会有错 } }
值得注意的时,
- int i = (Integer)intObj.get(); 在使用时必须强制类型转换,因为get方法返回的时对象类型。
- intObj = strObj; 此句是对象赋值,语法上正确,但语义上错误,只有在运行时才会出错
使用泛型能够很好的解决上述问题。
// T 为类型参数,下面的就是引用该类型参数,类似方法中 public class NoType<T> { private T obj; // 定义泛型成员变量 public NoType(T obj){ // 构造函数 this.obj = obj; } public void set(T obj) { // set 方法 this.obj = obj; } public T get(){ // get 方法 return obj; } }
public class Test { public static void main(String[] args){ NoType intObj = new NoType(new Integer(66)); // 创建一个传入整数的对象 NoType strObj = new NoType("Hello world"); // 创建一个传入字符串的对象 int i = intObj.get(); // 1. 无需强制类型转换 intObj = strObj; // 2. 执行时将提示错误,编译无法通过 } }
二、 泛型的定义与使用
1. 泛型类
-
1.1 泛型类定义:
泛型类型参数只能是类类型,不能是基本数据类型。( 因此是integer而非int )
泛型的类型参数可以有多个// 给类增加泛型,相当于给类增加了过滤 // G 类型形参,G 用作传递给 FxClass 的实际类型的占位符,声明类型参数时,目标类型替换G即可 // G 为类型参数,下面的就是引用该类型参数,类似方法中 public class FxClass<G> { private G g; public G getG() { return g; } public void setG(G g) { this.g = g; } }
-
1.2 泛型类的继承
// 使用泛型类:使用时会根据泛型定义对操作进行可一些过滤 public class Main { public static void main(String[] args) { FxClass<String> fxClass = new FxClass<>(); fxClass.setG("自定义泛型类"); System.out.println(fxClass.getG()); Test test = new Test(); } // 泛型类的继承 class Test extends FxClass<String> { // 继承泛型类的时候要指定具体的类型实参 } // 原始类型 // 不写类型实参被称为原始类型等价于 class Test extends FxClass<object> class Test extends FxClass { }
List list = new ArrayList(); // 没有使用泛型 // 使用了泛型:没有生成新的 List 本质上还是上一个 List,只不过泛型做了规定只能装载特定的数据 List<String> stringlist = new ArrayList<>();
2. 限定类型
- 有时需要对类型参数的取值进行一定程度的限制,使数据具有可操作性。
例如,在制定参数类型时可以使用 extends 关键字限制此参数类型必须继承自制定父类或父类本身。// 限定只能时number 类型 public class BoundType<T extends Number> { T[] array; // 定义泛型成员变量 public BoundType(T[] array){ // 构造函数 this.array = array; } public double Sum(){ // 计算总和 double sum = 0.0; for(T element : array){ sum+=element.doubleValue(); // 该方法只能将 Number 类型的值转化为 double // 没有 extends Number 会报错 } return sum; } }
public class Test{ Integer []a = {1,2,3,4}; BoundType<Integer> intObj = new BoundType<Integer>(a); // 实参制定为整型 sum1 = intObj.sum(); Number []b = {1, 3.0f, 1.3, 5}; // float、double、int只要是Number类型均可计算 BoundType<Number> numObj = new BoundType<Number>(b); sum2 = numObj.sum(); }
3. 通配符
-
3.1 通配符的使用
当某一方法所传参数是泛型类时,为了更明显,该如何去标识该参数是泛型类而不是普通类呢?
// 定义了一个泛型类(将该泛型类作为下述方法的参数) public class NoType<T> { private T obj; // 定义泛型成员变量 public NoType(T obj){ // 构造函数 this.obj = obj; } public void set(T obj) { // set 方法 this.obj = obj; } public T get(){ // get 方法 return obj; } }
如果我们想用该泛型类作为参数传到下面的方法里,并标什该参数是泛型类,会想到用Object标识。测试类:
此时,会产生一个编译错误,试图将
NoTpye<Integer>
类型传递给NoType<object>
类型,类型不匹配导致编译错误。这种情况下我们就可以使用通配符解决,通配符由 " ? " 来表示,代表一个未知类型。当然,参数直接写为NoType 不限定类型也会通过不报错,但是对于该参数是一个泛型类不明显,因此我们用通配符来标识。
输出:
-
3.2 通配符的限定类型
在通配符的使用过程中,也可以用 extends 关键字限定通配符界定的类型参数的范围。
上述代码中:public class Main { public static void func(NoType<? extends Number> t){ // 通配符限定类型 System.out.println(t); } public static void main(String[] args) { NoType<Object> obj = new NoType<Object>(12); func(obj); // 这里会产生一个编译错误 NoType<Integer> intobj = new NoType<Integer>(12); func(intobj); } }
4. 泛型接口
- 定义泛型接口
interface MyList<E> { // E 作为类型形参 // 打印泛型实际类型实参的类型名称 void showTypeName(E e) }
- 实现一个泛型接口
// 通过 MyListImp 类实现一个泛型接口 public class MyListImp<E> implents MyList<E>{ public void showTypeName(E e) { System.out.println(e.getClass().getName()); } }
- 测试
public static void main(String[] args) { MyList<String> myList = new MyListImp<>(); myList.showTypeName("hello"); }
5. 泛型方法
修饰符 <T,E, … 形参> 返回值类型 方法名称 (形参列表) {
方法体,具体的逻辑实现
}
一般地,E:在集合框架中,K V:含有映射关系使用(Map),N:用于数字,T S U V:通用类型。
public class Test {
public static void main(String[] args) {
// 普通方法
public void doS(String s) {
System.out.println(s.getClass().getName());
}
// 泛型方法
public <T> void doSS(T t) { // <T> T 形参
System.out.println(T.getClass().getName());
}
}
}
三、泛型的局限性
Java 并没有真正实现泛型,是编译器在编译的时候在字节码上做了手脚,即类型擦除,这种实现理念造成 Java 泛型本身有很多漏洞。为规避这些问题,Java 对其使用做了一些约束,但还有一些由类型擦除引起地问题存在导致泛型的局限性:
1. 泛型参数只能是类类型
泛型类型参数只能是类类型,不能是基本数据类型。
擦除类型后原先的类型参数被 Object 或者限定类型替换,而基本类型是不能被对象所存储的。通常可以使用基本类型的包装类来解决此问题。( 例如用 int 的包装类 Integer )
2. 泛型类型不能被实例化
- 不能建立泛型对象
// 非法
public class NoType<T> {
private T obj;
public NoType(){
obj = new T(); // 不能建立泛型对象
// 擦除类型将泛型T替换成new Object()
}
}
- 不能建立泛型数组
public <T> T[] build(T[] a){
T[] arrays = new T[2]; // 非法,不能建立泛型数组(不能实例化泛型数组)
// 擦除类型会让该方法总是构造一个Objec[2]的数组
}
可以通过Class.newInstance 和 Array.newInstance 方法,利用反射构造泛型对象和数组。
- 不能创建一个限定类型的泛型类的数组。除非使用通配符。
NoType<String> []arrays = new NoType<String>[100]; // × 非法
NoType<?> []arrays = new NoType<?>[100]; // √
3. 静态属性/方法不能引用类型参数
在泛型类中,不能在静态变量或方法中引用类型参数。
// T为类型参数,下面的就是引用该类型参数,类似方法中
public class NoType<T> {
// 静态变量不能引用类型参数。
static T obj;
// 静态方法不能引用类型参数
static T getobj(){
return obj
}
}
尽管不能在类中的静态变量或静态方法中引用类型参数,但是可以声明静态泛型方法。
4. 泛型在异常中的限制
- 不能自定义泛型异常
public class FxException <T> extends Exception{
// 泛型类无法继承 Throwable,非法
}
-
不能捕获类型参数异常。 即不能在catch中使用类型参数。下面方法不能编译:
-
可以抛出泛型异常。可以在异常声明中使用类型参数。例如下面方法合法: