1.简单理解泛型的作用
下面看一段代码
List list = new ArrayList();
list.add(123);
list.add("string");
String s1 = (String) list.get(0); //易出错
for(int i = 0 ; i < list.size() ; i++) {
System.out.println(list.get(i));
}
虽然List是add()一个Object,你可以传入 int ,String 等,但是你get()的时候也是返回一个Object。如果你想要转换成自己的类并操作,就要向上图一样实现强制转换,虽然代码没错,但是一运行就出错。有时候很多类型,你可能已经忘记要转换成什么类型,那是不是会出错呢。
比如上面就会报错:
Exception in thread "main" java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String
所以就要想一个办法,让List记住你add()传入的参数类型,当你get()的时候不需要强制转换,它自动已经帮你转换好了。减少错误的发生。例如:
List<String> list2 = new ArrayList<String>();
list2.add("qweq");
//list2.add(456); //报错
String s = list2.get(0);
for(int i = 0 ; i < list2.size() ; i++) {
System.out.println(list2.get(i));
}
代码中 <String> 就是泛型.虽然减少了错误发生,但是也对你add()加了限制,不再是传入Object,而是String。这样你get()的也是String。
2.泛型概念
泛型是Java SE 1.5的新特性,本质是参数化类型,所操作的数据类型被指定为一个参数。这种参数类型可以用在类、接口和方法的创建中,分别称为泛型类、泛型接口、泛型方法。 Java语言引入泛型的好处是安全简单。
在Java SE 1.5之前,没有泛型的情况的下,通过对类型Object的引用来实现参数的“任意化”,“任意化”带来的缺点是要做显式的强制类型转换,而这种转换是要求开发者对实际参数类型可以预知的情况下进行的。对于强制类型转换错误的情况,编译器可能不提示错误,在运行的时候才出现异常,这是一个安全隐患。
泛型的好处是在编译的时候检查类型安全,并且所有的强制转换都是自动和隐式的,以提高代码的重用率。
3.我们来自己定义个 泛型类 和 泛型方法 (泛型接口类似)
在泛型接口、泛型类和泛型方法的定义过程中,我们常见的如T、E 等形式的参数常用于表示泛型形参,接收来自外部使用时候传入的类型实参。
注意:泛型的类型参数只能是类类型(包括自定义类),不能是简单类型如(int)。
public class TestFx {
public static void main(String[] args) {
Person<String> p1 = new Person<String>("2018");
System.out.println(p1.getId());
//Person<int> p2 = new Person<int>(8102); //不能是int,可以是Integer
Person<Integer> p2 = new Person<Integer>(123);
System.out.println(p2.getId());
}
}
class Person<T>{
private T id;
public Person(T id) {
this.id = id;
}
public T getId() {
return id;
}
}
使用泛型类时,虽然传入了不同的泛型实参,但并没有真正意义上生成不同的类型,传入不同泛型实参的泛型类在内存上只有一个,即还是原来的最基本的类型(本实例中为Person)。
究其原因,在于Java中的泛型这一概念提出的目的,导致其只是作用于代码编译阶段,在编译过程中,对于正确检验泛型结果后,会将泛型的相关信息擦出,也就是说,成功编译过后的class文件中是不包含任何泛型信息的。泛型信息不会进入到运行时阶段。
System.out.println(p1.getClass() == p2.getClass()); //true
4.类型通配符
先讲个例子,理解为什么需要类型通配符?
public class TestFx {
public static void main(String[] args) {
Person<String> p1 = new Person<String>("2018");
System.out.println(p1.getId());
//Person<int> p2 = new Person<int>(8102); //不能是int,可以使Integer
Person<Integer> p2 = new Person<Integer>(123);
System.out.println(p2.getId());
System.out.println(p1.getClass() == p2.getClass()); //true
//getId(p1); //报错 ←
//getId(p2); //报错 ←
}
public static void getId(Person<Object> p) { //泛型参数方法
System.out.println(p.getId());
}
}
class Person<T>{
private T id;
public Person(T id) {
this.id = id;
}
public T getId() {
return id;
}
}
可以看到上面代码中有2个报错。原因如下:
Person<Object> 不能看做是 Person<String> / Person<Integer> 的父类。可以推导其它的子父类也不成立。
那么我们想要让Person<String> / Person<Integer>传入方法中该怎么办? [ Person<?> p ]
这时候就需要我们的 类型通配符(?) 修改代码如下:
public static void main(String[] args) {
Person<String> p1 = new Person<String>("2018");
System.out.println(p1.getId());
//Person<int> p2 = new Person<int>(8102); //不能是int,可以使Integer
Person<Integer> p2 = new Person<Integer>(123);
System.out.println(p2.getId());
System.out.println(p1.getClass() == p2.getClass()); //true
getId(p1); // ←
getId(p2); // ←
}
public static void getId(Person<?> p) {
System.out.println(p.getId());
}
我们可以看到错误没有了。
虽然错误没有了,但是是不是觉得参数范围很广,我们如果想要缩小参数范围又该怎么办? (类型通配符上限和类型通配符下限)
- 类型通配符上限通过形如Person<? extends Number>形式定义 Number为上限
(类型通配符上限: 只想让方法的参数为某个类和它的子类)
- 相对应的,类型通配符下限为Box<? super Number>形式 Number为下限
(类型通配符下限: 只想让方法的参数为某个类和它的父类)
代码如下:
public class TestFx {
public static void main(String[] args) {
Person<String> p1 = new Person<String>("2018");
System.out.println(p1.getId());
//Person<int> p2 = new Person<int>(8102); //不能是int,可以使Integer
Person<Integer> p2 = new Person<Integer>(123);
System.out.println(p2.getId());
Person<Number> p3 = new Person<Number>(456);
System.out.println(p3.getId());
Person<Object> p4 = new Person<Object>("爱你");
//getId_extends(p1); // 报错,String不是Number的子类 ←
getId_extends(p2); // 不报错,Integer是Number的子类 ←
getId_extends(p3); // 本身肯定不报错 ←
//getId_extends(p4); // 报错,Object不是Number的子类 ←
//getId_super(p1); // 报错,String不是Number的父类 ←
//getId_super(p2); // 报错,Integer不是Number的父类 ←
getId_super(p3); // 本身肯定不报错 ←
getId_super(p4); // 不报错,Object是Number的父类 ←
}
public static void getId_extends(Person<? extends Number> p) {
System.out.println(p.getId());
}
public static void getId_super(Person<? super Number> p) {
System.out.println(p.getId());
}
}
5.泛型数组
一直听说没有泛型数组这个东西,可能是我学的还比较浅吧。
不过我自己还是实验出来可以用的,如下:
Person<?>[] ps = new Person<?>[4];
ps[0] = p1;
ps[1] = p2;
ps[2] = p3;
ps[3] = p4;
for(int i = 0 ; i < ps.length ; i++) {
System.out.println(ps[i].getId());
}
不过,若是你的泛型参数传入的不是 通配符(?),而是其它的比如(Integer , String , Object)就不行。看来实现泛型数组,必须要
通配符<?>。