Java学习之泛型及应用场景

一、泛型概括

泛型的本质是参数化类型,也就是说所操作的数据类型被指定为一个参数的方式传递,类似于方法中的变量参数。可以用在类、接口、方法的创建中,分别简称为泛型类、泛型接口、泛型方法。

在没有使用泛型的情况下,如果要实现参数“任意化”,通常会定义成Object类型来接受,然后强制类型转换使用;而强制类型转换有明显的缺点,就是必须要知道实际参数的具体类型的情况才可以进行转换,同时在强制转换的过程中,编译器不会报错提示的,只有在运行阶段才会出现异常,一定程度上存在安全隐患。

例子:

public class GenDemo {
	
	//如果x变量要接受不同数据类型,即通用的成员变量,比如同时接sh:String、Boolean、Integer……
	//这样每次在getX方法调用时,都要进行类型强制转换才能使用。
	//如果多次调用setX方法进行赋值时,可能还会出现类型转换异常java.lang.ClassCastException
	private Object x;

	public Object getX() {
		return x;
	}

	public void setX(Object x) {
		this.x = x;
	}
	
}
public static void main(String[] args) {
		GenDemo genDemo1 = new GenDemo();
                GenDemo genDemo2 = new GenDemo(); 
		GenDemo genDemo3 = new GenDemo();
		//多次调用setX方法
		genDemo1.setX(233);
		genDemo2.setX("gen");
		genDemo3.setX(false);
		
		//必须强制类型转换才能输出实际参数,同时存在类型转换异常的隐患
		Integer x = (Integer)genDemo3.getX();
		System.out.println(x);

	}

输出异常:

Exception in thread "main" java.lang.ClassCastException: java.lang.Boolean cannot be cast to java.lang.Integer
	at collections.GenTest.main(GenTest.java:13)

由于GenDemo类中X成员变量需要接受不同数据类型,定义成了Object类型,在创建GenDemo对象后,调用多次setX方法进行赋值初始化,值的实际类型有Integer、String、Boolean,最后进行了强制类型转换,在整个编译过程中,没有报任何错误提示,结果在运行时报出ClassCastException异常了。可见使用Object实现类型任何化,存在很大安全隐患。

接下来上面的代码,使用泛型进行改造下,体验下泛型带来的方便:

public class GenDemo<T> {
	
	private T x;

	public T getX() {
		return x;
	}

	public void setX(T x) {
		this.x = x;
	}
	
}

public static void main(String[] args) {
		
		GenDemo<String> demo1 = new GenDemo<String>();
		GenDemo<Integer> demo2 = new GenDemo<Integer>();
		GenDemo<Boolean> demo3 = new GenDemo<Boolean>();
		//下面这行代码在编译过程中,会提示报错
//		GenDemo<Integer> demo3 = new GenDemo<Boolean>();
		demo1.setX("111");
		demo2.setX(22);
		demo3.setX(false);
		
		 String x = demo1.getX();
		 System.out.println(x);

	}

可见,通过泛型实现数据类型的任意化,即灵活、又有安全性,易于维护。通过上面的例子可以证明,在编译之后程序会采取去泛型化的措施。也就是说Java中的泛型,只在编译阶段有效。在编译过程中,正确检验泛型结果后,会将泛型的相关信息擦出,并且在对象进入和离开方法的边界处添加类型检查和类型转换的方法。也就是说,泛型信息不会进入到运行时阶段。

二、泛型的使用

泛型的使用方式,可以在类、接口、方法中使用,分别简称之泛型类、泛型接口、泛型方法

泛型类

/**
 * 
 * @param <T> 此处的T可以随便写,相当于任意类型的标识符,除了T外,还可以用K、V、I……表示
 * 在实例化中需指定T的数据类型,如果不指定,默认是Object类型
 */
public class GenDemo<T> {
	
	//定义泛型成员变量,此处的T是外部指定的,即GenDemo<T>中的T。
	private T x;

	/**
	 * @return 返回是数据类型是T,同样也是外部指定的
	 */
	public T getX() {
		return x;
	}

	/**
	 * setX方法中赋值形参类型是T,是外部指定的。
	 */
	public void setX(T x) { 
		this.x = x;
	}
	
}

实例化输出

// 指定泛型的具体类型,这样指定为String
GenDemo<String> demo1 = new GenDemo<String>();
demo1.setX("ok");

String x = demo1.getX();
System.out.println("泛型测试:" + x);
		 
//输出:		 
泛型测试:ok
在使用类泛型过程中,是不是一定指定泛型的具体类型吗,其实并不是绝对的,也可不传入泛型的具体类型,具体情况是要根据类泛型的有没有设置限制条件,比如有没有 使用extends语句’、intercept……。 如果不传入指定泛型的具体类型的话,默认情况下Object类型。看下面的例子可知

		
// 没有指定类泛型的具体类型,默认是Object
//此时编译器并不会报错,当会警告语提示:
//GenDemo is a raw type. References to generic type GenDemo<T> should be parameterized
GenDemo demo1 = new GenDemo();
demo1.setX("ok");
//从getX方法返回的值,可见是Object类型
Object x = demo1.getX();
System.out.println("泛型测试:" + x);
		
		
//输出:
泛型测试:ok

上面代码虽然可以编译运行通过,但警告语提示你,参考泛型GenDemo <T>应参数化,所以开发中若使用类泛型,建议是泛型具体参数化传入。

泛型接口

泛型接口和泛型类的使用方法基本是一样的,一般泛型接口都有具体的实现类来完成方法的调用。

/**
 * 
 * 定义接口时指定了一个类型形参,该形参名为E
 * 此处的E也可以任意标识
 * @param <E>
 */
public interface Gen<E> {

	//该接口方法,返回E形参的实例
	E getGenInfo();
	
