1. 概述

在这个教程中,我们将探讨使用类名创建Java对象的过程。Java反射API提供了多种方法来实现这个任务,但在当前上下文中选择最合适的可能颇具挑战性。

为了处理这个问题,我们首先从一个简单的策略开始,然后逐步优化到更高效的解决方案。

2. 使用类名创建对象

设想一个汽车服务中心,它负责车辆的维修和保养,使用工作单对服务请求进行分类和管理。我们可以将此表示为类图:

让我们看看MaintenanceJobRepairJob类:

public class MaintenanceJob {
    public String getJobType() {
        return "Maintenance Job";
    }
}
public class RepairJob {
    public String getJobType() {
        return "Repair Job";
    }
}

现在,让我们实现BronzeJobCard

public class BronzeJobCard {
    private Object jobType;
    public void setJobType(String jobType) throws ClassNotFoundException,
            NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
        Class jobTypeClass = Class.forName(jobType);
        this.jobType = jobTypeClass.getDeclaredConstructor().newInstance();
    }

    public String startJob() {
        if(this.jobType instanceof RepairJob) {
            return "Start Bronze " + ((RepairJob) this.jobType).getJobType();
        }
        if(this.jobType instanceof MaintenanceJob) {
            return "Start Bronze " + ((MaintenanceJob) this.jobType).getJobType();
        }
        return "Bronze Job Failed";
    }
}

BronzeJobCard中,Class.forName()方法接受完整的类名并返回原始的工作对象。然后,startJob()方法使用类型转换对原始对象进行操作以获取正确的作业类型。除了这些缺点外,还有处理异常的开销。

让我们看看它的实际应用:

@Test
public void givenBronzeJobCard_whenJobTypeRepairAndMaintenance_thenStartJob() throws ClassNotFoundException,
  InvocationTargetException, NoSuchMethodException, InstantiationException, IllegalAccessException {

    BronzeJobCard bronzeJobCard1 = new BronzeJobCard();
    bronzeJobCard1.setJobType("com.baeldung.reflection.createobject.basic.RepairJob");
    assertEquals("Start Bronze Repair Job", bronzeJobCard1.startJob());

    BronzeJobCard bronzeJobCard2 = new BronzeJobCard();
    bronzeJobCard2.setJobType("com.baeldung.reflection.createobject.basic.MaintenanceJob");
    assertEquals("Start Bronze Maintenance Job", bronzeJobCard2.startJob());
}

所以,上述方法启动了两个任务,一个修理任务和一个维护任务。

几个月后,服务中心决定也开始涂装工作。那么,BronzeJobCard是否能适应这个新添加的PaintJob呢?让我们看看:

@Test
public void givenBronzeJobCard_whenJobTypePaint_thenFailJob() throws ClassNotFoundException,
  InvocationTargetException, NoSuchMethodException, InstantiationException, IllegalAccessException {

    BronzeJobCard bronzeJobCard = new BronzeJobCard();
    bronzeJobCard.setJobType("com.baeldung.reflection.createobject.basic.PaintJob");
    assertEquals("Bronze Job Failed", bronzeJobCard.startJob());
}

这完全失败了!由于使用了原始对象,BronzeJobCard无法处理新的PaintJob

3. 使用Class对象创建对象

在这个部分,我们将升级工作单,使其不再使用类名而是使用java.lang.Class来创建对象。首先,看看类图:

BronzeJobCard相比,SilverJobCard有何不同:

public class SilverJobCard {
    private Object jobType;

    public void setJobType(Class jobTypeClass) throws
      NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
        this.jobType = jobTypeClass.getDeclaredConstructor().newInstance();
    }

    public String startJob() {
        if (this.jobType instanceof RepairJob) {
            return "Start Silver " + ((RepairJob) this.jobType).getJobType();
        }
        if (this.jobType instanceof MaintenanceJob) {
            return "Start Silver " + ((MaintenanceJob) this.jobType).getJobType();
        }
        return "Silver Job Failed";
    }
}

它不再依赖于作业类的全限定名来创建对象。然而,原始对象的问题和异常处理问题仍然存在。

如下面所示,它也可以创建并启动任务:

@Test
public void givenSilverJobCard_whenJobTypeRepairAndMaintenance_thenStartJob() throws InvocationTargetException,
  NoSuchMethodException, InstantiationException, IllegalAccessException {

    SilverJobCard silverJobCard1 = new SilverJobCard();
    silverJobCard1.setJobType(RepairJob.class);
    assertEquals("Start Silver Repair Job", silverJobCard1.startJob());

    SilverJobCard silverJobCard2 = new SilverJobCard();
    silverJobCard2.setJobType(MaintenanceJob.class);
    assertEquals("Start Silver Maintenance Job", silverJobCard2.startJob());
}

