Java中的泛型

前言

泛型是jdk1.5之后出现的新特性,其本质是参数化类型(type parameters),通过参数化类型让代码可以应用于多种类型。

"泛型"这个概念产生的目的是希望类或方法能够具备最广泛的表达能力,怎么做呢,正是通过解耦类或方法与之所使用的类型之间的约束来达成的。

如何理解泛型呢?

让我们以方法的参数作为切入点开始这个话题:

//方法
private static void saySomeThing(String parameter){
		System.out.println(parameter);
}

//使用
String par = "hello,world";
saySomeThing(par);

上述代码中,在方法定义阶段saySomeThing方法中的parameter称之为形参,它没有任何具体的值。而在方法调用阶段saySomeThing(par),方法中传入的参数par叫做实参,它有具体的值"hello,world"。

引申到泛型中,以ArrayList为例:

//ArrayList类定义阶段
public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{……}

//使用阶段
ArrayList<String> list = new ArrayList<String>();

类定义阶段:

  • ArrayList<E>称为泛型类型
  • ArrayList是原始类型
  • <>译为"typeof"
  • E称为类型变量或者类型参数(理解成形式类型参数更简单些),它没有具体的类型,使用的时候类型作为参数传入

类使用阶段:

  • ArrayList<String> 称为参数化的类型
  • String称为实际类型参数,此处实际类型参数为String类型

这样就对泛型有了初步了解,接下来将会了解泛型的好处和泛型的使用。

泛型的意义

泛型通过类型参数的方式指定元素的类型,这样就能把类型不匹配的错误在编译阶段暴露出来。比如ArrayList中add():Integer类型的数据,在get()方法中却把获取的元素赋值给String类型的变量,这就会导致程序运行的时候出现类型不匹配的错误。

泛型的使用

泛型的定义

public class Generic<T>{
		private T arg1;
		public Generic(){}
		public Generic(T s){
			arg1 = s;
		}
		public T getArg(){
			return arg1;
		}
	}

类声明后<>中的这个T叫做类型参数,可以指代任意类型,这里可以用T也可以用A,T只是一个代表。不过因为JDK规范,类型参数用单个的大写字母来代替,通常如下,

  • T:代表任意类型(S、U也可以)
  • E:表示Element或是异常
  • K:与V搭配使用,通常用于表示键值对中的key
  • V:与K搭配使用,通常用于表示键值对中的value

泛型中可以拥有多个类型参数,在定义它的<>中对多个参数使用逗号隔开即可。注意,使用时泛型中的实际类型参数只能是引用类型

泛型类

一个泛型类(generic class) 就是具有一个或多个类型变量的类。

public static class Generic2<T> {
		private T arg1;

		public Generic2() {
		}

		public Generic2(T s) {
			arg1 = s;
		}

		public T getArg() {
			return arg1;
		}
}
//使用
Generic2<String> g2 = new Generic2<String>("str");
System.out.println(g2.getArg().getClass().getTypeName());

Generic2<Integer> g3 = new Generic2<Integer>(1);
System.out.println(g3.getArg().getClass().getTypeName());

拥有多个类型参数的泛型类,在定义它的<>中对多个参数使用逗号隔开即可。

class Generic3<K, V> {
	private K k;
	private V v;
	public Generic3() {}
	public Generic3(K ka, V va) {
		k = ka;
		v = va;
	}
	private String get() {
		return k.toString()+v.toString();
	}
}

再来个内部类也是泛型类的例子。

class LinkedStack<T> {
	private static class Node<U> {
		U item;
		Node<U> next;

		public Node() {
			item = null;next = null;
		}

		public Node(U item, Node<U> next) {
			this.item = item;this.next = next;
		}
	}
	private Node<T> top = new Node<T>();

	public boolean end(){return top.item == null || top.next == null;}
	public T pop(){
		T result = null;
		if (!end()){
			result = top.item;
			top = top.next;
		}
		return result;
	}
	public void push(T item){
		top = new Node<T>(item,top);
	}
}

测试:

public class Generic4Demo {
	public static void main(String[] args) {
		LinkedStack<String> stack = new LinkedStack<>();
		for (String s : "hello world I am coming".split(" ")) {
			stack.push(s);
		}
		String str;
		while ((str = stack.pop()) != null) {
			System.out.println(str);
		}
	}
}
结果:
coming
am
I
world
hello