	//在接口方法中,E可作为类型使用
	void addGenInfo(E e);

}

实现泛型接口:

public class GenImpl implements Gen<String>{
	
	private String content;

	public String getGenInfo() {
		return content;
	}

	public void addGenInfo(String e) {
		this.content=e;
	}


}

泛型方法

泛型方法是指在调用该方法时指明泛型的具体类型,相对于泛型类和泛型接口会更复杂些。

泛型方法的定义:

public class GenDemo<T> {

	/**
	 * 泛型方法的说明
	 * 1.Class<T>的作用是声明泛型的具体类型
	 * 2.clazz是用来创建泛型类T的对象
	 * 3.clazz.newInstance()创建泛型类T的对象
	 * 4.<T>T:尖括号外的T的表示返回类型是T,
	 * 5.<T>:声明该方法为泛型方法,这个很重要,如果方法中没有改标识说明不是泛型方法
	 * @param clazz
	 * @return
	 * @throws InstantiationException
	 * @throws IllegalAccessException
	 */
	public <T>T getGenInfo(Class<T> clazz) throws InstantiationException, IllegalAccessException{
		T t = clazz.newInstance();
		return t;
	}
	
}

泛型方法的调用:

/**
 * Class.forName("collections.GenImpl")就是指定了泛型的具体类型
 * info:是指GenImpl类的实例
 */
GenDemo<GenImpl> genDemo = new GenDemo<GenImpl>();
Object info = genDemo.getGenInfo(Class.forName("collections.GenImpl"));

泛型方法的基本使用

/**
 * 定义的泛型类GenericMethodDemo<T>
 */
public class GenericMethodDemo<T> {

	//定义泛型成员变量,T的具体类型由外部泛型类中指定的
	private T x;
	


	/**
	 * 这是一个普通方法,并不是泛型方法,只不过它返回的
	 * 是泛型类中已经声明过的泛型的具体类型
	 */
	public T getX() {
		return x;
	}

	/**
	 * 是一个普通方法,把泛型T以形参的方式设置值
	 */
	public void setX(T x) {
		this.x = x;
	}
	
	
	/**
	 * 此方法是一个泛型方法,是一个没有返回参的泛型方法
	 * 在viod前面带有了<E>泛型方法的标识,尖括号内的字母是任意写,A、B、C……都可以
	 * 如果把void的<E>去掉,编译会报错提示E cannot be resolved to a type,意思就是无法解析E
	 * 如果把GenDemo<E>的E去掉,编译会警告语,提示GenDemo<E>应参数化
	 */
	public <E>void setGenericInfo(GenDemo<E> e){
		System.out.println(e.getX());
	}
	
	
	
	/**
	 * 此方法是一个泛型方法,是一个带返回类型的T的泛型方法
	 * 和上面的描述基本一样的,都带了泛型方法的标识符<T>,说明是泛型方法
	 */
	@SuppressWarnings("unchecked")
	public <T>T getGenericInfo(){
		//强制类型转换了,实际具体看情况,这里只是说明泛型方法的定义
		return (T) x;
	}
	

}

静态方法中的泛型

如果在静态方法中使用泛型的话,是无法访问在类中定义的泛型类型,必须把静态方法定义成静态方法。

public static <E>void setGenericInfo(GenDemo<E> e){
		System.out.println(e.getX());
}
	

总结:

泛型方法能独立于类而产生变化,总之,需记住一点,定义泛型方法一定要有<T>标识符。

三、类型通配符

类型通配符就是匹配任意类型的形参。

类型通配符是一个问号(?),将一个问号作为类型形参传递给List集合,写作:List<?>,(意思是元素类型未知的List)。

这个问号(?)被成为通配符,它的元素类型可以匹配任何类型。

例子:

public void test(List<?> c){
  for(int i =0;i<c.size();i++){
    System.out.println(c.get(i));
  }
}

调用:

List<String> list = new ArrayList<String>();
list.add("333");
List<Integer> list2 = new ArrayList<Integer>();
list2.add(3334);
test(list);
test(list2);

上限通配符

如果想限制使用泛型类别时,只能用某个特定类型或者是其子类型才能实例化该类型时,可以在定义类型时,使用extends关键字指定这个类型必须是继承某个类,或者实现某个接口,也可以是这个类或接口本身。

//它表示集合中的所有元素都是Shape类型或者其子类
List<? extends Shape>

这就是所谓的上限通配符,使用关键字extends来实现,实例化时,指定类型实参只能是extends后类型的子类或其本身。

下限通配符

如果想限制使用泛型类别时,只能用某个特定类型或者是其父类型才能实例化该类型时,可以在定义类型时,使用super关键字指定这个类型必须是是某个类的父类,或者是某个接口的父接口,也可以是这个类或接口本身。

//它表示集合中的所有元素都是Circle类型或者其父类
List <? super Circle>
这就是所谓的下限通配符,使用关键字super来实现,实例化时,指定类型实参只能是extends后类型的子类或其本身。

四、类型擦除

例子:

Class c1=new ArrayList<Integer>().getClass();
Class c2=new ArrayList<String>().getClass();
System.out.println(c1==c2);

输出:

true
这是因为不管为泛型的类型形参传入哪一种类型实参,对于Java来说,它们依然被当成同一类处理,在内存中也只占用一块内存空间。从Java泛型这一概念提出的目的来看,其只是作用于代码编译阶段,在编译过程中,对于正确检验泛型结果后,会将泛型的相关信息擦出,也就是说,成功编译过后的class文件中是不包含任何泛型信息的。泛型信息不会进入到运行时阶段。


已标记关键词 清除标记
©️2020 CSDN 皮肤主题: 技术黑板 设计师:CSDN官方博客 返回首页