Java 泛型

一、泛型


               泛型是提供给 javac 编译器使用的,可以限定集合中的输入类型,让编译器挡住源程序中的非法输入,编译器编译带类型说明的集合时会去除掉“类型”信息,是程序运行效率不受影响,对于参数化的泛型类型,getClass() 方法的返回值和原始类型完全一样。由于编译生成的字节码会去掉泛型的类型信息,只要能跳过编译器,就可以往某个泛型集合中加入其它类型的数据。


             编程实例:在一个类型为 Integer 的集合中加入 String 对象。

public class A {

	public static void main(String[] args) throws Exception {
		Collection<Integer> collection = new ArrayList<Integer>(); // 定义一个类型信息为 Integer 的集合

		collection.getClass().getMethod("add", Object.class).invoke( // 通过反射的方法添加其它类型的数据
				collection, "DriverKing");
		
		System.out.println(collection.size());
	}
}
// 输出:1


二、Collection<E>


              (1)Collection<E> 整个称为泛型类型,其中 E 称为类型变量或类型参数。Collection<Integer> 称为参数化类型,Integer 称为类型参数的实例或实际类型参数。Collection 称为原始类型。


              (2)参数化类型与原始类型的兼容性:

  • 参数化类型可以引用一个原始类型的对象,编译报告警告。原始类型可以引用一个参数化类型的对象。例如:

             Collection<String> c = new ArrayList();

             Collection c = new ArrayList<String>();

  • 参数化类型不考虑类型参数的继承关系:

             ArrayList<String> arrayList = new ArrayList<Object>();  // 错误!  后面的实例对象不写参数化类型就没错,写了就是明知故犯

             ArrayList<Object> arrayList = new ArrayList<String>(); // 也错误!

  • 在创建数组实例时,数组的元素不能使用参数化的类型:

             ArrayList<String>[] array = new ArrayList<String>[10];    // 错误!

  • 思考下面代码会报错吗?

             ArrayList a1  = new ArrayList<String>();  // 正确

             ArrayList<Object> a2 = a1; // 正确,因为此时 a1 为 Object 类型

            

三、泛型中的通配符 “?”


         (1)由一个问题引出通配符,如果现在需要写一个方法,方法的功能是打印所有类型的集合,那么就需要传递一个集合的参数,如果参数写Collection<Object> c,那么就有一问题,当我传递实参为:Collection<Integer> c1 = new ArrayList<Integer>();,就相当于 Collection<Object> c = c1,那么很明显是不可以的。所以 jdk1.5 就提供了一种 ? 号通配符的方式。? 号就表示我可以指向任意类型。但是在用了通配符定义的类型后,在调用该类型的方法时就不能调用具有类型信息的方法。例如上面的参数改为 Collection<?> c 那么在方法内,就不能使用 c.add(); 因为 add 方法是要添加某个元素,而一个元素是属于某个类型的,在方法调用之前是不知道参数的类型信息的,所以只能调用与参数化无关的方法,不能调用与参数化有关的方法。


         编程实例:编写一个可以打印所有集合的方法。

public class A {

	public static void main(String[] args) throws Exception {
		Collection<Integer> collection = new ArrayList<Integer>(); // 定义一个类型信息为
		collection.add(1);
		collection.add(2);
		collection.add(3);
		printCollection(collection);
	}

	private static void printCollection(Collection<?> collection) {

		// collection.add("DriverKing_斌"); 与参数化有关的方法都不能调用
		// collection.add(100);
		// int size = collection.size(); // 可以调用

		for (Object object : collection) {
			System.out.println(object);
		}
	}
}


        (2)泛型中 ? 号通配符的扩展

  • 限定通配符的上边界(向上限定符)

            ArrayList<? extends Number> x = new ArrayList<Integer>(); // 正确

            ArrayList<? extends Number> x = new ArrayList<String>(); // 错误

  • 限定通配符的下边界(向下限定符)
            ArrayList<? super Integer> x = new ArrayList<Number>(); // 正确

            ArrayList<? super Integer> x = new ArrayList<Byte>(); // 错误

  • 提示:限定通配符总是包括自己

            例:

                 Collection<? super String> c1 = new ArrayList<String>();
                 Collection<? extends Number> c2 = new ArrayList<Byte>();


                    编程实例:编写方法打印出所有 Map 集合中的元素。

public class A {

	public static void main(String[] args) throws Exception {

		Map<String, Integer> map = new HashMap<String, Integer>();
		map.put("DriverKing_斌", 23);
		map.put("SuperMan", 500);
		map.put("SpiderMan", 300);

		printMap(map);
	}

