泛型Generic
目录一、什么是泛型?
泛型是JDK5中引入的,泛型是一种参数化类型。
泛型是限定参数类型的参数。它是参数,用于限定传递的对象的类型。
二、为什么要引入泛型?
先来看一个例子。
我们首先定义两个相似的Dog、Cat类。
class Dog{
private String name;
private int age;
public Dog(String name, int age){
this.name = name;
this.age = age;
}
//省略get
}
class Cat{
private String name;
private int age;
public Cat(String name, int age){
this.name = name;
this.age = age;
}
//省略get
}
接着在测试类中进行如下操作。
public class Test{
public static void main(String[] args){
List list = new ArrayList();
list.add(new Dog("小黄",5));
list.add(new Dog("发财",2));
//list.add(new Cat("英短",2));
for(Object obj : list){
Dog dog = (Dog)obj;
System.out.println("Dog's name: "+dog.getName()+
",Dog's age: "+dog.getAge());
}
}
}
上述例子我们在动态数组中加入了两个Dog对象。在使用for-each循环遍历这个数组时,为了访问到Dog类中的私有属性,我们需要调用Dog类中的方法。对 obj 向下转型成Dog对象。
到这里看是没有任何问题的,因为在向动态数组中添加元素时,我们清楚的知道添加的元素的类型,这似乎是没有什么问题的。但是,首先由于数据量小,我们可以清楚的知道元素的类型,那么数据量大的时候呢?还能保证每个被添加的元素的类型和我们想象中一致吗?第二,list这个对象变量是我们自己创建的,我们清楚的知道我要用它来存储什么类型的元素,对于其他人而言,他会认为这是一个任何类型元素都可以储存的数组,这会造成什么情况呢?
//假如现在我忘记了list用于存储Dog元素而往其中添加了一个Cat类元素
//上面注释掉的代码
list.add(new Cat("英短",2));
//而后继续调用上面的for-each循环
抛出ClassCastException异常。
因此引入泛型就很有必要
①保证程序运行的安全。上面就是一个例子,至于如何保证安全在后面会说到。
②节约资源。这点也会在后面说到
三、泛型的使用
3.1 泛型方法
由前面所言,我们知道:泛型是一种类型参数,用于限定在方法、类间传递参数的类型。
我们对上面的代码进行些许的改变。
改变在于在建立 对象变量list的时候多出了一个<>,Java中把这个叫做菱形运算符,在其中输入我们的类型参数。
List<Dog> list = new ArrayList<>();
以该句为例,List
在对list限制后,向list内添加Cat类元素,编译器会自己报错。
泛型的第一个好处,保证运行时安全。
在申请对象变量时对其传入参数作出限制,在传入元素类型不一致时,编译器会自动报错,避免了元素类型强制转换时错误。
第二个好处,节约资源。
![泛型好处二 节约资源](D:\桌面\MarkDown\泛型\泛型好处二 节约资源.png)
再次对上面代码进行调整,for-each中的Object obj替换成了Dog dog,在未使用泛型时,编译器为了保证减少错误,Object obj是不允许更改的。
未经过泛型限定的集合输出时元素的类型转换(以Dog为例):Dog -> Object -> Dog
经过泛型限定的集合输出时元素的类型转换 : Dog -> Dog-> Dog
3.2 泛型类
3.2.1 自定义泛型类
-
在类名后采用菱形运算符<>,并且给定参数类型的类称为自定义泛型类
-
参数类型通常用单个大写字母表示,理论上任何大写字母都可以,为了清晰的表示参数类型所指,通常使用E、K、V、T等
-
普通成员、构造函数、私有属性、数组均可以为泛型类型
-
泛型数组不能初始化
-
静态方法无法访问类上定义的泛型;如果静态方法操作的引用数据类型不确定的时候,必须要将泛型定义在方法上。因为静态属性、方法和类一起初始化,在类初始化时无法确定泛型属性或泛型方法的具体类型。
-
接上条,如果静态方法要使用泛型的话,必须将静态方法也定义成泛型方法 。
-
泛型类的类型变量在类实例化时确定
3.3 泛型接口
自定义一个泛型接口Gt01。
public interface Gt01<E,V> {
// E num; 泛型接口不能如此申明属性,因为接口属性默认为 public static final
int num = 502;
E print(E e);
}
public class GenericTest03 implements Gt01<String,Object>{
//类型变量在实现接口时确定,默认为Object
@Override
public String print(String s) {
return "null";
}
public static void main(String[] args) {
String s = new GenericTest03().print("?");
}
}
- 接口中无法定义泛型属性,因为接口属性默认为public static final
- 类型变量在实现接口时确定,默认为Object
3.4 泛型方法
1.泛型方法既可以定义在泛型类中,也可以定义在普通类中
2.泛型方法也用菱形运算发标注,但与泛型类与泛型接口不同的是,标注的位置不同。
3.泛型方法的类型变量只用于该方法中
public<T,U> void fly(){
T t;
U u; // T,U 泛型标识字母只用于该方法中,而不能用于其存在的泛型类中
System.out.println(t+u);
}
4.使用了泛型的方法和泛型方法的区别
class Test<T>{
public void hi(T t){
....
}
public<R,U> void fly(){
...
}
}//hi()为使用了泛型的方法,fly()为泛型方法
3.5 通配符
- 支持任意泛型类型
- 支持A类及其A类的子类,限定泛型上限
- 支持A类及其父类,不限于直接父亲,限定了泛型下限
四、 泛型注意事项
-
泛型在限定时只能时引用类型,不能时基本类型
List<Integer> list = new ArrayList<>();//正确 List<int> list = new ArrayList<>();//错误示范
-
在给出泛型的具体类型后,可以传入该类型或其子类类型
-
泛型定义时,后面的菱形运算符内可以省略
-
[泛型类、泛型接口、泛型方法]在不给定类型参数的情况下,默认为类型参数为Object