`
阅读更多

一、简介

 

  • 在Java SE 1.5之前,没有泛型的情况的下,通过对类型Object的引用来实现参数的“任意化”,
  • “任意化”带来的缺点是要做显式的强制类型转换,
  • 而这种转换是要求开发者对实际参数类型可以预知的情况下进行的。
  • 对于强制类型转换错误的情况,编译器可能不提示错误,在运行的时候才出现异常,这是一个安全隐患。
  • 泛型的好处是在编译的时候检查类型安全,并且所有的强制转换都是自动和隐式的,提高代码的重用率。

二、泛型

 

  • 泛型的本质是参数化类型,也就是说所操作的数据类型被指定为一个参数。
  • 用于解决安全问题,是一个类型安全机制。
  • 这种参数类型可以用在类、接口和方法的创建中,
  • 分别称为泛型类、泛型接口、泛型方法。
  • 泛型是提供给javac编译器使用的
  • 如集合中的泛型可以限定输入类型
  • 让编译器挡住源程序中的非法输入
  • 编译器编译带类型说明的集合时会去掉“类型”信息,使程序运行效率不受影响
  • getClass方法的返回值和原始类型完全一样
  • 由于编译生成的字节码会去掉泛型的类型信息(泛型擦除),
  • 只要能跳过编译器,就可以往某个泛型集合中加入其他类型的数据
  • 例如:用反射得到集合,再调用其add方法即可
import java.util.*;

public class GenericTest {

	public static void main(String[] args) throws Exception{
		ArrayList<String> a1 = new ArrayList<String>();
		a1.add("abc");
		ArrayList<Integer> a2 = new ArrayList<Integer>();
		a2.add(3);
		
		System.out.println(a1.getClass() == a2.getClass());
				
		//通过反射就可以将Integer添加到a1中
		a1.getClass().getMethod("add", Object.class).invoke(a1, 5);
		System.out.println(a1);
	}
}

 

三、重要结论

 

  1. 实验发现上述例子中如果将最后一句改为:System.out.println(a1.get(1)),就会出现 java.lang.ClassCastException
  2. 通过jad工具反编译class文件得出结论:
  3. 通过反射可以绕过编译器,向指定了泛型的集合中添加其他类型的元素
  4. 但是通过get方法调用时如果泛型为String将在get方法前加上强转(String)
  5. 如果泛型是其他类型,则不会强转
  6. 如果在get得到元素后还有其他操作,即像get(1).getClass()之类的操作
  7. 也会加上对应泛型类型的强转
  8. 也就是说泛型有可能会影响到class文件的内容,并不是完全擦除泛型

 

四、初步了解泛型

 

  • ArrayList<E>类定义和ArrayList<Integer>类引用中涉及的术语
  • 整个称为ArrayList<E>泛型类型
  • ArrayList<E>中的E称为类型变量或类型参数
  • ArrayList<Integer>中的Integer称为类型参数的实例或实际类型参数
  • ArrayList<Integer>中的<>念typeof
  • ArrayList称为原始类型
  • 参数化类型与原始类型的兼容性
  • 参数化类型可以引用一个原始类型的对象,编译报告警告
  • 如:Collection<String> c = new Vector();
  • 原始类型可以引用一个参数化类型的对象,编译报告警告
  • 如:Collection c = new Vector<String>();
  • 参数化类型不考虑类型参数的继承关系
  • Vector<String> v = new Vector<Object>();//错误
  • Vecotr<Object> v = new Vector<String>();//错误
  • 在创建数组实例时,数组的元素不能实用参数化的类型,例如,下面渔具有错误
  • Vector vector1[] = new Vector[10];//正确
  • Vector<Integer> vector[] = new Vector<Integer>[10];//错误cannot create a generic array of Vector<Integer>

五、规则和限定

 

  1. 泛型的类型参数只能是类类型(包括自定义类),不能是简单类型。
  2. 泛型的参数类型可以使用extends语句,例如<T extends superclass>。习惯上称为“有界类型”。
  3. 泛型的参数类型还可以是通配符类型。例如Class<?> classType = Class.forName("java.lang.String");
  4. ?通配符,也可以理解为占位符
  5. ?extends E:可以接收E类型或者E的子类型。上限 ArrayList(Collection<? extends E> c)
  6. ?super E:可以接收E类型或者E的父类型。下限 TreeSet(Comparator<? super E> comparator)
import java.util.*;

/**
 * 定义一个方法,该方法用于打印出任意集合中的数据
 * 
 * 使用?通配符可以引用其他各种参数化的类型
 * ?通配符定义的变量主要用作引用
 * 可以调用与参数无关的方法,不能调用与参数化有关的方法
 */
public class GenericTest {

	public static void main(String[] args) {
		ArrayList<String> list = new ArrayList<String>();
		list.add("abc");
		list.add("hjx");
		printCllection(list);
		
		ArrayList<Integer> list1 = new ArrayList<Integer>();
		list1.add(5);
		list1.add(8);
		printCllection(list1);
	}

	private static void printCllection(Collection<?> collection) {
		//collection.add("d12");
		System.out.println(collection.size());
		for(Object obj : collection)
			System.out.println(obj);
	}
}
想要实现添加等操作参数的方法时,就必须写成泛型T,而不是用通配符?

 

限定通配符的上边界
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>();//错误

 

六、示例

import java.util.*;

public class GenericTest {

	public static void main(String[] args){
		HashMap<String,Integer> map = new HashMap<String, Integer>();
		map.put("zhangsan", 20);
		map.put("lisi", 18);
		map.put("wangwu", 16);
		
		printMap(map);
	}

	private static void printMap(HashMap<String, Integer> map) {
		Iterator<Map.Entry<String, Integer>> entrySet = map.entrySet().iterator();
		while(entrySet.hasNext())
		{
			Map.Entry<String, Integer> mapEntry = entrySet.next();
			String key = mapEntry.getKey();
			Integer value = mapEntry.getValue();
			System.out.println(key+":"+value);
		}
	}
}

 

七、类型推断

 

  • 编译器判断泛型方法的实际类型参数的过程称为类型推断
  • 类型推断是相对于知觉推断的,其实现方法是一个非常复杂的过程
  • 根据调用泛型方法时实际传递的参数类型或返回值的类型来推断
  •     如果不接收返回值,传入的类型相同就是这个类型,传入的类型不同则取父类的最小交集,见示例1
  •     如果要接收返回值,则优先考虑接收变量的类型,并在class文件中加入强转 ,见示例2
  •     参数类型的类型推断具有传递性 ,见示例3
示例1
public class GenericTest {

	public static void main(String[] args){
		add(5,6);	//推断为Integer
		add(5,3.0); //推断为Number
		add(5,"");	//推断为Object
	}
	private static <T> T add(T x,T y)
	{
		//return x + y; 加法运算可能不适合T类型不能用
		return null;
	}
}
 
示例2
public class GenericTest {

	public static void main(String[] args) 
	{
		String str = autoConvert("abc");//推断为String
		int x = autoConvert(45);//推断为Integer
		int x = autoConvert("adf");//推断为Integer,运行时会出现转换异常
		autoConvert(45);//推断为Object
		System.out.println(str+":"+x);
	}

	private static <T> T autoConvert(Object obj) {
		return (T)obj;
	}
}
如果将autoConvert(Object obj)改为autoConvert(T t)
int x = autoConvert(45);//推断为Integer
autoConvert(45);//推断为Integer
int x = autoConvert("adb");//不能通过编译,只能传入Integer,因为类型已经确定为Integer
 
示例3
static <T> void copy(T[] a,T[] b){
}
copy(new Integer[5],new String[5]);//推断为Object
static <T> void copy(Collection<T> a,T[] b){
}
copy(new Vector<String>(),new Integer[5]);//推断为String,所以编译不能通过,只能传入String数组
 

八、泛型方法

 

  • 泛型类定义的泛型,在整个类中有效,
  • 如果被方法使用,那么泛型类的对象明确要操作的具体类型后,所有方法要操作的类型就已经固定了
  • 为了让不同方法可以操作不同类型,而且类型还不确定,那就可以将泛型定义在方法上 
  • 用于放置泛型的类型参数的尖括号应出现在方法的其他所有修饰符之后,返回类型之前
  • 按照惯例,类型参数通常用耽搁大写字母表示
  • 除了在应用泛型时可以实用extends限定符,在定义泛型时也可以
  •     如:public <A extends Annotation> A getAnnotation(Class<A> annotationClass){}
  • 并且可以用&来指定多个边界
  •     如:<V extends Serializable & Cloneable> void method(){}
  • 普通方法、构造方法、静态方法都可以用泛型
  • 特殊之处:静态方法不可以访问类上定义的泛型(因为它优先于对象存在)
  • 如果静态方法操作的引用数据类型不确定,可以将泛型定义在方法上
  • 也可以用类型变量表示异常,称为参数化的异常,可以用于方法的throws列表中,但不能用于catch子句中
  • 在泛型中可以同时有多个类型参数,用逗号隔开,如:Map<String,Integer> 
/**
 * 定义方法打印任意类型数据
 */
public class GenericTest {

	public static void main(String[] args) 
	{
		print("haha");
		print(123);
	}

	public static <T> void print(T t)
	{
		System.out.println(t);
	}
}
 
import java.util.*;

/**
 * 定义方法交换任意类型数组中的两个元素的位置
 * 
 * 因为泛型类型只能是引用数据类型,所以基本数据类型的数组,操作不了
 */
public class GenericTest {

	public static void main(String[] args) 
	{
		String[] strs = {"123","abc","hahh"};
		swap(strs,0,1);
		System.out.println(Arrays.toString(strs));
		
		Integer[] ints = {2,5,6};
		swap(ints,1,2);
		System.out.println(Arrays.toString(ints));
	}

	public static <T> void swap(T[] t,int x,int y)
	{
		T temp = t[x];
		t[x] = t[y];
		t[y] = temp;
	}
}
 
import java.util.Arrays;
import java.util.Collection;
import java.util.Date;
import java.util.Vector;

public class GenericTest {

	public static void main(String[] args) {
		String[] strs = {"56","nihao","zhangsan"};
		fillArray(strs,"liuliu");
		System.out.println(Arrays.toString(strs));
		
		copy1(new String[10],new String[10]);
		copy2(new String[10],new Vector<String>());
		
		copy1(new String[10],new Date[10]);
		//copy2(new String[10],new Vector<Date>());//因为泛型的传递性,数组应该定义为Date型,编译失败
	}

	/**
	 * 把任意类型集合中的数据安全的复制到相应类型的数组中
	 * @param dest	目标数组
	 * @param src	源集合
	 */
	private static <T> void copy2(T[] dest,Collection<T> src) {
		int x = 0;
		for(T t : src)
		{
			dest[x] = t;
			x++;
		}
	}

	/**
	 * 把任意类型数组中的数据安全的复制到相应类型的另一个数组中
	 * @param dest	目标数组
	 * @param src	源数组
	 */
	private static <T> void copy1(T[] dest,T[] src) {
		for(int x=0;x<src.length;x++)
		{
			dest[x] = src[x];
		}
	}

	/**
	 * 该方法可以将任意类型的数组中的所有元素填充为相应类型的某个对象
	 * @param t 数组
	 * @param obj  填充的对象 
	 */
	private static <T> void fillArray(T[] t,T obj) {
		for(int x=0;x<t.length;x++)
			t[x] = obj;
	}
}
 

九、自定义泛型类

 

class Gen<T> 
{
	private T ob; //定义泛型成员变量
	public Gen(T ob) 
	{
		this.ob = ob;
	}
	public T getOb() 
	{
		return ob;
	}
	public void setOb(T ob) 
	{
		this.ob = ob;
	}
	public void showType() 
	{
		System.out.println("T的实际类型是: " + ob.getClass().getName());
	}
}
public class GenDemo 
{
	public static void main(String[] args)
	{
		//定义泛型类Gen的一个Integer版本
		Gen<Integer> intOb=new Gen<Integer>(88);
		intOb.showType();
		int i= intOb.getOb();
		System.out.println("value= " + i);
		System.out.println("----------------------------------");
		//定义泛型类Gen的一个String版本
		Gen<String> strOb=new Gen<String>("Hello Gen!");
		strOb.showType();
		String s=strOb.getOb();
		System.out.println("value= " + s);
	}
}

 

十、通过反射获取泛型的实际类型

import java.lang.reflect.*;
import java.util.*;

public class GenericTest {
	public static void main(String[] args) throws Exception
	{
		//想要获得v的泛型的实际类型,要通过一个方法
		Vector<Date> v = new Vector<Date>();
		
		Method method = GenericTest.class.getMethod("apply", Vector.class);
		Type[] types = method.getGenericParameterTypes();
		ParameterizedType type = (ParameterizedType) types[0];
		System.out.println(type.getRawType());
		System.out.println(type.getActualTypeArguments()[0]);
	}
	public static void apply(Vector<Date> v)
	{
		
	}
}

 

//按照声明顺序返回 Type 对象的数组,这些对象描述了此 Method 对象所表示的方法的形参类型的
public Type[] getGenericParameterTypes(){}

//Type 是 Java 编程语言中所有类型的公共高级接口。它们包括原始类型、参数化类型、数组类型、类型变量和基本类型
public interface Type

//ParameterizedType 表示参数化类型,如 Collection<String>,从jdk5开始
public interface ParameterizedType extends Type
{
	//返回表示此类型实际类型参数的 Type 对象的数组
	Type[] getActualTypeArguments();

	//返回 Type 对象,表示声明此类型的类或接口
	Type getRawType();
}

 

分享到:
评论

相关推荐

    泛型dao 泛型dao 泛型dao

    Struts2、Hibernate、Spring整合的泛型DAO (本人评价: 代码开发效率提高30% 代码出错率减少70%) 对于大多数开发人员,系统中的每个 DAO 编写几乎相同的代码到目前为止已经成为一种习惯。虽然所有人都将这种重复...

    1.java泛型定义.zip

    1.java泛型定义.zip1.java泛型定义.zip1.java泛型定义.zip1.java泛型定义.zip1.java泛型定义.zip1.java泛型定义.zip1.java泛型定义.zip1.java泛型定义.zip1.java泛型定义.zip1.java泛型定义.zip1.java泛型定义.zip1....

    C#泛型集合与非泛型集合

    在.NET FCL为我们提供了很多集合类型,是编程中非常有力的工具。泛型集合主要在 System.Collections....因为,1. 泛型编程是从c#2.0开始才被.net支持的。2.泛型集合在性能和类型安全方面优于非泛型集 合。 。。。。

    Java如何获取泛型类型

    参考:我眼中的Java-Type体系(1) 我眼中的Java-Type体系(2) 秒懂Java类型(Type)系统 Java 运行时如何获取泛型参数的类型 Java类型Type 之 ParameterizedType,GenericArrayType,TypeVariabl,WildcardType 从实现...

    泛型类型的写法

    泛型类型的写法1

    Java 实现泛型List

    Java 实现泛型List的源码,基本实现了List接口的全部所有方法。欢迎大家发表自己的观点和建议。

    《集合框架及泛型》

    BDQN ACCP 7.0 Java《集合框架及泛型》学习资料.part1

    looly#hutool-site#泛型类型工具-TypeUtil1

    介绍获取方法的参数和返回值类型(包括Type和Class)获取泛型参数类型(包括对象的泛型参数或集合元素的泛型类型)方法首先我们定义一个类:public cla

    Java1_5泛型.zip

    Java1_5泛型,提高你的开发效率

    C# 2.0中泛型编程思想分析

    在2005年底微软公司正式发布了C# 2.0,与C# 1.x相比,新版本增加了很多新特性,其中最重要的是对泛型的支持。通过泛型,我们可以定义类型安全的数据结构,而无需使用实际的数据类型。这能显著提高性能并得到更高质量...

    基于C++1x新标准实现泛型抽象工厂测试实例

    基于C++1x新标准实现泛型抽象工厂测试实例。用于创建多系列“相关或者相互依赖的对象”。 用户无需指定工厂和产品的具体的类型、无需了解它们的具体实现,从而绕过常规的对象创建方式。

    反射创建泛型类

    通过反射创建带有泛型参数的类 1.Limin.Reflector.DLL中的BaseDal.cs里包含要创建的带泛型参数的类 2.Limin.Reflector.Invoke中的Factory.cs完成泛型类的创建 代码写的不是很好,不足之处,请多多指教

    学习泛型语法1

    泛型语法教学 定义泛型类,定义多个泛型,定义Stack泛型类,结构类泛型, 展示泛型继承 自定义类和接口等相关内容

    华科java实验-用泛型栈实现泛型队列

    试用java.util.Stack泛型栈作为父类,用另一个泛型栈对象作为成员变量,模拟实现一个泛型子类Queue,当存储元素的第1个栈的元素超过dump时,再有元素入队列就倒入第2栈。除提供无参构造函数Queue( )外,其它所有队列...

    acm初级学习资料 C++编程

    1 泛型程序设计 22 2 STL 的组成 26 第二篇 算法篇 45 第1章 基本算法 46 1 算法初步 46 2 分治算法 51 3 搜索算法 54 4 贪婪算法 60 第2章 进阶算法 70 1 数论基础 70 2 图论算法 76 3 计算几何基础 93 第三篇 实践...

    泛型笔记.pdf

    1;继承一个泛型类时,必须为其传递泛型参数 public class Father,V&gt; -------整个叫泛型 K,V ----泛型参数 2;定义子类时直接为父类泛型参数赋值 public class Son:Father,V&gt; public class Son:Father,string&gt; ...

    泛型集合项目练习

    1)使用类的属性和方法创建相关类 2)使用泛型集合Dictionary,V&gt;组织对相关数据进行维护 3)使用泛型集合List组织对相关数据进行维护 4)使用写入器打印数据

    自定义泛型实现元素交换

    用C#代码,自定义泛型集合,泛型集合类中需要实现两个元素的交换。

    Spring/泛型Hibernate的实现

    1.实现泛型的Hibernate 2.Spring+Hibernate搭建 3.maven构建工程

    java中的泛型-详细

     1、Java泛型  其实Java的泛型就是创建一个用类型作为参数的类。就象我们写类的方法一样,方法是这样的method(String str1,String str2 ),方法中参数str1、str2的值是可变的。而泛型也是一样的,这样写class Java...

Global site tag (gtag.js) - Google Analytics