Java--泛型浅谈

泛型


泛型是Java SE 1.5的新特性,泛型的本质是参数化类型,也就是说所操作的数据类型被指定为一个参数。这种参数类型可以用在类、接口和方法的创建中,分别称为泛型类、泛型接口、泛型方法。 Java语言引入泛型的好处是安全简单。

我们看下以下五个关于泛型的定义 哪几个可行:




泛型一定要注意两边的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);
		}
		
	}

效果:





例2(Map集合【键值对】,键key泛型实际参数类型Integer,值value....类型String):




注意,一定要使用包装类型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泛型的操作就会了百分之八九十吧。



  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值