`
阅读更多

一、简介

 

  • 在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();
}

 

分享到:
评论

相关推荐

    泛型集合泛型集合泛型集合

    1. **确定类型参数**:你需要决定哪些类型应被抽象为类型参数。越多的类型参数可以使代码更灵活,但也可能增加理解难度。 2. **约束类型参数**:你可以对类型参数施加约束,比如限制它们必须是类类型、接口实现或者...

    泛型dao 泛型dao 泛型dao

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

    C#泛型C#泛型C#泛型

    1. 泛型类声明 泛型类声明是一个需要提供类型参数以形成实际类型的类的声明。类声明可以有选择地定义类型参数。例如: ``` class List&lt;T&gt; { } ``` 这个声明定义了一个泛型类 List,其中 T 是一个类型参数。这个泛型...

    C#的泛型C#的泛型

    ### C#中的泛型机制详解 #### 泛型的基本概念 C#的泛型是一种强大的功能,它允许程序员创建能够处理任何数据类型的类、接口或方法。通过泛型,可以在编写一次代码的基础上处理多种不同类型的对象,提高了代码的灵活...

    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....

    泛型java的泛型知识,非常有用

    1. **泛型的基本概念** - 泛型的本质是在类、接口或方法中使用类型参数,让它们能够处理多种数据类型。在Java中,泛型通常以尖括号 `&lt;T&gt;` 表示,其中 `T` 是类型参数,可以代表任何引用类型。 - 类如 `class Java_...

    关于java基础的泛型的练习

    1. 泛型的基本概念: - 泛型是一种参数化类型,允许在定义类、接口和方法时使用类型参数,从而创建一种可以适用于多种类型的通用代码。 - 泛型的主要目标是提供类型安全,避免在运行时出现ClassCastException。 2...

    VC++ 2005:泛型编程

    1. CLI托管类型,包括引用类型(如类)和值类型(如结构体)。 2. CLI接口类型。 3. CLI委托类型。 4. 函数(成员函数和全局函数)。 在C++/CLI中声明泛型类或方法时,使用`generic &lt;typename T&gt;`关键字,其中`T`是...

    c#泛型类、泛型方法、泛型接口、泛型委托

    1. 泛型类: 泛型类是具有一个或多个类型参数的类。这些类型参数是占位符,代表一种未知的数据类型,直到在创建类实例时提供具体类型。例如,`List&lt;T&gt;` 是一个常见的泛型类,其中 T 表示存储元素的类型。这样,我们...

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

    1. **性能问题**:由于非泛型集合存储的是 `object` 类型,这意味着当向集合中添加基本类型(如 `int`)时,这些类型会被装箱为引用类型。而在取出时,又需要进行拆箱操作。装箱和拆箱过程中会产生额外的内存分配和...

    泛型讲解 类型通配符

    1. 类型安全:泛型可以在编译时检查类型的正确性,避免了ClassCastException。 2. 代码复用:泛型可以使得代码更加通用和可重用。 3. 高度灵活性:泛型可以根据需要定义不同的类型形参,满足不同的需求。 泛型的...

    SSH泛型代码实例

    - `DAO(1).rar`可能包含使用泛型的DAO设计模式示例,展示了如何创建泛型DAO以处理不同类型的数据对象。 综上所述,SSH框架结合泛型能够提升Java应用的开发效率和代码质量。通过学习和理解这些示例,开发者可以更好...

    java 1.5泛型详解

    1. **泛型类**:在类声明中使用尖括号`&lt;&gt;`定义类型参数,如`class MyList&lt;T&gt;`,其中`T`为类型参数。 2. **泛型接口**:接口声明中也可以使用泛型,如`interface MyInterface&lt;T&gt;`。 3. **泛型方法**:在方法签名中...

    泛型笔记学习2009

    1. **类型安全**:编译器可以在编译阶段检测到类型错误,而不是在运行时抛出异常。 2. **消除类型转换**:使用泛型后,可以避免不必要的类型转换操作,使代码更加简洁明了。 3. **提升程序可读性**:泛型提供了更...

    gson解析泛型和将泛型转为json字符串

    1. **Gson与泛型解析** 当我们需要从JSON字符串反序列化到泛型类型时,可以创建一个泛型类型的`Gson`对象或使用`fromJson()`方法。例如,如果你有一个泛型列表`List&lt;T&gt;`,你可以这样做: ```java Type listType =...

    泛型和泛型集合类用法

    ### 泛型和泛型集合类用法详解 #### 一、泛型基础概念 泛型是现代编程语言中的一项重要特性,它允许开发者在不指定具体数据类型的情况下编写类或方法,从而达到代码重用的目的。在.NET Framework 2.0及以后版本中...

    c# 泛型的使用,教你如何用泛型

    1. **泛型类**:C#中的泛型类是在类定义时引入类型参数。例如,`List&lt;T&gt;`就是一个泛型类,`T`是类型参数,你可以将它替换为`int`、`string`或其他任何类型。泛型类可以提供通用的方法,如`Add(T item)`,这样无需为...

    Java泛型_Java中的泛型结构_

    1. 泛型的基本概念: - 类型参数(Type Parameter):在创建泛型类或泛型方法时使用的占位符,例如 `&lt;T&gt;`、`&lt;E&gt;` 等。 - 类型参数边界(Type Bounds):可以限制类型参数的类型,如 `class Box&lt;T extends Number&gt;`...

Global site tag (gtag.js) - Google Analytics