关于Java中泛型的认知与理解

近期看了《深入理解Java虚拟机》中对语法糖、泛型(称伪泛型)的一些介绍,顺便总结梳理一下以前对JDK1.5引入的泛型的理解与使用。

为何会引入泛型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class Tools{
private Object obj;
public Object getObj() {
return obj;
}
public void setObj(Object obj) {
this.obj = obj;
}
public static void main(String[] args) {
Tools tools = new Tools();
tools.setObj("这是Object");
Object obj2 = tools.getObj();
Integer i = (Integer) obj2;
System.out.println(i);
}
}

上面这段代码,稍微有点经验,都知道会抛异常java.lang.ClassCastException。但是抛异常的时机却是在运行期。JVM在编译这段代码时,是可以通过的。tools.getObj()获取到对象时,需要程序员手动进行类型转换。这是早期关于Object做法。所以,泛型有了它的用武之地。

泛型类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public class Utils<T> {
private T t;
public void setObject(T t) {
this.t = t;
}
public T getObject() {
return t;
}
public static void main(String[] args) {
Utils<Singer> utils1 = new Utils<Singer>();
utils1.setObject(new Singer());
Singer s = utils1.getObject();
}
}
class Singer {
}
class Worker {
}

utils1.getObject()时不用进行类型强转。因为它的类型是在new Utils()时就已经确定了。


直接在编译期就进行了安全提示。

泛型方法

1
2
3
4
5
6
7
8
9
10
11
12
public class Demo {
// 泛型方法
public <T> void show(T t) {
System.out.println("show:::" + t);
}
public static void main(String[] args) {
Demo demo = new Demo();
demo.show("haha");
demo.show(new Tools());
}
}

泛型方法,它的类型是调用方法时,由实参决定的。

泛型类,泛型方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public class Demo<T> {
// 该方法跟着定义的泛型类走,固定类型
public void show(T t) {
System.out.println("show--->" + t);
}
// 泛型方法
public <W> void print(W w) {
System.out.println("print--->" + w);
}
// 静态泛型方法
public static <Q> void say(Q q) {
System.out.println("say--->" + q);
}
public static void main(String[] args) {
Demo<String> demo = new Demo<String>();
demo.show("haha1");
demo.print(2008);
demo.print("haha2");
Demo.say(3.1415926);
Demo.say("haha3");
}
}

实际运行结果如下:

show()方法的类型是跟着Demo上面定义的T走的,所以可以隐藏该方法的泛型。

print()方法的类型是由实参决定的。
say()方法的类型也是由实参决定的。

关于泛型的总结

  • 用于解决安全问题,是一个安全机制。类型安全机制。
  • 将运行时期出现的问题java.lang.ClassCastException转移到了编译期。
  • 避免强制类型转换的麻烦。
  • 泛型的类型必须为引用类型,不能是基本类型。

什么时候定义泛型

  • 当类中要操作的引用数据类型不确定的时候,早期定义Object来完成扩展,现在定义泛型来完成扩展。
  • 泛型类定义的泛型,在整个类中有效。如果该泛型被方法使用,那么泛型类的对象在明确要操作的具体类型后,该方法要操作的数据类型就已经固定了。
  • 为了让不同的方法可以操作不同类型,而且类型不确定。那么可以将泛型定义在方法上。
  • 静态方法是不可以访问类上定义的泛型。如果静态方法操作的数据类型不确定,可以将泛型定义在该方法上。
  • 泛型类每次都是在建立对象时才会被明确,由对象带着类型去运行。

实际使用场景一

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class WebUtils {
//把request中的数据封装到bean中
public static <T> T request2Bean(HttpServletRequest request,Class<T> clazz){
try{
T t = clazz.newInstance();
Enumeration e = request.getParameterNames();
while(e.hasMoreElements()){
String name= (String) e.nextElement();
String value = request.getParameter(name);
BeanUtils.setProperty(t, name, value);
}
return t;
}catch (Exception e) {
throw new RuntimeException(e);
}
}
}

使用静态泛型方法把request中的请求参数,通过反射+BeanUtils,直接封装成对象并返回。

实际使用场景二

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class DaoFactory {
private DaoFactory(){}
private static final DaoFactory instance = new DaoFactory();
public static DaoFactory getInstance(){
return instance;
}
public <T> T createDao(String className,Class<T> clazz){
try {
return (T) Class.forName(className).newInstance();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}

使用工厂设计模式+泛型方法。

实际使用场景三

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class BaseDao<T> {
// 把最共性的方法,抽取到该基类中来
public List<T> findAll() {
System.out.println("findAll--->");
return new ArrayList<T>();
}
}
public class DemoDao extends BaseDao<Demo> {
public static void main(String[] args) {
DemoDao demoDao = new DemoDao();
System.out.println(demoDao.findAll());
}
}

在new DemoDao的时候,DemoDao继承基类中的findAll()方法就明确是操作Demo这种类型的数据了。

泛型中的通配符与限定(用于泛型扩展)

  • ?通配符,可以理解为占位符。?表示不确定类型。T表示为具体类型,传什么就是什么类型。
  • ? extends E:表示可以接收E类型或者E类型的子类型。称为泛型的上限。
  • ? super E:表示可以接收E类型或者E类型的父类型。称为泛型的下限。
谢谢你请我吃糖果

--------- 本文结束,感谢您的审阅 ---------
0%