僅從類名構造 Java 對象
1. 概述
在本教程中,我們將探索使用類名創建 Java 對象的過程。 Java Reflection 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()](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/lang/Class.html#forName(java.lang.String))
採用類的完全限定名稱來返回原始作業對象。稍後, 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());
}
所以,上述方法啟動了兩個作業,一個修復作業和一個維護作業。
幾個月後,服務中心也決定開始油漆工作。因此,我們創建了一個新類PaintJob
,但是BronzeJobCard
可以容納這個新添加的類嗎?讓我們來看看:
@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. 使用原始類對象創建對象
在本節中,我們將升級作業卡以使用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() 方法中使用泛型**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()
現在使用 Reflection 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
現在僅接受由T extends Job
參數指示的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
接口,我們擺脫了 Reflection 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());
}
六,結論
在本文中,我們探討了使用類名和Class
對象創建對象的各種方法。我們展示了相關對像如何實現基本接口。然後,它可以進一步用來簡化對象創建過程。使用這種方法,不需要類型轉換,而且它確保了Job
接口的使用,在開發過程中強制執行類型檢查。
與往常一樣,本文中使用的代碼可以 在 GitHub 上找到。