Java泛型全面解析(雷惊风)

一、概述。

   前段时间,有同事说想要了解一下Java中的泛型相关知识,想想自己对泛型也不是特别了解,只是简单的应用而已,作为一个有追求的工程师,怎么能够这个样子呢。正好借此机会,也了解了一下,并抽时间整理出来,也能使自己记得更牢一些。我最后会将后续文章中的代码实例上传到网上供大家下载,并附上下载地址

二、知识点。

1.概念。

   泛型是JDK1.5中引入的,在代码编译期就会对代码进行检查,后会将泛型去掉(擦除),Class文件中是不包含泛型信息的,不进入运行阶段。常用的英文符合有T、E、K、V、N,其中T为Type,泛指所有Java类型中的某一种,也是我们最常见最常用的一种,;E为Element,常用在集合类中,集合中存放的都是元素;K为Key,键值对中的key,V为Value,键值对中的value;N为Number,常用作数值类型。另外我们也可以自定义,如S、U等。泛型个人理解就是通过泛型,我们可以将类型动态化,不在局限于一种,举个例子,我们在开发过程中定义一个方法:private void setPrice(int price){},方法参数中price的数据类型为int类型,它是不可变的,这时候如果我们添加一种需求就是这个产品的价格也可能是float类型的,按照我们通常的开发习惯估计80%的人就会重载一个setPrice(float price)的方法。但是有了泛型,这个问题就简单了,我们可以这样定义一个方法:public <T extends Number> void set(T price){}

这样我们就可以实现,一个方法又可以传int 又可以传float,看一下代码:

public <T extends Number> void set(T price){
		System.out.println("Log_"+price);
	}
Main m = new Main();
m.set(11);
m.set(11.11);

输出:

Log_11
Log_11.11

我们通过泛型实现了在一个方法中传递不同数据类型的数据,编译器不报错。这是在方法中的应用。在类中其实也是一样的,看一下:

public class Person<T>{
	private T a;
	public Person(T a){
		this.a=a;
	}
}
A<String> a=new A<String>("aaa");
A<Integer> b=new A<Integer>(111);

通过在类中使用泛型,我们可以为Person中的T指定不同的数据类型。这就是泛型的作用。泛型可以作用在接口上,如,Java源码中的集合父类public  interface  Collection<E> extends Iterable<E>,也可以作用在类和方法上,上边也已经说了。

 

2.与Object对比

   可能你会说Object也能实现上边的功能,没错,但是Object是不安全的,而且需要进行强制转换。而泛型可以很好的解决上面的问题,例如,在List中如果我们不使用泛型,它默认接收的就是Object类型的,我们可以看一下:

List list=new ArrayList();
list.add("zhangsan");
list.add(111);
System.out.println((String)list.get(0));
System.out.println((String)list.get(1));//编译时不抱错,运行时报错。

在我们添加数据时,添加了不同类型的数据,编译器不会报错,因为List不指定泛型类型时默认接收Object,但是如果在输出时并不知道,就会很容易出现ClassCastException错误,所以说他是不安全的,很容易出问题,而且再取数据时,必须进行强制转化。下边看一下泛型实现:

List<String> list=new ArrayList<String>();
list.add("zhangsan");
//list.add(111);//编译期就会报错;
System.out.println(list.get(0));
System.out.println(list.get(1));

同样的代码,只不过给List加上了泛型,编译器就会在编译期自动检查代码是否符合要求并指出错误。提高了代码的正确性与安全性,并且在我们取数据是不用进行强制类型转换的,编译器已经知道它是String类型了。

3.应用

   泛型的应用最常见的就是在类与方法上的应用。在这里简单说一下,首先了解一下泛型中的几个东西,extends、super、?,相信这几个东西是大家最不好理解的了。extends、super用于定义有界类型,extends用于定义类型的上限,表明参数化的类型可能是所指定的类型或者所指定类型的子类型;super定义类型的下限,表名参数化的类型可能是所指定的类型或者所指定的类型的父类型,一直到Object,因为Object为所有类型的基类。“?”定义不确定的类型,表示当前类型可能是任何一种类型。我会在下边的讲解中涉及到,大家慢慢理解。

