Java基础加强第五讲 泛型(下)——泛型类及其应用
发布日期:2021-06-30 18:04:30 浏览次数:2 分类:技术文章

本文共 7321 字,大约阅读时间需要 24 分钟。

定义泛型类型

如果类的实例对象中的多处都要用到同一个泛型参数,即这些地方引用的泛型类型要保持同一个实际类型时,这时候就要采用泛型类型的方式进行定义,也就是类级别的泛型,语法格式如下:

package cn.liayun.generic;import java.util.Set;//dao:data access object → C(创建)、R(检索)、U、Dpublic class GenericDao
{
//泛型类型 public void add(/*merchinedise:产品*/E x) {
} public E findById(int id) {
return null; } public void delete(E obj) {
} public void delete(int id) {
} public void update(E obj) {
} public Set
findByConditions(String where) {
return null; } public E findByUserName(String name) {
return null; } // 类里面的静态方法不能使用泛型类型的变量。如果要使用,得单独独立出来,独立出来的静态泛型方法 public static
void update2(E obj) {
//静态方法不能使用泛型 }}

类级别的泛型是根据引用该类名时指定的类型信息来参数化类型变量的,例如,如下两种方式都可以:

在这里插入图片描述
这里,我们需要注意以下几点:

  1. 在对泛型类型进行参数化时,类型参数的实例必须是引用类型,不能是基本类型;
  2. 当一个变量被声明为泛型时,只能被实例变量、方法和内部类调用,而不能被静态变量和静态方法调用。因为静态成员是被所有参数化的类所共享的,所以静态成员不应该有类级别的类型参数。例如,(类里面的)静态方法不能使用泛型类型的变量,而应单独定义泛型。所以,以下代码编译错误。
    在这里插入图片描述
  3. 类中多个方法需要使用泛型,是使用类级别的泛型,还是使用方法级别的泛型?显然是使用类级别的泛型。

泛型类的典型应用

通过反射获得泛型的参数化类型

对于如下的代码:

Vector
v1 = new Vector
();

我们迫切地想要知道变量v1里面的泛型类型,这对以后的框架学习会有极大的帮助,但通过变量v1自己是没法知道变量里面的泛型类型的,而当把这个变量v1交给一个方法去使用时,通过这个方法是可以知道该变量里面的泛型类型的。

package cn.liayun.generic;import java.lang.reflect.Method;import java.lang.reflect.ParameterizedType;import java.lang.reflect.Type;import java.util.Date;import java.util.Vector;public class GenericTest {
public static void main(String[] args) throws Exception {
/* * 想通过反射的方式来得到Vector里面到底装的是什么类型? */// Vector
v1 = new Vector
();// v1.getClass(); Method applyMethod = GenericTest.class.getMethod("applyVector", Vector.class); Type[] types = applyMethod.getGenericParameterTypes(); //ParameterizedType:参数化的类型 ParameterizedType pType = (ParameterizedType) types[0]; System.out.println(pType.getRawType());//class java.util.Vector,打印原始类型 System.out.println(pType.getActualTypeArguments()[0]);//class java.util.Date,得到实际的类型参数,可能有多个,例如HashMap
} /* * 我没法通过v1来知道它前面变量的类型,但是我可以通过applyVector这个方法来知道它的参数列表的类型 */ public static void applyVector(Vector
v1) {
} /* public static void applyVector(Vector
v1) {//不是重载,因为去类型化了。 } */ }

编写BaseDao

一个项目中势必会有很多实体类型,这样就要编写很多XxxDao了,这时有什么好办法来简化我们代码的编写呢?我们可以设计一个泛型类(BaseDao<T>),该泛型类提供最基本的增删改查方法,使用泛型类的原因是因为我们不知道要增删改查的具体是一个什么类型的对象,当你传递进来的T是Book,那就增删改查Book对象;你传递进来的T是Category,那就增删改查Category对象。这样,泛型类(BaseDao<T>)应为:

public class BaseDao
{
public void add(T t) {
//blabla... } public T find(String id) {
//blabla... } public void update(T t) {
//blabla... } public void delete(String id) {
//blabla... }}

现在,我们就要写该泛型类具体的增删改查方法了,但是以我们目前的功力,写出来是非常麻烦的,为了简化开发,我们打算导入Hibernate框架相关的jar包,来模拟Hibernate操作数据库(即使我们现在还没学习Hibernate框架)。我们来开始写这样的代码吧!

public class BaseDao
{
private Session session;//要想增删改查数据库,只需要得到与数据库会话的org.hibernate.Session即可 public void add(T t) {
session.save(t); } public T find(String id) {
// 发现写到这儿卡壳了 session.get(Xxx.class, id); } public void update(T t) {
} public void delete(String id) {
}}

为什么我们写不下去了呢?Hibernate框架根据id从数据库表里面get出来的数据,它是不知道要把这些数据封装到哪儿去的,所以应该传递给它一个Class。

我们现在遇到了一个问题,这也是一个非常重要的问题:现在Hibernate框架根据id从数据库表里面get出来数据之后,一定要传递一个Class进来,但我们在设计find方法的时候是不知道要传递什么Class进来的,那么就没办法了,就只能由该泛型类的使用者来决定了。所以该泛型类的代码就要修改为:

public class BaseDao
{
private Session session;//要想增删改查数据库,只需要得到与数据库会话的org.hibernate.Session即可 private Class clazz; public BaseDao(Class clazz) {
this.clazz = clazz; } public void add(T t) {
session.save(t); } public T find(String id) {
return session.get(clazz, id); } public void update(T t) {
session.update(t); } public void delete(String id) {
T t = session.get(clazz, id); session.delete(t); }}

这样该泛型类才算写好了。前面说过一个项目中会有很多实体类型,所以,这里我们来新建一个Book类和一个Category类。

  • Book类的代码如下:

    package cn.liayun.domain;public class Book {
    }
  • Category类的代码如下:

    package cn.liayun.domain;	public class Category {
    }

接下来,我们就要编写相应的XxxDao了,为了让相应的XxxDao拥有CRUD方法,那么它们就要继承BaseDao了,并传递一个实际的类型参数进去。

  • BookDao类的代码如下:

    public class BookDao extends BaseDao
    {
    public BookDao() {
    super(Book.class); // 调用父类的构造方法 }}
  • CategoryDao类的代码如下:

    public class CategoryDao extends BaseDao
    {
    public CategoryDao() {
    super(Category.class); // 调用父类的构造方法 }}

若是泛型类真如上面所写,那么在编写这样的XxxDao时,代码里面定少不了这样的构造方法:

public XxxDao() {
super(Xxx.class); // 调用父类的构造方法}

显然这样设计的泛型类是不优雅的,为了使其优雅,我们就需要反射泛型了,这样该泛型类的代码就要修改为:

public class BaseDao
{
private Session session;//要想增删改查数据库,只需要得到与数据库会话的org.hibernate.Session即可 private Class clazz; public BaseDao() {
/* * 谁调用了这个构造方法,this就指向谁。 * 此处,this指向子类对象。 */ // this.getClass();意味着取得了BookDao.class ParameterizedType pt = (ParameterizedType) this.getClass().getGenericSuperclass(); // 取得泛型化了的父类(例如BaseDao
),即参数化了的类型 clazz = (Class) pt.getActualTypeArguments()[0]; // 取得实际类型参数,例如Book.class // System.out.println(clazz); } public void add(T t) {
session.save(t); } public T find(String id) {
return session.get(clazz, id); } public void update(T t) {
session.update(t); } public void delete(String id) {
T t = session.get(clazz, id); session.delete(t); }}

在该泛型类的无参构造函数中,this究竟指代的是谁呢?这里,this并不是指代的是自己,而是到底是谁调用了BaseDao()这个构造方法,this就指向谁。编写好这样的BaseDao之后,就会有诸如BookDao等去继承它,在真正地操作数据库时,总会写这样的代码:new BookDao();。用比较专业的术语来说即子类继承父类,等一会儿new子类对象的时候,根据Java语言new对象的机制,在new子类对象的时候,子类会调用父类的无参构造函数,所以this指代的是子类对象,到底是哪个子类对象,这无从知晓。修改完BaseDao泛型类之后,那么像诸如BookDao等编写起来就清爽许多了。

  • BookDao类的代码如下:

    public class BookDao extends BaseDao
    {
    }
  • CategoryDao类的代码如下:

    public class CategoryDao extends BaseDao
    {
    }

这里,我们还要注意一个小细节,即该泛型类可以限定处理的类型。

//(extends Serializable & Cloneable)限定处理的类型//T extends Serializable & Cloneable这样写,BaseDao处理的那个类型必须即是Serializable的崽,又是Cloneable的崽,//也就是说BaseDao处理的那个对象既要实现Serializable接口,又要实现Cloneable接口。public class BaseDao
{
private Session session;//要想增删改查数据库,只需要得到与数据库会话的org.hibernate.Session即可 private Class clazz; public BaseDao() {
/* * 谁调用了这个构造方法,this就指向谁。 * 此处,this指向子类对象。 */ // this.getClass();意味着取得了BookDao.class ParameterizedType pt = (ParameterizedType) this.getClass().getGenericSuperclass(); // 取得泛型化了的父类(例如BaseDao
),即参数化了的类型 clazz = (Class) pt.getActualTypeArguments()[0]; // 取得实际类型参数,例如Book.class // System.out.println(clazz); } public void add(T t) {
session.save(t); } public T find(String id) {
return session.get(clazz, id); } public void update(T t) {
session.update(t); } public void delete(String id) {
T t = session.get(clazz, id); session.delete(t); }}

这时要让程序不报错,还得修改Book类和Category类的代码,改后如下:

  • Book类的代码如下:

    package cn.liayun.domain;import java.io.Serializable;public class Book implements Serializable, Cloneable {
    }
  • Category类的代码如下:

    package cn.liayun.domain;import java.io.Serializable;public class Category implements Serializable, Cloneable {
    }

转载地址:https://liayun.blog.csdn.net/article/details/92065018 如侵犯您的版权,请留言回复原文章的地址,我们会给您删除此文章,给您带来不便请您谅解!

上一篇:Java Web基础入门第九十九讲 JavaMail开发——在WEB应用中集成邮件发送程序
下一篇:Java Web基础入门第九十三讲 在线网上书店(八)——实现数据库管理模块

发表评论

最新留言

能坚持,总会有不一样的收获!
[***.219.124.196]2024年04月29日 01时25分18秒