`
lzh166
  • 浏览: 298015 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

java 泛型

阅读更多
一、泛型的基本入门
泛型(Generic type 或者generics)是对 Java 语言的类型系统的一种扩展,以支持创建可以按类型进行参数化的类。可以把类型参数看作是使用参数化类型时指定的类型的一个占位符,就像方法的形式参数是运行时传递的值的占位符一样。
可以在集合框架(Collection framework)中看到泛型的动机。例如,Map类允许您向一个Map添加任意类的对象,即使最常见的情况是在给定映射(map)中保存某个特定类型(比如String)的对象。
因为Map.get()被定义为返回Object,所以一般必须将Map.get()的结果强制类型转换为期望的类型,如下面的代码所示:
Map m = new HashMap(); 
m.put("key", "blarg"); 
String s = (String) m.get("key"); 

要让程序通过编译,必须将get()的结果强制类型转换为String,并且希望结果真的是一个String。但是有可能某人已经在该映射中保存了不是String的东西,这样的话,上面的代码将会抛出ClassCastException。
理想情况下,您可能会得出这样一个观点,即m是一个Map,它将String键映射到String值。这可以让您消除代码中的强制类型转换,同时获得一个附加的类型检查层,该检查层可以防止有人将错误类型的键或值保存在集合中。这就是泛型所做的工作。
泛型是提供给javac编译器使用的,可以限定集合中的输入类型,让编译器挡住源程序中的非法输入。
Map<String, String> m = new HashMap<String, String>();
m.put("key", "blarg");
m.put("age",100);// 错误
String s = m.get("key");

二、泛型深入接触
先贴段代码来分析一下:
public static void main(String[] args) throws Exception {
			//利用泛型规定只允许装Integer类型的对象
			ArrayList<Integer> arrayAttr = new ArrayList<Integer>();
			//利用反射向arrayAttr中加入String对象
			arrayAttr.getClass().getMethod("add", Object.class).invoke(arrayAttr, "abc");
			//依然能够正确的执行
			System.out.println(arrayAttr.get(0));
		}

输出结果为:abc
(1)因为泛型定义是给编译器看的,所以当编译器检查完毕后我们可以利用反射绕过泛型检查,来向集合中注入值。通过这里我们可以看出java虚拟机在编译的时候并不知道其定义的类型。好的,接着向下看
下面再来贴一段代码:
ArrayList<Integer> arrayAttr1 = new ArrayList<Integer>();
		
		ArrayList<String> arrayAttr2 = new ArrayList<String>();
		
		System.out.println(arrayAttr1.getClass() == arrayAttr2.getClass());//true

输出结果已在上面标注出来了。那大家知道这其中原理吗?
(2)在java编译器编译完成之后,会去掉泛型中规定的类型信息,两个对象是完全一模一样的,也就是他们的编译后的class字节码是同一份。
下面来看一下泛型中的几个特性:
1、参数化的类型与原始类型的兼容性:
(1)参数化的类型可以引用一个原始类型的对象,编译报告警告 例如:
  Collection<String> c = new Vector();
(2)原始类型可以引用一个参数化类型的对象,编译报告警告 例如
  Collection c = new Vector<String>();
2、参数化类型不考虑类型参数的继承关系
(1)Vector<String> v = new Vector<Object>();//错误
(2)Vector<Object> v = new Vector<String>();//也错误
3、参数化类型的对象只允许是引用类型的对象,基本类型(装箱/拆箱成引用类型)
(1)Vector<Integer> v = new Vector<Integer>();
   v.add(2); //2被自动装箱成Integer类型的对象
4、在创建数组实例时,数组的元素不能使用参数化的类型,例如:下面的语句有错误:
Vector<Integer> vectorList[] = new Vector<Integer>[10];//错误
通过上面的几点介绍:下面我们来一个思考题:
思考题:下面的代码会报错吗?
(1)Vector v1 = new Vector<String>();
(2)Vector<Object> v = v1;
//答案是不会的,因为(1)处是将原始类型引用一个参数化类型的对象,(2)处是参数化类型引用一个原始类型的对象,在这里千万不要混淆了。
三、泛型的通配符扩展
1、通配符的基本应用
先来看个问题:定义一个方法,该方法用于打印出任意参数化类型集合中的所有数据,该方法该如何定义?
也许我们会想到用下面的这种方式:(错误)
public static void printCollection(Collection<Object> coll) { 
			for(Object o : coll) {
				System.out.println(o);
			}
		}

但是这种方式是错误,无法实现上面的功能的,原因很简单,因为泛型中是不支持继承关系
的,比如Collection<Integer> c1 与Collection<Object> c2 两个不同类型的对象。因此是不可以用Ojbect来代替所有类型的。
下面来看使用泛型中通配符的方式:
public static void printCollection(Collection<?> coll) {
			//coll.add("abc");//不可以:因为你不知道<?>表示的是什么类型
			//coll.size();//可以,因为这个方法和具体的参数没有关系
			//coll = new HashSet<Date>()是可以的,因为你是将一个不确定参数类型变量指向一个Date参数类型对象
			for(Object o : coll) {
				System.out.println(o);
			}
		}

在上面的方法中,通过一些方法的调用,大家肯能也会发现了,使用?通配符可以引用其他各种参数化的类型,?通配符定义的变量的主要作用是做引用可以调用与参数化无关的方法,不可以调用与参数化有关的方法。
2、泛型中的?通配符的扩展
先来阐述一下Number类,它 是 BigDecimal、BigInteger、Byte、Double、Float、Integer、Long 和 Short 类等八种基本类型的父类。
(1)限定通配符的上边界:
正确:Vector<? extends Number> x = new Vector<Integer>();
错误:Vector<? extends Number> x = new Vector<String>();
(2)限定通配符的下边界:
正确:Vector<? super Integer> x = new Vector<Number>();
错误:Vector<? super Integer> x = new Vector<Byte>();
提示:限定通配符总是包括自己
//通过利用泛型练习一下map类型集合的遍历方式
		
		Map<String, Integer> maps = new HashMap<String, Integer>();
		maps.put("lzh", 22);
		maps.put("ghl", 21);
		maps.put("cyf", 33);
		
		//第一种遍历方式,遍历map中entrySet集合
		Set<Map.Entry<String, Integer>> entrySet = maps.entrySet();
		for(Map.Entry<String, Integer> entry : entrySet){
			System.out.println(entry.getKey() + ":" + entry.getValue());
		}
	
		//第二种遍历方式,遍历map中keKey集合
		Set<String> keySet = maps.keySet();
		for(String key : keySet){
			System.out.println(key + ":" + maps.get(key));
		}
		
		//第三种遍历方式,遍历map中values集合,但是不能遍历键值
		Collection<Integer> values = maps.values();
		for (Iterator iterator = values.iterator(); iterator.hasNext();) {
			Integer value = (Integer) iterator.next();
			System.out.println(value);
		}

四、自定义泛型方法
1、基本应用
(1)根据自己的理解,简单写个测试例子,判断两个数是否相等
public static <T>T eq(T x, T y){
		if(x.equals(y)){
			return x;
		}
		return y;
	}

调用方法:
在这里的参数1,1分别是int类型的,但是在真正传值的时候是传的Integer类型的(自动装箱)
int vs1 = eq(1, 1);//---1  
Number vs2 = eq(1, 1.0);//---2
Object vs3 = eq(1, '1');//---3
Object vs4 = eq(1, "1");//---4
Number vs5 = eq(1, 1L);//---5
//通过上面的例子,我们可以看出在返回值的数据类型是两个参数类型的交集,
//就是这个类型必须把传递的两种参数的类型都包括了(类型推断)

(2)交换任意给定类型数组的两个元素的顺序:
public static <T>void swap(T[] a, int i, int j) {
		T temp = a[i];
		a[i] = a[j];
		a[j] = temp;
		
		System.out.println(Arrays.asList(a));
	}

调用代码段:
swap(new String[]{"abc", "lzh","xyz"}, 1,2);
		//swap(new int[]{1,2,3},1,2);//这里会报错,这是什么原因呢?
		//原因是只有引用类型才能作为泛型方法的实际参数,在这里java并不会帮我们自动装箱,Int[]数组本身就是个对象

总结:除了在应用泛型的时候可以用extends限定符,在定义泛型时也可以使用extends限定符
例如:Class.getAnnotation()方法的定义,并且可以用&来指定多个边界,
如<V extends Serializable & cloneable> void method(){}
1、普通方法,构造方法和静态方法中都能使用泛型。
2、也可以用类型变量表示异常,成为参数化的异常,可以用于方法的throws列表中,但是不能用于catch
字句中。
private static <T extends Exception> sayHello() throws T {
			try {
			//不能写catch(T e); 
			} catch(Exception e) {
				throw (T)e;
			}
		}

3、在泛型中可以同时有多个类型参数,在定义他们的尖括号中用逗号分隔,例如:
public static<K,V> V getValue(return map.get(key));

下面利用泛型在来做一下上面我们所做过的例子:打印出任意参数化集合类型中的内容
public static <T>void printCollection2(Collection<T> coll, T obj2) {
			for(Object obj : coll) {
				System.out.println(obj);
			}
			coll.add(obj2);//只是告诉大家这里可以通过泛型进行此操作
		}

2、类型推断
a、当某个类型只在整个参数列表中的所有参数和返回值中的一处被应用了。那么根据调用方法时该处的实际应用类型来确定
  eg.swap(new String[3],3,4)----> static <E> void swap(E[],int i,int j)
b、当某个类型变量在某个参数列表中的所有参数和返回值中的多次被应用了。如果调用方法时这多处的实际应用类型都对应同一种类型类确定
  eg.add(2,4)----->static <T> T add(T a, T b)
c、当某个类型变量在整个参数列表和参数值中多处被应用了,如果调用方法时这多处的实际应用类型对应了不同的的类型,且没有返回值,这时取这多个参数中的最大交集类型
eg. fill(new Integer[3],1.3f)----> static <T> void fill(T[] a,T v)
d、当某个类型变量在整个参数列表和参数值中多处被应用了,如果调用方法时这多处的实际应用类型对应了不同的的类型,且有返回值,这时根据其返回值的类型
eg. int x = add(3,2.3f)-----> static <T> T add(T a, T b)
f、参数类型推断具有传递性

eg.copy(new Integer[5],new String[5]) -----> static <T> void copy(T[] a,T[] b)
   //编译通过,类推断实际类型参数为Object
   copy(new Vector<String>(),new Integer[5])------> static <T> void copy(Collection<T> a, T[] b)
   //编译出问题,原因是其根据参数化得Vector实例将变量类型直接确定为String,所以在Integer——>复制时将出现问题
五、自定义泛型类
public class GenericDao<T> {
		public void add(T obj) {
			
		}
		public T find(int id) {
			return null;
		}
	}

当一个变量或方法被声明为泛型的时候,只能被实例变量和方法调用,而不能被静态变量和静态方法调用。因为静态成员变量是被所有参数化的类的对象所共享的。
public class GenericDao<T> {
		//原始方法
		public void add(T obj) {
			
		}
		//是错误的,
		public static void add(T obj) {
			
		}
		//修正上面的方法;
		public static <T> void add(T obj) {
			
		}		
	}

最后我们来简单研究一下,java到底如何通过泛型得到已知参数类型的?
如:Vector<Date> v1 = new Vector<Date>();
下面我们如何通过反射来得到其v1对象中泛型参数类型呢?
首先我们来否定一种情况,取得class文件得到其对应的泛型参数是行不通的,原因他们是同一份字节码,所以无法实现,
下面我们就转变一下思想,我们可以通过反射得到某个方法中,同样也可以得到某个方法的参数、参数类型...那么我们就假设有个一个方法,并且参数为我们所要得到的定义泛型的参数类型对象,定义方法如下:
public static void applyVector(Vector<Date> v1) {
		
	}

下面就通过反射来得到其参数的泛型定义的类型
具体代码如下:
public static void main(String[] args) {
		//通过反射得到指定名称的方法对象
		Method applyMethod = GenericTest.class.getMethod("applyVector", Vector.class);
		//得到这个方法的"泛型的参数化类型"返回一个数组,因为这个方法可能有多个参数 
		Type[] types = applyMethod.getGenericParameterTypes();
		//因为我们知道我们的方法中只有一个参数所以:types[0]
		ParameterizedType pType =(ParameterizedType) types[0];
		//得到第一个参数的参数化类型(可以简单理解为:就是泛型),是一个数组,因为可能有多个例如:Map<K,V>
		System.out.println(pType.getActualTypeArguments()[0]);
		
		//得到第一参数的真实类型:Vector
		System.out.println(pType.getRawType());
	}

分享到:
评论

相关推荐

    Java泛型的用法及T.class的获取过程解析

    Java泛型的用法及T.class的获取过程解析 Java泛型是Java编程语言中的一种重要特性,它允许开发者在编写代码时指定类型参数,从而提高代码的灵活性和可读性。本文将详细介绍Java泛型的用法 及T.class的获取过程解析...

    Java泛型三篇文章,让你彻底理解泛型(super ,extend等区别)

    Java 泛型详解 Java 泛型是 Java SE 5.0 中引入的一项特征,它允许程序员在编译时检查类型安全,从而减少了 runtime 错误的可能性。泛型的主要优点是可以Reusable Code,让程序员编写更加灵活和可维护的代码。 ...

    Java泛型应用实例

    Java泛型是Java编程语言中的一个强大特性,它允许我们在定义类、接口和方法时指定类型参数,从而实现代码的重用和类型安全。在Java泛型应用实例中,我们可以看到泛型如何帮助我们提高代码的灵活性和效率,减少运行时...

    很好的Java泛型的总结

    Java泛型机制详解 Java泛型是Java语言中的一种机制,用于在编译期检查类型安全。Java泛型的出现解决了Java早期版本中类型安全检查的缺陷。Java泛型的好处是可以在编译期检查类型安全,避免了运行时的...

    java 泛型类的类型识别示例

    综上所述,虽然Java泛型在编译后会进行类型擦除,但通过上述技巧,我们仍然能够在运行时获得关于泛型类实例化类型的一些信息。在实际开发中,这些方法可以帮助我们编写更加灵活和安全的代码。在示例文件`GenericRTTI...

    java泛型技术之发展

    Java泛型是Java编程语言中的一个关键特性,它在2004年随着Java SE 5.0的发布而引入,极大地增强了代码的类型安全性和重用性。本篇文章将深入探讨Java泛型的发展历程、核心概念以及其在实际开发中的应用。 1. **发展...

    SUN公司Java泛型编程文档

    Java泛型是Java编程语言中的一个关键特性,它在2004年随着JDK 5.0的发布被引入。这个特性极大地提高了代码的类型安全性和可读性,减少了在运行时出现ClassCastException的可能性。SUN公司的Java泛型编程文档,包括...

    java 泛型接口示例

    下面我们将详细探讨Java泛型接口的相关知识点。 1. **泛型接口的定义** 泛型接口的定义方式与普通接口类似,只是在接口名之后添加了尖括号`&lt;T&gt;`,其中`T`是一个类型参数,代表某种未知的数据类型。例如: ```java...

    java 泛型方法使用示例

    下面我们将深入探讨Java泛型方法的概念、语法以及使用示例。 **一、泛型方法概念** 泛型方法是一种具有类型参数的方法,这些类型参数可以在方法声明时指定,并在方法体内部使用。与类的泛型类似,它们提供了编译时...

    Java 泛型擦除后的三种补救方法

    Java 泛型是一种强大的工具,它允许我们在编程时指定变量的类型,提供了编译时的类型安全。然而,Java 的泛型在运行时是被擦除的,这意味着在运行时刻,所有的泛型类型信息都会丢失,无法直接用来创建对象或进行类型...

    java泛型的内部原理及更深应用

    Java泛型是Java编程语言中的一个强大特性,它允许在定义类、接口和方法时使用类型参数,从而实现参数化类型。这使得代码更加安全、可读性更强,并且能够减少类型转换的必要。在“java泛型的内部原理及更深应用”这个...

    JAVA泛型加减乘除

    这是一个使用JAVA实现的泛型编程,分为两部分,第一部分创建泛型类,并实例化泛型对象,得出相加结果。 第二部分用户自行输入0--4,选择要进行的加减乘除运算或退出,再输入要进行运算的两个数,并返回运算结果及...

    java泛型学习ppt

    "Java 泛型学习" Java 泛型是 Java 语言的类型系统的一种扩展,以支持创建可以按类型进行参数化的类。泛型的主要目标是提高 Java 程序的类型安全。通过知道使用泛型定义的变量的类型限制,编译器可以在一个高得多的...

    Java泛型使用详细分析.pdf

    Java 泛型使用详细分析 Java 泛型是 Java 语言中的一种类型系统特性,允许开发者在编译期检查类型安全,以避免在运行时出现类型相关的错误。在本文中,我们将详细介绍 Java 泛型的使用方法和实现原理。 一、泛型的...

    Java泛型技术之发展.pdf

    Java泛型是Java编程语言中的一个关键特性,它在2004年随着Java SE 5.0的发布而引入,极大地增强了代码的类型安全性和重用性。本篇文章将深入探讨Java泛型的发展历程、核心概念以及其在实际开发中的应用。 1. **发展...

    Java泛型类型擦除后的补偿

    本文将深入探讨Java泛型类型擦除的概念,并介绍在类型擦除后,为了保持泛型的安全性和便利性,Java设计者所采取的一些补偿机制。 1. **类型擦除**: - 在编译期间,所有的泛型类型信息都会被替换为它们的实际类型...

Global site tag (gtag.js) - Google Analytics