上述实例中使用链表的方式实现了一个向下压栈的Stack,使用单链表数据结构实现。

泛型方法

泛型方法让该方法具备了能独立于类而产生变化的能力,将泛型参数列表放到返回值之前即可定义泛型方法。注意:static方法无法访问泛型类的类型参数。

//泛型类
class Generic3<T> {
	private T t;

	public Generic3() {
		t = null;
	}

	public Generic3(T t) {
		this.t = t;
	}

	//泛型类中的方法具备访问泛型类的类类型参数的能力
	public T get() {
		return t;
	}

	public void setT(T t) {
		this.t = t;
	}

	//将泛型参数列表(<E>)放在返回值之前(定义泛型方法),让这个静态方法具备泛型的能力
	public static <E> ArrayList<E> getArrayList() {
		return new ArrayList<E>();
	}
}

//test
public class Generic3Demo {
	public static void main(String[] args) {
		//泛型类中使用的方法具备访问泛型类的类型参数的能力
		Generic3<String> g = new Generic3<String>();
		g.setT("java");
		System.out.println(g.get());
		
		//普通类中的泛型方法的使用
		new Generic3Demo().f(new Generic3<Integer>());
		
		//具备泛型能力的静态方法
		ArrayList<String> list = Generic3.getArrayList();
		list.add("aaa");
		System.out.println(list.get(0));
	}

	//普通类中的泛型方法
	public <T> void f(T t) {
		System.out.println(t.getClass().getName());
	}
}

泛型限定

无边界通配符(Unbounded Wildcards):

声明格式:<?>, 比如List<?>。

无边界的通配符的主要作用就是让泛型能够接受未知类型的数据。?通配符定义的变量主要用作引用,可以调用与参数化无关的方法,不能调用与参数化有关的方法。比如集合中的add()方法,使用了?的集合就不能再调用这个方法。

public void printCollection(Collection<?> col){}

也就是说Collection<?> col = new ArrayList<String>();编译器让这个表达式被编译通过。  

上边界通配符(Upper Bounded Wildcards):

声明格式:<? extends E>

使用上边界通配符的泛型,就能够接受指定类及其子类类型的数据。 这里的E就是该泛型的上边界,表示实现或者继承E的所有类都可以使用,此时E就是上边界。

下边界通配符(Lower Bounded Wildcards):

声明格式:<? super E>

使用下边界通配符的泛型,就能够接受指定类及其父类类型的数据。这里的E就是该泛型的下边界.

注意:

  • 只能一个泛型指定上边界或下边界,不能同时指定上下边界。
  • 定义泛型的时候也可以限定类型如:public <A extends Serializable&cloneable> void method(){}
  • 普通方法、构造方法和静态方法中都可以定义泛型。编译器不允许创建类型变量的数组
  • 可以用类型变量表示异常,称为参数化的异常,可以用于方法的throws列表,但是不能用于catch子句中。

泛型的擦除

java中的泛型是假泛型,在字节码阶段所有泛型的类型信息都会被擦除,比如ArrayList 和ArrayList的字节码都是ArrayList.class,字节码阶段并不存在ArrayList.class 和ArrayList.class。,如果强行操作,idea会提示错误。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8luC2L6d-1668923670663)(%E2%80%BB%20Res/generic_01.png)]

也正是由于泛型的擦除,所以我们可以通过反射实现List<String> list = new ArrayList<String>()的对象list中存取Integer类型的对象。

public class GenericListReflectDemo {
	public static void main(String[] args) {
		List<String> list = new ArrayList<String>();

		try {
			Method addMethod = List.class.getDeclaredMethod("add", Object.class);
			addMethod.invoke(list,2);
			Method getMethod = List.class.getDeclaredMethod("get", int.class);
			System.out.println(getMethod.invoke(list,0));
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
}

注意

参数化类型不考虑类型参数的继承问题。也就是说泛型类型中的类型参数,等号两边只要不是同一种,那么编译就一定不会通过。

泛型的参数类型只能是引用类型。

创建数组实例时,数组的元素不能使用参数化的类型。因为,编译器是严格按照步骤走的。它检测每一行代码的信息是否有误,但是不会用执行时候的答案来检测代码的正确性。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值