简介
泛型是Java SE 1.5的新特性,泛型的本质是参数化类型,也就是说所操作的数据类型被指定为一个参数。这种参数类型可以用在类、接口和方法的创建中,分别称为泛型类、泛型接口、泛型方法。 Java语言引入泛型的好处是安全简单。
详请请查询---百度百科
- 常用的泛型例子
- 为什么使用泛型
- 泛型的使用
- 泛型的通配符&上限下限
- 泛型的特性
- 泛型数组
一: 常用的泛型例子
1.1 集合的泛型规范
public static void main(String[] args) {
List<String> names = new ArrayList<String>();
names.add("jack");
names.add("rose");
}
我们指定集合names
为String类型的集合,因此在编译期间,此集合只能添加String类型的数据,做到集合的规范作用
1.2 类范围规范
class Meat { }
class Hamburger extends Meat { }
class MacHamburger extends Hamburger{}
public void main() {
Info<? extends Meat> temp0 = new Info<>(new MacHamburger());//上限
Info<Meat> temp1 = new Info<>(new Meat());
Info<Hamburger> temp2 = new Info<>(new Hamburger());
Info<MacHamburger> temp4 = new Info<>(new MacHamburger());
Info<? super MacHamburger> temp5 = new Info<>(new Meat());//下限
}
class Info<T> {
private T var;// 定义泛型变量
Info(T var){ this.var=var; }
public void setVar(T var) { this.var = var; }
public T getVar() { return this.var; }
public String toString() { return this.var.toString(); }// 直接打印
}
通过泛型来控制Info
类可以指定的食物,不像以前简单的指定一种类型,我们可以通过?
通配符和extends 上限
,super 下限
来实现一定范围的类规范。比如:<? extends Meat>
只能传入Meat类及其子类,或比如:<? super MacHamburger>
只能传入MacHamburger类及其父类。
二:为什么使用泛型
从上面的集合例子来讲,在Java SE 1.5之前 是没有这个特性的。那如果需要同时有string规范的集合,又有int规范的集合等,岂不是要有很多个新的List类来进行匹配,这样子的话类的数量会增加很多,而且不好做统一维护。
三:泛型的使用
1.1 泛型类
//此处T可以随便写为任意标识,常见的如T、E、K、V等形式的参数常用于表示泛型
//在实例化泛型类时,必须指定T的具体类型
public class Generic<T>{
//key这个成员变量的类型为T,T的类型由外部指定
private T key;
public Generic(T key) { //泛型构造方法形参key的类型也为T,T的类型由外部指定
this.key = key;
}
public T getKey(){ //泛型方法getKey的返回值类型为T,T的类型由外部指定
return key;
}
}
泛型的类型参数只能是类类型Interger
,不能是简单类型int
泛型如果不传入泛型实参的话,就不会起到限制作用,泛型类的泛型方法和成员变量定义的类型可以为任何类型
1.2 泛型接口
用法基本与泛型类相同
//定义一个泛型接口
public interface Generator<T> {
public T next();
}
实现泛型接口的类,未传入泛型实参,此泛型类与普通泛型类定义相同,需要将泛型的声明一起加入类中
class FruitGenerator<T> implements Generator<T>{
@Override
public T next() {
return null;
}
}
实现泛型接口的类,传入泛型实参,则所有使用泛型的地方都要替换成传入的实参类型
public class FruitGenerator implements Generator<String> {
private String[] fruits = new String[]{"Apple", "Banana", "Pear"};
@Override
public String next() {
Random rand = new Random();
return fruits[rand.nextInt(3)];
}
}
1.3 泛型方法
泛型类 是在实例化的时候指明泛型的具体类型
泛型方法 是在调用方法的时候指明泛型的具体类型
泛型方法能使方法独立于类的产生变化
只有声明了<T>的方法才是泛型方法,泛型类中使用了泛型的成员方法并不是泛型方法
//泛型类
class Test<T> {
//成员方法
public void funA(T t){}
//泛型方法1
public <K> void funB(K k){}
//泛型方法2 此T是方法泛型T 不是类泛型T
public <T> void funC(T t){}
//泛型方法3 泛型方法不仅可以使用自身泛型,也可以用类泛型
public <K> void funD(K k,T t){}
}
可变参数
public <T> void printMsg( T... args){
for(T t : args){
Log.d("泛型测试","t is " + t);
}
}
printMsg("111",222,"aaaa","2323.4",55.55);
静态方法无法使用类泛型,静态方法若要使用泛型,必须让其成为泛型方法,尽管如此,依然无法使用类泛型
public class StaticGenerator<T> {
//编译错误
public static void show(T t){}
public static <T> void show(T t){}
}
四:泛型的通配符&上限下限
1.1 通配符
Integer是Number的子类,List<Integer>和List<Number>实际上也是同一种基本类型。但是Generic<Number>作为形参,能否使用Generic<Integer>的实例传入呢?
public void showKeyValue1(Generic<Number> obj){}
Generic<Integer> gInteger = new Generic<Integer>(123);
Generic<Number> gNumber = new Generic<Number>(456);
showKeyValue(gInteger);//编译错误
showKeyValue(gNumber);
同一种泛型可以对应多个版本(因为参数类型是不确定的),不同版本的泛型类实例是不兼容的
那如何解决上面的问题,达到同时表示Generic<Integer>和Generic<Number>父类的引用类型呢?类型通配符为此而来
public void showKeyValue1(Generic<?> obj){}
类型通配符一般用?
表示,代替具体的类型实参,如果单写?
可以简单理解是Object作为实参。
1.2 泛型上限下限
泛型的上下线顾名思义就是限制泛型的使用范围。
extends为上限
super为下限
//通配符 只能使用Number本身及其Number的子类的泛型
public void showKeyValue1(Generic<? extends Number> obj){}
//泛型类
public class Generic<T extends Number>{
private T key;
public Generic(T key) {
this.key = key;
}
public T getKey(){
return key;
}
}
Generic<String> a = new Generic<String>("11111");//错误
//泛型方法的边界必须在泛型声明的时候添加
public <T extends Number> T showKeyName(Generic<T> fn){
T test = fn.getKey();
return test;
}
五:泛型的特性
泛型仅在编译阶段是有效
例子:
List<String> list1 = new ArrayList<String>();
List<Integer> list2 = new ArrayList<Integer>();
Class class1 = list1.getClass();
Class class2 = list2.getClass();
if(class1.equals(class2)){
Log.d("pmm","类型相同");
}
在编译期间,正确检查泛型后,编译器会将泛型的相关信息擦出,并且在对象进入和离开方法的边界处天价类型检查和类型转换的方法。即泛型信息不会进入到运行阶段
泛型类型在逻辑上看似是多个不同的类型,实则相同
六:泛型数组
在java中 不能创建一个确切的泛型类型的数组
List<String>[] ls = new ArrayList<String>[10];//错误
List<?>[] ls = new ArrayList<?>[10];//可以
List<String>[] ls = new ArrayList[10];//可以
例子1:
List<String>[] lsa = new List<String>[10]; // Not really allowed.
Object o = lsa;
Object[] oa = (Object[]) o;
List<Integer> li = new ArrayList<Integer>();
li.add(new Integer(3));
oa[1] = li; // Unsound, but passes run time store check
String s = lsa[1].get(0); // Run-time error: ClassCastException.
这种情况下,由于JVM泛型的擦除机制,在运行时JVM是不知道泛型信息的,所以可以给oa[1]赋上一个ArrayList而不会出现异常,但是在取出数据的时候却要做一次类型转换,所以就会出现ClassCastException,如果可以进行泛型数组的声明,上面说的这种情况在编译期将不会出现任何的警告和错误,只有在运行时才会出错。
而对泛型数组的声明进行限制,对于这样的情况,可以在编译期提示代码有类型安全问题,比没有任何提示要强很多。
例子2:
List<?>[] lsa = new List<?>[10]; // OK, array of unbounded wildcard type.
Object o = lsa;
Object[] oa = (Object[]) o;
List<Integer> li = new ArrayList<Integer>();
li.add(new Integer(3));
oa[1] = li; // Correct.
Integer i = (Integer) lsa[1].get(0); // OK
上面采用通配符的方式是被允许的:数组的类型不可以是类型变量,除非是采用通配符的方式,因为对于通配符的方式,最后取出数据是要做显式的类型转换的。
PS:本文
整理
自以下博客
java 泛型详解-绝对是对泛型方法讲解最详细的,没有之一
Java总结篇系列:Java泛型
若有发现问题请致邮 caoyanglee92@gmail.com