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("cn.tuyucheng.taketoday.reflection.createobject.basic.RepairJob");
assertEquals("Start Bronze Repair Job", bronzeJobCard1.startJob());
BronzeJobCard bronzeJobCard2 = new BronzeJobCard();
bronzeJobCard2.setJobType("cn.tuyucheng.taketoday.reflection.createobject.basic.MaintenanceJob");
assertEquals("Start Bronze Maintenance Job", bronzeJobCard2.startJob());
}
所以,上述方法启动了两个作业,一个修复作业和一个维护作业。
几个月后,服务中心也决定开始喷漆工作。因此,我们创建了一个新的类PaintJob,但是BronzeJobCard可以容纳这个新添加的内容吗?让我们看看:
@Test
public void givenBronzeJobCard_whenJobTypePaint_thenFailJob() throws ClassNotFoundException, InvocationTargetException, NoSuchMethodException, InstantiationException, IllegalAccessException {
BronzeJobCard bronzeJobCard = new BronzeJobCard();
bronzeJobCard.setJobType("cn.tuyucheng.taketoday.reflection.createobject.basic.PaintJob");
assertEquals("Bronze Job Failed", bronzeJobCard.startJob());
}
彻底失败了!由于使用原始对象,BronzeJobCard无法处理新的PaintJob。
3. 使用原始类对象创建对象
在本节中,我们将升级作业卡,使用java.lang.Class而不是类名来创建作业。首先,看一下类图:
让我们看看SilverJobCard与BronzeJobCard有何不同:
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接口摆脱了反射API和startJob()方法中的类型转换,值得庆幸的是,现在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接口的使用,在开发过程中强制进行类型检查。
Post Directory