但就像BronzeJobCard一样,SilverJobCard也无法处理新的PaintJob

@Test
public void givenSilverJobCard_whenJobTypePaint_thenFailJob() throws ClassNotFoundException,
  InvocationTargetException, NoSuchMethodException, InstantiationException, IllegalAccessException {

    SilverJobCard silverJobCard = new SilverJobCard();
    silverJobCard.setJobType(PaintJob.class);
    assertEquals("Silver Job Failed", silverJobCard.startJob());
}

此外,setJobType()方法也不限制传递任何除RepairJobMaintenanceJob之外的对象。这可能会导致开发阶段出现错误的代码。

4. 使用Class对象和泛型创建对象

之前,我们看到了原始对象如何影响代码质量。在这个部分,我们将解决这个问题。首先,看看类图:

这次,我们去掉了原始对象。GoldJobCard采用类型参数并在setJobType()方法中使用了泛型。让我们看看实现:

public class GoldJobCard<T> {
    private T jobType;

    public void setJobType(Class<T> jobTypeClass) throws
      NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
        this.jobType = jobTypeClass.getDeclaredConstructor().newInstance();
    }

    public String startJob() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
        return "Start Gold " + this.jobType.getClass().getMethod("getJobType", null)
                .invoke(this.jobType).toString();
    }
}

有趣的是,startJob()现在通过反射API在对象上调用方法。最后,我们也消除了类型转换的需求。让我们看看它的表现:

@Test
public void givenGoldJobCard_whenJobTypeRepairMaintenanceAndPaint_thenStartJob() throws InvocationTargetException,
  NoSuchMethodException, InstantiationException, IllegalAccessException {

    GoldJobCard<RepairJob> goldJobCard1 = new GoldJobCard();
    goldJobCard1.setJobType(RepairJob.class);
    assertEquals("Start Gold Repair Job", goldJobCard1.startJob());

    GoldJobCard<MaintenanceJob> goldJobCard2 = new GoldJobCard();
    goldJobCard2.setJobType(MaintenanceJob.class);
    assertEquals("Start Gold Maintenance Job", goldJobCard2.startJob());

    GoldJobCard<PaintJob> goldJobCard3 = new GoldJobCard();
    goldJobCard3.setJobType(PaintJob.class);
    assertEquals("Start Gold Paint Job", goldJobCard3.startJob());
}

现在,它也能处理PaintJob

但是,我们仍然无法在开发阶段限制startJob()方法中传递的对象。因此,对于没有getJobType()方法的对象(如MaintenanceJobRepairJobPaintJob),它会失败。

5. 使用类型参数扩展创建对象

是时候解决之前提出的问题了。让我们从传统的类图开始:

我们引入了Job接口,所有Job对象都必须实现它。此外,PlatinumJobCard现在只接受Job对象,由T extends Job类型的参数表示。

实际上,这种方法类似于工厂设计模式。我们可以引入一个JobCardFactory来处理Job对象的创建。

接下来,我们来看看实现:

public class PlatinumJobCard<T extends Job> {
    private T jobType;

    public void setJobType(Class<T> jobTypeClass) throws
      NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
        this.jobType = jobTypeClass.getDeclaredConstructor().newInstance();
    }

    public String startJob() {
        return "Start Platinum " + this.jobType.getJobType();
    }
}

我们通过引入Job接口消除了startJob()方法中的反射API和类型转换。值得庆幸的是,现在PlatinumJobCard无需修改就能处理未来的Job类型。让我们看看它的运行情况:

@Test
public void givenPlatinumJobCard_whenJobTypeRepairMaintenanceAndPaint_thenStartJob() throws InvocationTargetException,
  NoSuchMethodException, InstantiationException, IllegalAccessException {

    PlatinumJobCard<RepairJob> platinumJobCard1 = new PlatinumJobCard();
    platinumJobCard1.setJobType(RepairJob.class);
    assertEquals("Start Platinum Repair Job", platinumJobCard1.startJob());

    PlatinumJobCard<MaintenanceJob> platinumJobCard2 = new PlatinumJobCard();
    platinumJobCard2.setJobType(MaintenanceJob.class);
    assertEquals("Start Platinum Maintenance Job", platinumJobCard2.startJob());

    PlatinumJobCard<PaintJob> platinumJobCard3 = new PlatinumJobCard();
    platinumJobCard3.setJobType(PaintJob.class);
    assertEquals("Start Platinum Paint Job", platinumJobCard3.startJob());
}

6. 总结

在这篇文章中,我们探讨了使用类名和Class对象创建对象的不同方式。我们展示了相关对象如何实现基接口,然后可以进一步简化对象创建过程。通过这种方法,我们不需要类型转换,并且确保使用了Job接口,在开发过程中实现了类型检查。

如往常一样,本文中使用的代码可以在GitHub上找到。