首先我们来定义几个类:

public class Person {
	public String name;
	public Person(String name) {
		this.name = name;
	}
	public void printName() {
		System.out.println("Log_" + name);
	}
}
public class BlackPerson extends Person{

	public BlackPerson(String name) {
		super(name);
		// TODO Auto-generated constructor stub
	}
}
public class YellowPerson extends Person{

	public YellowPerson(String name) {
		super(name);
		// TODO Auto-generated constructor stub
	}
}
public class Zhangsan extends YellowPerson{

	public Zhangsan(String name) {
		super(name);
		// TODO Auto-generated constructor stub
	}

}

我们定义了四个类,一个Person类做为顶级父类,两个Person直接子类BlackPerson、YellowPerson,还有一个YellowPerson的子类Zhangsan,这是他们的层级关系。我们在定义一个普通的方法:

public void settingPerson1(List<Person> list) {
		for (Person p : list) {
			p.printName();

		}
	}

我们进行下面编码:

Main m = new Main();//代码所在编译环境,包含Main方法。
List<Person> myList1 = new ArrayList<Person>();
myList1.add(new YellowPerson("Yellow Man"));
myList1.add(new BlackPerson("Black Man"));
m.settingPerson1(myList1);

这种是最常见的用法,我们经常在开发中用到,不会有任何问题,看一下输出内容:

Log_Yellow Man
Log_Black Man

  下边看一下我们在方法里用“?”与extends的方式,这个只是普通方法,不是泛型方法,泛型方法有它固定的书写方式,看方法:

public void settingExtends(List<? extends Person> list) {
		for (Person p : list) {
			p.printName();
		}
	}

实现代码:

List<BlackPerson> myList2 = new ArrayList<BlackPerson>();
myList2.add(new BlackPerson("Black Man1"));
myList2.add(new BlackPerson("Black Man2"));
m.settingExtends(myList2);

List<YellowPerson> myList22 = new ArrayList<YellowPerson>();
myList22.add(new YellowPerson("Yellow Man1"));
myList22.add(new BlackPerson("Black Man"));
m.settingExtends(myList22);

这两种情况两个不会出现问题,注意一下list指定的泛型类型,下边分析一下,“?”是指不确定的某种类型,extends表示定义上限类型,放到一起就是当前方法接收的List中都必须是Person或者其子类型。注意这里,而且我们的settingExtends()方法中的?不能改为T,因为?为不确定的多种类型,T为不确定的某一种类型。同样在类上边使用的泛型也不能改为?,如,public class Person<T>{},这里的T不能用?替代,当我们在类上定义一个T时,这个T类型可以在整个类中使用,都表示相同的某种类型。

我们在方法上稍作修改,如下:

public void settingExtends(List<? extends YellowPerson> list) {
		for (Person p : list) {
			p.printName();
		}
	}

extends Person改成extends YellowPerson,再写一段实现代码:

List<Person> myList3 = new ArrayList<Person>();
myList3.add(new Person("Person"));
myList3.add(new YellowPerson("Yellow Man"));
myList3.add(new BlackPerson("Black Man"));
m.settingExtends(myList3);//编译出错。

这里就很好理解了,虽然我们在定义myList3时指定了Person类型,但是我们在方法里定义的只接受YellowPerson类型或妻子类型,我们这里又把Person加进去了,所以编译就通过不了了。

 下边接着看另一种方式:

List<? extends Person> myList5=new ArrayList();
myList5.add(new Person("Person"));//编译不通过
myList5.add(new Zhangsan("张三"));//编译不通过
myList5.add(new YellowPerson("Yellow Man"));//编译不通过

上边代码我们定义了一个接收所有Person子类的一个List,并且向里边添加的都是Person的子类,这在我们理解着应该是完全没问题的,但是其实不对,编译通过不了,你会发现add方法被画上了红线,为什么呢?“?”代表的是不确定的某种类型,可能是Person类型,也可能是YellowPerson类型或者BlackPerson与Zhangsan类型,当我们加入YellowPerson类型时,他有可能是BlackPerson类型,因为他的类型是不确定的某一种类型,所以我们不能向里边添加任何数据,这里需要你好好理解理解。

 下边看一下super定义类型下限,先定义一个方法:

