泛型
我们看下以下五个关于泛型的定义 哪几个可行:
泛型一定要注意两边的E保持一致,如果不用两边,保持一边一致。
由于泛型是Java 1.5引入的,也就是1.5以前,都没有泛型的概念
因此,为了向下(1.5+ ---> 1.4-)兼容
List<String> list2 = new ArrayList(); //这行代码是可行的
同理,为了向上(1.4- ---> 1.5+)兼容
List list3 = new ArrayList<String>(); // 这行代码是可行的
demo说明:
//非泛型方法
public void a(List list) {
//1.5以前,程序员不知道有泛型这个概念
}
//泛型方法
public void b(List<String> list){
//1.5+以后,泛型在程序中随处可见
}
public void test1(){
b(new ArrayList());//一个不知道泛型是什么的老程序员调用一个用泛型方法
a(new ArrayList<String>());//一个学会用泛型的程序员调用一个非泛型方法
}
上述代码,在编译期间,不会报错,为的就是兼容新老版本。知道有这种写法就行。
泛型的使用
例1(List集合,泛型实际参数类型为String):
public static void main(String[] args){
List<String> list = new ArrayList<String>();
list.add("aa");
list.add("bb");
list.add("cc");
//1、传统的取出泛型集合list中的元素 --> 迭代器迭代(next)出元素(String 类型)
Iterator<String> it = list.iterator();
while(it.hasNext()){
String s = it.next();
System.out.println(s);
}
System.out.println("-------------------------");
//2、加强for(for-each)遍历list中的String元素
for(String s : list){
System.out.println(s);
}
}
效果:
注意,一定要使用包装类型Integer,而不是基本数据类型int(报错),Java有自动装箱和拆箱操作。
看下完整demo:
public static void main(String[] args){
Map<Integer, String> week = new HashMap<>();
week.put(1, "Mon");
week.put(2, "Tue");
week.put(3, "Wen");
/*传统获取Map中的键值对的方法 -->entrySet()
Set类似于Map,只不过Set集合中只有键key,没有键值value
感兴趣的朋友可以看下我写的Python3学习系列
里面有介绍集合set和dict,dict就相当于Java的Map
*/
Set<Entry<Integer, String>> set = week.entrySet();
Iterator<Entry<Integer, String>> it = set.iterator();
while(it.hasNext()){
Entry<Integer, String> entry = it.next();
int key = entry.getKey(); //自动完成拆箱,getKey()返回一个包装类型的Integer对象
String value = entry.getValue();
System.out.println(key+" : "+value);
}
System.out.println("------------------");
//加强for(for-each),遍历Map中的entrySet
for(Entry<Integer, String> entry : week.entrySet()){
System.out.println(entry.getKey()+" : "+entry.getValue());
}
}
效果:
注意,HashMap是一个无序集合,比如,添加的是1、2、3,可能取出的是2、1、3。(当然,拿Int打个比方)
如果想要使用有序Map集合,我们需要改成LinkedHashMap:
比如,购物车里面的商品,就可以用一个顺序的LinkedHashMap集合存放,而不能用无序的HashMap。
泛型类和泛型方法
泛型变量T、K、E未定义就使用了,因此上述泛型方法定义错误
我们看下正确的定义方式:
对比类,也一样,如果定义类的一开始就定义了这些泛型变量T,K,E,则类中的非静态方法和成员变量可以直接使用泛型变量:
因此,我们需要给上述泛型类中的静态泛型方法B也加上泛型变量的定义,定义后才能使用(切记规则):
通过反射reflect获得泛型类的相关参数化类型
由于性能驱使,Java在编译泛型类的时候,对应.class文件中,会擦除掉有关类的泛型参数类型<Integer,String>的字眼,比如:
我们看到,类DemoTest是一个泛型类,参数化类型一个是Integer,另一个是String,但是我们getClass的时候,和普通的Class一样
我们看到,类DemoTestA继承自泛型类DemoTest,打印子类的父类的时候,显示了DemoTest的类型为generic,表示父类是一个泛型类,但是,泛型的参数化类型<Integer,String>却没有体现出来,因此,如果,在编译的时候,重载方法的参数为泛型类的对象的时候,你就要小心了,因为编译的时候会擦出掉参数化类型,下面的代码是编译不通过的:
但是,你换成其他类型,就可以通过了:
那我们怎么拿到泛型类的实际泛型参数化类型呢?
通过Java的reflect 反射技术
下面的讲解主要以demo为主,涵盖注释
泛型类:Week<T,K> ---->父类,T,K并没有被实际赋予其参数类型
方法 :A 无泛型实际参数化类型,简单点就是无参数类型,但是返回值是一个泛型参数化类型
方法 :B 正好和A颠倒,本篇主要讲解A,方法B的反射同理A
Week.java:
package com.appleyk.generic;
import java.util.Map;
public class Week<T,K>{
public Map<T, K> A() {
System.out.println("父类:get week");
return null;
}
public void B(Map<T, K> week) {
System.out.println("父类:set week");
}
}
子类 :W,继承自Week<T,K>,由于,子类要具体化父类,因此,这里我们要指定父类的泛型实际参数化类型为
Week<Integer,String>,如果你继承泛型类,泛型类的参数类型都不指定的话,鬼晓得你要干嘛,意义何在?
Demo3.java:
package com.appleyk.generic;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
class W extends Week<Integer,String>{
public Map<Integer, String> A() {
System.out.println("子类:get week");
return null;
}
public void B(Map<Integer, String> week) {
System.out.println("子类:set week");
}
}
public class Demo3 {
public static void main(String[] args) throws Exception{
//getGenericSuperclass() 获得带有泛型的父类
//ParameterizedType 实际泛型参数化类型
ParameterizedType type =(ParameterizedType)W.class.getGenericSuperclass();
System.out.println(type);
System.out.println("---------------");
//getActualTypeArguments() 获取参数化类型数组,泛型可能有多个
Type[] t = type.getActualTypeArguments();
System.out.println("泛型类Week的实际泛型参数化数组的元素:");
for(Type paramType : t){
//打印父类泛型的实际参数类型
System.out.println("#"+paramType);
}
System.out.println("获得子类W的相关泛型信息---------------");
W w = new W();
w.A();
w.B(new HashMap<Integer,String>());
System.out.println("子类W的类---------------");
System.out.println(w.getClass());
System.out.println("父类Week的类---------------");
System.out.println(w.getClass().getSuperclass());
System.out.println("反射方法A的参数泛型信息---------------");
Method methodA = W.class.getMethod("A", null);
Type[] mt = methodA.getGenericParameterTypes();
System.out.println("A的泛型参数类型数组长度="+mt.length); //方法A的泛型参数类型数组长度为0
for(Type parT : mt){
System.out.println(parT); //显然方法A没有使用泛型参数,这个for不会执行
}
System.out.println("反射方法A的返回参数的泛型信息---------------");
//getGenericReturnType 获得泛型丰富的返回值类型(注意不是一个 Type[])
Type gReturnType = methodA.getGenericReturnType();
if(gReturnType instanceof ParameterizedType)//如果返回值类型是一个实际泛型参数类型的话(有意义)
{
//转化一下类型,将方法的泛型返回值类型转化为实际泛型参数化的类型(-->比如Week<Integer,String>)
Type[] gTypes = ((ParameterizedType) gReturnType).getActualTypeArguments();
//for遍历
for(Type gType : gTypes){
System.out.println("返回值的实际泛型参数类型:"+gType);
}
}
}
}
注释很详细了,这里我就不一一讲解了,不要看着demo一麻团,其实没使用多少类,多少方法,反射泛型信息的,也就那么几个常用的对象和方法:
ParameterizedType 实际泛型参数化类型
getGenericSuperclass() 获得带有泛型的父类
getActualTypeArguments() 获取参数化类型数组(Type[]),泛型可能有多个
getGenericParameterTypes() 获得方法的泛型参数化类型(Type[])
getGenericReturnType() 获得方法的泛型返回类型(注意不是一个Type数组,需要另外类型转换)
我们看下,执行效果:
最后,我们写一个小面试题,结束我们今天的泛型讲解
题目:利用泛型编写一个方法,实现指定数组的元素反转。
注意,是指定数组,并没有说是String类型的还是Int类型的数组,另外,注意,使用泛型的时候,一定要用包装类型,比如,int类型用Integer,而不用基本数据类型int
答案:
package com.appleyk.generic;
public class demo4 {
//第一个元素和倒数第一个元素互换,第二个元素和倒数第二个元素互换,依次类推
public static<T> T[] reverse(T[] arr){
int start = 0; //第一个元素
int end = arr.length-1 ; //第二个元素
T temp ; //临时变量
while(true){
if (start>=end) {
break;//如果start 和 end 碰头了,意味着两边元素的交换已经到尽头了
}
temp = arr[start];
arr[start] = arr[end];
arr[end] = temp;
start++;
end --;
}
return arr;
}
public static void main(String[] args){
String[] names ={"a","b","c","d"};
names = reverse(names);
for(String s: names){
System.out.println(s);
}
System.out.println("---------泛型的使用--------");
Integer[] ages = {1,2,3,4};
ages = reverse(ages);
for(Integer s: ages){
System.out.println(s);
}
}
}
验证下:
本篇虽然有点长,但是从头到尾耐心的跟我所讲的走一遍,一定会收获不少,泛型在Java中是很重要的一个角色,掌握了本篇所讲,基本上Java泛型的操作就会了百分之八九十吧。