目录
力求通俗易懂,便于记忆。
(一)泛型方法
编程中经常会遇到相同的方法,相同的结构,只是变量类型不同。通常可以如下多写几个方法,但是代码量很大,如下例中的getprint():
public class Test {
public static String getprint(String s) {
return s;
}
public static Integer getprint(Integer i) {
return i;
}
public static ArrayList<String> getprint(ArrayList<String> l) {
return l;
}
public static void main(String[] args) {
System.out.println(getprint("abc"));
System.out.println(getprint(123));
ArrayList<String> list = new ArrayList<>();
list.add("aaa");
list.add("bbb");
System.out.println(getprint(list));
}
}
/*
运行结果:
abc
123
[aaa, bbb]
*/
既然只是类型不同,那有没有一种“类型变量”呢?
我们可以用java提供的泛型来实现这种“类型变量”,以达到简化代码的目的。ps:不知道为翻译成泛型,我们可以理解成“广泛的类型”,或者理解成“通用类型”。
像其他变量一样,使用泛型也必须要申明,1、在方法的返回值类型前加<T>,申明泛型T。当然也可以是其他字母,泛型习惯上使用大写的T、V、E等。2、上例中我们把那些变化的类型:String、Integer、 ArrayList<String>用T代替。简化成如下代码:
public class Test {
public static <T> T getprint(T s) {return s;}
public static void main(String[] args) {
System.out.println(getprint("abc"));
System.out.println(getprint(123));
ArrayList<String> list=new ArrayList<>();
list.add("aaa");
list.add("bbb");
System.out.println(getprint(list));
}
}
是不是简化很多?这种含有泛型申明<T>的方法我们把他称之为:泛型方法,其中T称之为泛型。
这里需要说明下:getprint(T s)我们例子中传入了三个参数:"abc"、123、list,但并没有指定类型。是的,java自动判断了传入参数的类型,当传入"abc"时,java会判断传入了String类型,并自动将T替换成String,变成了一个String类型的方法,再传入参数:
public static String getprint(String s) {
return s;
}
结论:泛型是一种变量
1、泛型方法:像其他变量一样使用泛型必须申明一个或多个泛型,即在方法的返回类型前加<T>,用以申明泛型T,表示该方法可以使用泛型T。
2、泛型方法在传入参数时就会确定泛型T的类型,并将T替换成对应类型。
3、泛型T可以为其他字母,但通常习惯用单个大写字母T、V、E等。
(二)泛型类
既然方法可以用泛型(通用类型),那么类呢?当然是可以的,先看代码:
public class Study {
public static void main(String[] args) {
Test01 test01 = new Test01("abc");
System.out.println(test01.getKey());
//
Test02 test02 = new Test02(123);
System.out.println(test02.getKey());
//
Test03 test03 = new Test03(new ArrayList<>(Arrays.asList("aaa", "bbb")));
System.out.println(test03.getKey());
}
}
class Test01 {
private String key;
public Test01(String key) {this.key = key;}
public String getKey() {return key;}
public void setKey(String key) {this.key = key;}
}
class Test02 {
private Integer key;
public Test02(Integer key) {this.key = key;}
public Integer getKey() {return key;}
public void setKey(Integer key) {this.key = key;}
}
class Test03 {
private ArrayList<String> key;
public Test03(ArrayList<String> key) {this.key = key;}
public ArrayList<String> getKey() {return key;}
public void setKey(ArrayList<String> key) {this.key = key;}
}
/*
运行结果:
abc
123
[aaa, bbb]
*/
Test01、Test02、Test03三个类有相同的方法,相同的结构,只是变量类型不同。
将上述三个类简化成一个泛型类,1、申明一个泛型,在类名后加<T>。2、把上述三个类不同的类型:String、Integer、 ArrayList<String>替换成T。简化得到如下代码:
public class Study {
public static void main(String[] args) {
Test<String> test01=new Test<String>("abc");
System.out.println(test01.getKey());
//
Test<Integer> test02=new Test<Integer>(123);
System.out.println(test02.getKey());
//
Test<ArrayList<String>> test03=new Test<ArrayList<String>>(new ArrayList<>(Arrays.asList("aaa", "bbb")));
System.out.println(test03.getKey());
}
}
class Test<T> {
private T key;
public Test(T key) {this.key = key;}
public T getKey() {return key;}
public void setKey(T key) {this.key = key;}
}
需要说明下,泛型类跟普通类比较,泛型类需要在申明时类名后用尖括号指定类型:
普通类:Test test=new Test();
泛型类:Test<String> test =new Test<String>();//等号后面那个尖括号里的String可以不要
尖括号指定String类型后,java系统会将该泛型类中的T替换成了String,自动生成了普通类。
class Test {
private String key;
public Test(String key) {this.key = key;}
public String getKey() {return key;}
public void setKey(String key) {this.key = key;}
}
如果泛型类申明时不指定类型,类型自动默认为Object。
结论:
1、申明泛型:泛型类的类名后面一定要加<T>以申明泛型T,表示该类可以使用泛型T了。
2、申明泛型类:需要在申明时类名后用尖括号指定类型:java在申明后泛型类会将T替换成指定类型生成普通类。Test<String> test =new Test<String>();
3、尖括号里的变量类型不能是基础变量类型:byte、short、int、long、float、double、char、boolean,必须用对应的:Byte、Short、Integer、Long、Float、Double、Character、Boolean。
4、泛型T可以为其他字母,但通常习惯用大写字母T、V、E等。
(三)泛型申明<T>
- 类似于变量申明,在类名后面或者方法返回类型前面加<T>,即表示申明了一个泛型T。该泛型T可以分别用于类或者方法中,在被调用前T自动替换成对应的类型。<T>也称为"泛型标识符"。
- 最简单的泛型方法:public static <T> void p(){}//申明了泛型T但未使用
1、泛型申明的作用域
public class Stdudy<T> {
/*
* 方法前无<T>,泛型类中的方法,其中的T类型与泛型类的T类型一致。
*/
public T gogo(T t) {return t;}
/*
* 方法前有<T>,泛型方法,其中的T类型是独立的,不受泛型类的T类型约束
*/
public <T> T go(T t) {
return t;
}
public static void main(String[] args) {
String s = "abc";
new Stdudy().go(s);
}
}
上例中<T>出现两次,也就是泛型T被申明了两次,第一次是类名后,第二次是在方法go()前。
泛型其实就是一种变量,类的泛型T只能影响除go()方法外的所有T,go()方法的T相当于局部变量独立使用,不受类泛型T的影响。
静态方法只能使用泛型方法,泛型类无法影响静态方法中的泛型T。
2、申明多个泛型
泛型可以申明多个,需在泛型标识符里申明。并在方法或类中应用。如:
public class TestMethod {
public static <T,R> void print(T t,R r){
System.out.println(""+t+r);
}
public static void main(String[] args) {
print("abc",123);
}
}
public class TestMethod<T,R> {
public void print(T t,R r){
System.out.println(""+t+r);
}
public static void main(String[] args) {
new TestMethod<String,Integer>().print("abc",123);
}
}
3、泛型可以在申明时限定使用范围
extends:表示类型上界。例:A extends B,表示类型必须是B或B的子类
super:表示类型下界。例:A super B,表示类型必须是B或B的父类
&:接口
public static <I extends Number & Serializable & Cloneable> I getI(I i){
return i;
}
(四)问号“?”与泛型的关系
- “?”是泛型通配符。但他不是泛型。好比是张三的苹果,不能说苹果就是张三。
- “?”通常是独立使用,与泛型无关,也可以与泛型配合使用。
- “?”不是泛型,因此不能用于泛型申明。
- 泛型实际是一种类型占位符,在调用泛型方法或者泛型类之前就确定了类型,调用前先将泛型替换成了对应的类型再调用。因此泛型实质是确定的类型。ps:通俗说法便于理解别较真
- “?”是不确定的类型,他表达的是一个类型范围。因此“?”定义的list,由于类型范围的问题,插入会校验时会报错。例如:List<?> list=new ArrayList<String>();(后面会详细讲原理。)
1、“?”用于初始化赋值。
- 初始化赋值时,会校验初始化值的类型是否在<>的范围内,<?>的上限值是Object,下限值是无穷小的子类,因此任何类型都在其范围内。
- <? extends Number>表示初始化值上限值是小于等于Number,下限值是无穷小的子类,因此Integer在其范围中。
- <? super Integer>表示初始化值上限值是小于等于Object,下限值是大于等于Integer,因此Integer在其范围中。
Class<?> classType = Class.forName("java.lang.String");
List<?> listString = new ArrayList<String>();
List<? extends Number> extendsNumber = new ArrayList<Integer>();
ArrayList<? super Integer> superInt = new ArrayList<Integer>();
2、“?”用于限定值的更新
这个可能不好理解,先复习一个基础问题List<Number> list=new ArrayList<>();
这个list在插入值时,java会做一个校验,判断<>中的下限是什么,我们的插入值类型必须小于等于这个下限,否则报错。这个例子中插入值必须小于等于Number,即必须是Number或者他的子类。
一个结论:在List中插入或更新值的类型必须是<>中类型下限或者类型下限的子类
List<?> listString中<?>下限是无穷小子类,也就是没下限。因此很尴尬,listString无法插入除null以外的任何值。同理List<? extends Number>下限也是无穷小子类。因此也不能插入除null以外的任何值。
令人难以相信的是ArrayList<? super Number> numberList中,numberLIst的类型下限是Number,因此得出结论,插入值必须是Number或者他的子类。ps:是不是有点头晕,哈哈哈~~~
//除null值外,都不能插入;可以读取
List<?> listString = new ArrayList<String>();
//除null值外,都不能插入,可以读取
List<? extends Number> extendsNumber = new ArrayList<Integer>();
//可以插入,读取数字
ArrayList<? super Number> superInt = new ArrayList<Number>();
合并1、2的结论:
- “?”通过自身或者extends或者super能限定一个范围(上限、下限)。
- 在赋值时,校验值尖括号里的类型是否在“?”限定的范围内。
- 在更新或插入值时,校验值的类型是否是“?”的下限的类型或者是下限的子类类型。
3、“?”可以独立用于参数范围的限定
参数传入时等同于赋值给参数,遵循第1条《“?”用于初始化赋值》的相关规则。
参数传入后遵循第2条规则。
public class Test {
public static void go(List<? extends Number> dest){
System.out.println(dest);
}
public static void main(String[] args) {
go(new ArrayList<>(Arrays.asList(111,222)));
}
}
4、“?”通配符也可以和泛型配合
public static <T> void go(List<? extends T> dest){
System.out.println(dest);
}
(五)泛型数组和泛型接口
1、使用泛型的数组不能直接初始化
错误:T[] tarr=new T[10];
正确:(用反射方式建立)
public static <T> T[] createArray(Class<T> clazz, int length) {
return (T[]) Array.newInstance(clazz, length);
}
public static <T> T[] createArray(T t, int length){
return (T[]) Array.newInstance(t.getClass(), length);
}
2、泛型应用于接口,跟类差不多,不再累述。