public void settingSuper(List<? super YellowPerson> list) {
for (Object p : list) {
System.out.println("Log_Class:"+p.getClass().getSimpleName());
}
}

Super表示只接受自身或者父类类型,我们定义了一个只接受YellowPerson及其父类的方法,并打印了类名,看一下实现:

List<Person> myList4 = new ArrayList<Person>();
myList4.add(new Zhangsan("张三"));
myList4.add(new YellowPerson("Yellow Man"));
myList4.add(new Person("Person"));
m.settingSuper(myList4);

当你写完上边代码运行:

Log_Class:Zhangsan
Log_Class:YellowPerson
Log_Class:Person

你会惊奇的发现竟然没有问题,还能打印出信息,代码加入了YellowPerson的子类Zhangsan竟然没有报错,为什么呢?因为我们知道Java是可以向上转型的,我们可以把Zhangsan看成是YellowPerson,如下:

YellowPerson y= new Zhangsan();

所以没有报异常。也就是说这里你可以把跟YellowPerson有直属关系的属于Person子类的(List<Person>,这里是Person子类)都加进去。

  我们再看一下不调用方法,直接List实现:

List<? super YellowPerson> myList6=new ArrayList();
myList6.add(new Zhangsan("张三"));
myList6.add(new YellowPerson("Yellow Man"));
myList6.add(new Person("Person"));//编译报错;

你是不是感觉这里也不应该出现问题,而实际上在代码最后一行报错了,为什么呢?因为跟上边extends时类似,Zhangsan与YellowPerson都可以看成YellowPerson。而“?”为一种不确定的类型,所以我们也不能添加数据,这里大家理解一下。

再看一下无界限类型:

List<?> myList7=new ArrayList();
myList7.add(111);//同样不知道里边具体是什么类型,不能添加数据;
myList7.add("ssssss");//编译出错;
myList7.add(new Person("Person"));//编译出错;

同样无界限类型也是不能添加数据的。

如果我们指定了一种特定类型的泛型,比如下边List<Object>,那么我们是可以添加数据的,只要是符合类型:

public void settingWujie(List<?> list) {
for (Object p : list) {
System.out.println("Log_Class:"+p.getClass().getSimpleName());
}}
List<Object> myList8=new ArrayList<Object>();
myList8.add(new Integer(5));
myList8.add(new Person("Person"));
myList8.add(new YellowPerson("Yellow Man"));
myList8.add(new BlackPerson("Black Man"));
myList8.add(new Zhangsan("张三"));
m.settingWujie(myList8);

以上代码都是没有问题的,看输出;

Log_Class:Integer
Log_Class:Person
Log_Class:YellowPerson
Log_Class:BlackPerson
Log_Class:Zhangsan

三、注意点

(1)泛型只针对类类型的参数,不针对基本数据类型,如:int、long等;

(2)T可以在类中用,“?”不能;

(3)T代表某一种确定的数据类型,“?”代表不确定的数据类型;

(4)注意通配符“?”修饰时几种不能直接进行add操作的情况;

(5)泛型参数可以不止一个,如<K,V>;

(6)泛型可以使用super,extends修饰,表示有界;“?”单独用表示无界限,如:List<?>,相当于list中可以放任何类型;

(7)List<Person>与ArrayList<YellowPerson>为两种不同类型,不能画等号。

(8)不能在静态域或普通静态方法中出现类上定义的泛型,因为静态域执行时间要早于类的构建,泛型方法可以;

(9)定义泛型方法时,泛型类型要在public(static)后返回值前,如:

   public static <T> void setName(){...}

   public static  void <T> setName(){...} //错误

(10)泛型方法中用到的泛型类型,如果没有在类上定义,必须在方法上定义,如下:

  public class Person<T>{

  public <M> void setName(){

   M a;

  }

 }

(11)类上定义的泛型作用于整个类,方法上定义的泛型,只在当前方法有效。


希望您看完本篇文章,能够对于泛型有一个全新的了解。

小例子下载地址:http://download.csdn.net/detail/liuyonglei1314/9778992

猜你喜欢

转载自blog.csdn.net/liuyonglei1314/article/details/61912554