	private static void printMap(Map<?, ?> map) {

		 第一种方法:根据 key 打印
		 Set<?> set = map.keySet();
		 for (Object object : set) {
		 System.out.println(object + "--->" + map.get(object));
		 }

		 第二种方法:获取 value 打印
		 Collection<?> collection = map.values();
		 for (Object object : collection) {
		 System.out.println(object);
		 }

		 第三种方法:根据 key和value的实体对象
		for (Entry<?, ?> entry : map.entrySet()) {
			System.out.println(entry.getKey() + "--->" + entry.getValue());
		}
	}
}

四、自定义泛型方法及其应用


           Java 的泛型方法没有 C++ 模版函数功能强大,仅仅是类似于 C++ 中的模版,但是这种相似性仅限于表面。Java 语言中的泛型基本上完全是在编译器中实现的,用于编译器执行类型检查和类型推断。然后生成普通的非泛型字节码。这种实现技术称为 擦出(编译器使用泛型类型信息保证类型安全。然后在生成字节码之前将其清除)。这是因为扩展虚拟机指令集来支持泛型被认为是无法接受的。这会为 Java 厂商升级 JVM 造成难以逾越的障碍。所以,Java 的泛型采用了可以完全在编译器中实现的擦出方法。


                 (1)自定义泛型方法的语法:(其中 如果有返回值 T ,则 T 的类型为  t1 和 t2 的最小公倍数,比如 t1 为 int,t2 为 float 则 T 为 Number)

public static <T> void method(T t1, T t2){
		//在返回值之前要声明 T 的类型
	}

           (2)泛型方法的实参只能是引用类型的


                  (3)除了在应用泛型的时候可以使用 extends 限定符,定义泛型的时候也可以用限定符。例:


                    public <T extends Serializable & cloneable> volid method(T t){}


           (4)普通方法、构造方法、静态方法都可以使用泛型。编译器不允许创建类型变量的数组。上面有提到。


                   (5)在泛型中,可以使用多个类型参数,用 “,” 号分割。例:public T<K,V> V getValue(K key){return map.get(key);}


           (6)在异常中,也可以使用泛型来指定异常类型。例:

private static <T extends Exception> void sayHello() throws T{
		try{
			
		}catch(Exception e){
			throw (T)e;
		}
	}

            (7)在上面写的打印所有集合的方法中,如果把通配符 ? 换成了 T 那么就可以使用参数化的方法了。但是使用通配符的方案要比泛型的方法更有效,当一个类型变量用来表达两个参数之间或者参数和返回值之间的关系式,即同一个类型变量在方法签名的两处被使用,或者类型变量在方法体代码中也被使用而不是仅在签名的时候用,才需要使用泛型方法。


五、类型参数的类型推断


              编译器判断泛型方法的实际类型参数的过程称为类型推断。具体方法:


              根据调用泛型方法时,实际传递的参数类型或返回值的类型来推断。具体规则如下:


              1、当某个类型变量只在整个参数列表中的所有参数和返回值中的一处被应用了,那么根据调用方法时该处的实际应用类型来确定。即根据调用方法时传递的参数类型或返回值类型来决定参数的类型。

                    例: static <E> void swap(E[] a, int i, int j)


              2、当某个类型变量在整个参数列表重哦该的所有参数和返回值中的多出被应用了,如果调用方法时这多处的实际应用类型都对同一种类型来确定。

                    例:static <T> T add(T a, T b)


              3、当某个类型变量在整个参数列表中的所有参数和返回值中的多处被应用了,如果调用方法时这多处的实际应用类型对应了不同的类型,且没有返回值,这是取多个参数中的最小公倍类型。

                    例:static <T> void fill(T[] a, T b)


              4、当某个类型变量在整个参数列表中的所有参数和返回值中的多处被应用了,如果调用方法时这多处的实际应用类型对应到了不同的类型,且没有返回值,这时候优先考虑返回值的类型。

                    例:static <T> T add(T a, T b)


六、通过反射来获取泛型的实际类型参数


public class A {

	public static void main(String[] args) throws Exception {

		Method method = A.class.getMethod("showType", Collection.class); // 获取方法
		Type[] types = method.getGenericParameterTypes(); // 获取参数的类型数组
		ParameterizedType parameterizedType = (ParameterizedType) types[0];// ParameterizedType 是参数化的类型
		System.out.println(parameterizedType.getRawType()); // 得到原始类型
		System.out.println(parameterizedType.getActualTypeArguments()[0]); // 得到实际的类型参数,实际类型参数可以有多个,所以返回的是数组
		System.out.println(types[0]);

	}

	public static void showType(Collection<String> collection) {
	}
}


评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值