黑马程序员:Java基础总结
泛型(高级)
泛型(高级)
去类型化
ArrayList<String> arr1 =
new
ArrayList<String>();
ArrayList<Integer> arr2 =
new
ArrayList<Integer>();
System.
out
.println(arr1.getClass() == arr2.getClass());
// true
用反射越过泛型限制
arr2.getClass().getMethod(
"add"
, Object.
class
).invoke(arr2,
"wanqi"
);
System.
out
.println(arr2);
泛型术语
整个称为ArrayList<E> 泛型类型
ArrayList<E>中的E称为类型变量或 类型参数
整个ArrayList<Integer>称为 参数化的类型
ArrayList<Integer>中的Integer称为类型参数的实例或 实际类型参数
ArrayList<Integer>中的<>念着typeof
ArrayList称为 原始类型
整个称为ArrayList<E> 泛型类型
ArrayList<E>中的E称为类型变量或 类型参数
整个ArrayList<Integer>称为 参数化的类型
ArrayList<Integer>中的Integer称为类型参数的实例或 实际类型参数
ArrayList<Integer>中的<>念着typeof
ArrayList称为 原始类型
参数化类型与原始类型的兼容性
参数化类型可以引用一个原始类型的对象,编译报告警告,例如,Collection<String> c = new Vector();//可不可以,不就是编译器一句话的事吗?
原始类型可以引用一个参数化类型的对象,编译报告警告,例如,Collection c = new Vector<String>();//原来的方法接受一个集合参数,新的类型也要能传进去
参数化类型可以引用一个原始类型的对象,编译报告警告,例如,Collection<String> c = new Vector();//可不可以,不就是编译器一句话的事吗?
原始类型可以引用一个参数化类型的对象,编译报告警告,例如,Collection c = new Vector<String>();//原来的方法接受一个集合参数,新的类型也要能传进去
参数化类型不考虑类型参数的继承关系:
Vector<String> v = new Vector<Object>(); //错误!///不写<Object>没错,写了就是明知故犯
Vector<Object> v = new Vector<String>(); //也错误!
Vector<String> v = new Vector<Object>(); //错误!///不写<Object>没错,写了就是明知故犯
Vector<Object> v = new Vector<String>(); //也错误!
可以使用通配符显示继承关系
限定通配符的上边界:限定通配符总是包括自己。
正确:Vector<? extends Number> x = new Vector<Integer>();
限定通配符的下边界:
正确:Vector<? super Integer> x = new Vector<Number>();
正确:Vector<? extends Number> x = new Vector<Integer>();
限定通配符的下边界:
正确:Vector<? super Integer> x = new Vector<Number>();
编译器不允许创建泛型变量的数组。
即在创建数组实例时,数组的元素不能使用参数化的类型,
例如,下面语句有错误:
Vector<Integer> vectorList[] = new Vector<Integer>[10];
Vector<Integer> vectorList[] = new Vector<Integer>[10];
泛型中的?通配符
使用?通配符可以引用其他各种参数化的类型,?通配符定义的变量主要用作引用,可以调用与参数化无关的方法,不能调用与参数化有关的方法。
?表示的是任意的同一类型参数
Class<?> y = Class.forName(
"java.lang.String"
);
Class<String> x = Class.forName(
"java.lang.String"
);
//错误
Class<String> x = Class<?> y;
//错误
限定通配符的上边界:
正确:Vector<? extends Number> x = new Vector<Integer>();
错误:Vector<? extends Number> x = new Vector<String>();
限定通配符的下边界:
正确:Vector<? super Integer> x = new Vector<Number>();
错误:Vector<? super Integer> x = new Vector<Byte>();
正确:Vector<? extends Number> x = new Vector<Integer>();
错误:Vector<? extends Number> x = new Vector<String>();
限定通配符的下边界:
正确:Vector<? super Integer> x = new Vector<Number>();
错误:Vector<? super Integer> x = new Vector<Byte>();
泛型集合类的综合
HashMap<String, Integer> hm =
new
HashMap<String, Integer>();
hm.put(
"wanqi"
, 18);
hm.put(
"zhanwen"
, 28);
Set<Map.Entry<String, Integer>> set = hm.entrySet();
for
(Map.Entry<String, Integer> entry : set) {
entry.getKey();
entry. getValue();
}
自定义泛型
定义泛型方法
如:public static <T> void demo(T a,T b){}
public
static
void
main(String[] args)
throws
Exception {
f(1);
f(
'a'
);
f(1.1);
f(
""
);
f(
new
int
[1]);
}
public
static
<T>
void
f(T a) {
System.
out
.println(a.getClass().getName());
}
//结果:
//java.lang.Integer
//java.lang.Character
//java.lang.Double
//java.lang.String
//[I
需注意操作:
1,自定义泛型方法,不一定可以想加
public
<T> T add (T a,T b){
//return a+b; 未定义方法
return
null
;
}
2,只有引用类型才能作为泛型方法的实际参数
swap(new int[3],3,5);语句会报告编译错误。
3,普通方法、构造方法和静态方法中都可以使用泛型。
4,也可以用类型变量表示异常,称为参数化的异常,可以用于方法的throws列表中,但是不能用于catch子句中。
5,在泛型中可以同时有多个类型参数,在定义它们的尖括号中用逗号分,例如:
public static <K,V> V getValue(K key) { return map.get(key);}
泛型方法的练习题
1,编写一个泛型方法,自动将Object类型的对象转换成其他类型。
public
static
<T> T auto(Object obj) {
return
(T) obj; }
2,采用自定泛型方法的方式打印出任意参数化类型的集合中的所有内容。
public
static
<E>
void
print(Collection<E> cols) {
for
(E obj : cols) {
System.
out
.println(obj);
}
}
3,定义一个方法,把任意参数类型的集合中的数据安全地复制到相应类型的数组中。
public
static
<T>
void
copy(Collection<T> col, T[] arr) {
for
(
int
i = 0; i < arr.
length
; i++) {
col.add(arr[i]);
}
}
类型参数的类型推断
编译器判断范型方法的实际类型参数的过程称为类型推断,类型推断是相对于知觉推断的,其实现方法是一种非常复杂的过程。
根据调用泛型方法时实际传递的参数类型或返回值的类型来推断,具体规则如下:
1,
当某个类型变量只在整个参数列表中的所有参数和返回值中的一处被应用了,那么根据调用方法时该处的实际应用类型来确定,这很容易凭着感觉推断出来,即直接根据调用方法时传递的参数类型或返回值来决定泛型参数的类型
2,
当某个类型变量在整个参数列表中的所有参数和返回值中的多处被应用了,如果调用方法时这多处的实际应用类型都对应同一种类型来确定,这很容易凭着感觉推断出来
3,
当某个类型变量在整个参数列表中的所有参数和返回值中的多处被应用了,如果调用方法时这多处的实际应用类型对应到了不同的类型,且没有使用返回值,这时候取多个参数中的最大交集类型,
4,
当某个类型变量在整个参数列表中的所有参数和返回值中的多处被应用了,如果调用方法时这多处的实际应用类型对应到了不同的类型, 并且使用返回值,这时候优先考虑返回值的类型
5,
参数类型的类型推断具有传递性,下面第一种情况推断实际参数类型为Object,编译没有问题,而第二种情况则根据参数化的Vector类实例将类型变量直接确定为String类型
定义泛型类型
如果类的实例对象中的多处都要用到同一个泛型参数,即这些地方引用的泛型类型要保持同一个实际类型时,这时候就要采用泛型类型的方式进行定义,也就是类级别的泛型
class
Demo<T>{
private
T
a
;
public
T getA() {
return
a
;
}
public
void
setA(T a) {
this
.
a
= a;
}
}
注意:
在对泛型类型进行参数化时,类型参数的实例必须是引用类型,不能是基本类型。
当一个变量被声明为泛型时,只能被实例变量、方法和内部类调用,而 不能被静态变量和静态方法调用。因为静态成员是被所有参数化的类所共享的,所以静态成员不应该有类级别的类型参数
在对泛型类型进行参数化时,类型参数的实例必须是引用类型,不能是基本类型。
当一个变量被声明为泛型时,只能被实例变量、方法和内部类调用,而 不能被静态变量和静态方法调用。因为静态成员是被所有参数化的类所共享的,所以静态成员不应该有类级别的类型参数
通过反射获得泛型的参数化类型
Method |
getDeclaredMethod(String name, Class<?>... parameterTypes) 返回一个 Method 对象,该对象反映此 Class 对象所表示的类或接口的指定已声明方法。 |
Type[] |
getGenericParameterTypes() 按照声明顺序返回 Type 对象的数组,这些对象描述了此 Method 对象所表示的方法的形参类型的。 |
接口 ParameterizedType extends Type方法摘要 |
|
---|---|
Type[] |
getActualTypeArguments() 返回表示此类型实际类型参数的 Type 对象的数组。 |
Type |
getOwnerType() 返回 Type 对象,表示此类型是其成员之一的类型。 |
Type |
getRawType() 返回 Type 对象,表示声明此类型的类或接口。 |
private
Vector<Date>
dates
=
new
Vector<Date>();
public
void
setDates(Vector<Date> dates) {
this
.
dates
= dates;
}
public
static
void
main(String[] args)
throws
Exception {
Method method = Ts0.
class
.getMethod(
"setDates"
, Vector.
class
);
ParameterizedType pType = (ParameterizedType) method
.getGenericParameterTypes()[0];
System.
out
.println(
"setDates("
+ ((Class) pType.getRawType()).getName()
+
"<"
+ ((Class) (pType.getActualTypeArguments()[0])).getName()
+
">)"
);
}