什么是泛型
泛型是Java SE 1.5的新特性,泛型的本质是参数化类型,也就是说所操作的数据类型被指定为一个参数。这种参数类型可以用在类、接口和方法的创建中,分别称为泛型类、泛型接口、泛型方法。 Java语言引入泛型的好处是安全简单。
泛型有什么好处
在Java SE 1.5之前,没有泛型的情况的下,通过对类型Object的引用来实现参数的“任意化”,“任意化”带来的缺点是要做显式的强制类型转换,而这种转换是要求开发者对实际参数类型可以预知的情况下进行的。对于强制类型转换错误的情况,编译器可能不提示错误,在运行的时候才出现异常,这是一个安全隐患。
泛型的好处是在编译的时候检查类型安全,并且所有的强制转换都是自动和隐式的,提高代码的重用率。
1、泛型的类型参数只能是类类型(包括自定义类),不能是简单类型。
2、同一种泛型可以对应多个版本(因为参数类型是不确定的),不同版本的泛型类实例是不兼容的。
3、泛型的类型参数可以有多个。
4、泛型的参数类型可以使用extends语句,例如<T extends superclass>。习惯上称为“有界类型”。
5、泛型的参数类型还可以是通配符类型。例如Class<?> classType = Class.forName("java.lang.String");
没有泛型的时候是这样的
ArrayList al = new ArrayList();
al.add("ysjian001");
al.add(1);
al.add(new Object());
这段代码看似功能强大,为什么呢?因为它似乎能够往集合添加各种类型的对象(int类型会被装箱成Integer对象类型),貌似一些老程序员也倾向于这么去做,而且他们可以理直气壮的告诉我理由:我这么做想存什么就存什么!先不否定这种说法,让我们继续,看看下面代码:
String first = (String) al.get(0);
往集合里面存值就是为了后期取出来用的,而不是System.out.println(first),这里就产生了一个强制转换的问题,而往往这种类型的强制转换在编译器是允许通过的,而写程序的人们会犯下无意间的错误,错误的进行了强制转换,导致程序运行失败。
强制类型转换导致的程序运行失败的原因是没有在编译器对类型进行控制,看看code01调用ArrayList对象的add方法,任何类型都是可以添加进行的,编译器无法进行错误检验,埋下了安全隐患,例如:
ArrayList al = new ArrayList();
// 无法进行错误检查,File对象可以添加进去,编译器和运行期都可以通过
al.add(new File());
String first = (String) al.get(0); // 类型转换失败导致运行失败
不用泛型的缺点
没有泛型的程序面临两个问题:
- 编译器无法进行类型检查,可以向集合中添加任意类型的对象。
- 取值时类型转换失败导致程序运行失败。
没有泛型的程序导致的后果:
- 程序的可读性有所降低,因为程序员可以不受限制往集合中添加任意对象。
- 程序的安全性遭到质疑,类型转换失败将导致程序运行失败。
Java5泛型提供了一个更好的解决方案:类型参数(type parameters),使用泛型的程序改善上述代码如下:
ArrayList<String> al = new ArrayList<String>();
al.add( "ysjian001");
// al.add(new Thread()); // 定义了String类型参数,添加File对象会报错
String first = al.get(0);// 使用泛型后取值不用进行类型转换
到这里,通过前后对比,泛型的好处是不是很清楚了呢?为什么用泛型呢?
因为出现编译错误比类在运行时出现强制类型转换异常要好得多,泛型的好处在于提高了程序的可读性和安全性,这也是程序设计的宗旨之一。
什么时候使用泛型
使用泛型类是一件很轻松的事,集合框架中的类都是泛型类,用起来很方便。有人会想类型限制我们为什么不直接用数组呢?这个问题就好像问为什么集合优于数组,数组是固定的,而集合是可以自动扩展的。另外在实际中,实现一个泛型其实并不是那么容易。
大多数应用程序员对泛型的熟练程度仅仅停留在使用泛型上,像集合类中的List、Set和Map这些泛型集合用的很多,他们不必考虑这些泛型集合的工作方式和原理。那么当把不同的泛型类混合在一起使用时,或者对Java5之前的遗留代码进行衔接时,可能会看到含糊不清的的错误消息。这样一来,程序员就需要学习Java泛型来解决问题了,而不是在程序中胡乱猜测了。最终,部分程序员可能想要实现自己的泛型类和泛型方法。
提炼出泛型程序设计的三种熟练程度就是:
- 仅仅使用泛型。
- 学习泛型解决一些问题。
- 掌握泛型,实现自己的泛型。
怎么使用泛型
如何使用泛型听起来是一件很容易的事情,因为Sun公司的那些工程师已经做了很大努力,而需求总是会稍微苛刻一点的,需要解决因为缺乏类型参数模糊不清的问题,或者我们有必要实现自己的泛型来满足业务需求,所以学习和掌握泛型是很有必要的。最常见的自定义泛型的场景是泛型类、泛型方法、泛型接口。
泛型类
把泛型定义在类上
格式:public class 类名<泛型类型1,…>
注意:泛型类型必须是引用类型
早期的时候,我们使用Object来代表任意的类型。
向上转型是没有任何问题的,但是在向下转型的时候其实隐含了类型转换的问题。
也就是说这样的程序其实并不是安全的。所以Java在JDK5后引入了泛型,提高程序的安全性。
下面我们就来学习泛型类是怎么回事
// 泛型类:把泛型定义在类上
public class ObjectTool<T> {
private T obj;
public T getObj() {
return obj;
}
public void setObj(T obj) {
this.obj = obj;
}
}
// 泛型类的测试
public class ObjectToolDemo {
public static void main(String[] args) {
ObjectTool<String> ot = new ObjectTool<String>();
ot.setObj(new String("中国"));
String s = ot.getObj();
System.out.println("姓名是:" + s);
ObjectTool<Integer> ot2 = new ObjectTool<Integer>();
ot2.setObj(new Integer(69));
Integer i = ot2.getObj();
System.out.println("年龄是:" + i);
}
}
输出结果:
姓名是:中国
年龄是:69
泛型方法
把泛型定义在方法上
格式:public <泛型类型> 返回类型 方法名(泛型类型 .)
上面我们把泛型定义在了类中,现在我们也可以把泛型定义在方法中,来一起学习
* * 泛型方法:把泛型定义在方法上 **
public class ObjectTool {
public <T> void show(T t) {
System.out.println(t);
}
}
public class ObjectToolDemo {
public static void main(String[] args) {
// 定义泛型方法后
ObjectTool ot = new ObjectTool();
ot.show("hello");
ot.show(100);
ot.show(true);
}
}
这样我们就可以传递任意类型的参数了
泛型接口
把泛型定义在接口上
格式:public interface 接口名<泛型类型1…>
/* * 泛型接口:把泛型定义在接口上 */
public interface Inter<T> {
public abstract void show(T t);
}
//实现类在实现接口的时候,我们会遇到两种情况
//第一种情况:已经知道是什么类型的了
public class InterImpl implements Inter<String> {
@Override
public void show(String t) {
System.out.println(t);
}
}
//第二种情况:还不知道是什么类型的
public class InterImpl<T> implements Inter<T> {
@Override
public void show(T t) {
System.out.println(t);
}
}
public class InterDemo {
public static void main(String[] args) {
// 第一种情况的测试
Inter<String> i = new InterImpl();
i.show("hello");
// 第二种情况的测试
Inter<String> i = new InterImpl<String>();
i.show("hello");
Inter<Integer> ii = new InterImpl<Integer>();
ii.show(100);
}
}
我们来写一个简单的例子验证一下上面所说的结论
public class GenericDemo {
public static void main(String[] args) {
// 泛型如果明确的写的时候,前后必须一致 Collection<Object> c1 = new ArrayList<Object>();
// Collection<Object> c2 = new ArrayList<Animal>();//报错
// Collection<Object> c3 = new ArrayList<Dog>();//报错
// Collection<Object> c4 = new ArrayList<Cat>();//报错
// ?表示任意的类型都是可以的
Collection<?> c5 = new ArrayList<Object>();
Collection<?> c6 = new ArrayList<Animal>();
Collection<?> c7 = new ArrayList<Dog>();
Collection<?> c8 = new ArrayList<Cat>();
// ? extends E:向下限定,E及其子类
// Collection<? extends Animal> c9 = new ArrayList<Object>();//报错
Collection<? extends Animal> c10 = new ArrayList<Animal>();
Collection<? extends Animal> c11 = new ArrayList<Dog>();
Collection<? extends Animal> c12 = new ArrayList<Cat>();
// ? super E:向上限定,E极其父类
Collection<? super Animal> c13 = new ArrayList<Object>();
Collection<? super Animal> c14 = new ArrayList<Animal>();
// Collection<? super Animal> c15 = new ArrayList<Dog>();//报错
// Collection<? super Animal> c16 = new ArrayList<Cat>();//报错
}
}
class Animal {
}
class Dog extends Animal {
}
class Cat extends Animal {
}
仔细观察一下上面的通配符有什么区别,你会很快的学会通配符的使用
泛型在实际开发中的应用场景
泛型在实际开发中用的很多,比如常见的Dao层的父类BaseDao
,这里面一般是一个泛型类,把实体作为通配符传过去。
网友评论