1. 概述
在这个教程中,我们将探讨使用类名创建Java对象的过程。Java反射API提供了多种方法来实现这个任务,但在当前上下文中选择最合适的可能颇具挑战性。
为了处理这个问题,我们首先从一个简单的策略开始,然后逐步优化到更高效的解决方案。
2. 使用类名创建对象
设想一个汽车服务中心,它负责车辆的维修和保养,使用工作单对服务请求进行分类和管理。我们可以将此表示为类图:
让我们看看MaintenanceJob
和RepairJob
类:
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()
方法也不限制传递任何除RepairJob
和MaintenanceJob
之外的对象。这可能会导致开发阶段出现错误的代码。
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()
方法的对象(如MaintenanceJob
、RepairJob
和PaintJob
),它会失败。
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上找到。