1. Overview

In this tutorial, we’ll explore the process of creating Java objects using their class names. The Java Reflection API offers various methods for accomplishing this task. However, determining the most suitable one for the current context can be challenging.

To address this, let’s begin with a straightforward approach and gradually refine it to a more efficient solution.

2. Create Objects Using Class Name

Let’s picture a motor service center. This center handles maintenance and repairs for motor vehicles, using job cards to categorize and manage service requests. We can represent this as a class diagram:

Bronze

Let’s take a look at the MaintenanceJob and RepairJob classes:

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

Now, let’s implement the 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";
    }
}

In BronzeJobCard, Class.forName() takes the fully qualified name of the class to return the raw job object. Later, startJob() uses type-casting on the raw object to get the correct job type. On top of these disadvantages, there is also the overhead of handling the exceptions.

Let’s see it in action:

@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());
}

So, the above method started two Jobs, a repair job and a maintenance job.

A few months later, the service center decided to start a paint job as well. So, we created a new class PaintJob, but can BronzeJobCard accommodate this new addition? Let’s see:

@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());
}

That failed miserably! Due to the use of raw objects, BronzeJobCard is unable to handle the new PaintJob.

3. Create Objects Using the Raw Class Object

In this section, we’ll upgrade the Job card to create the jobs using java.lang.Class instead of the name of the class. First, take a look at the class diagram:

 

Silver

Let’s see how different SilverJobCard is from the 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";
    }
}

It no longer relies on the fully qualified name of the job classes to create the objects. However, the issue with raw objects and exceptions remains unchanged.

As shown below, it can also handle creating the jobs and then starting them:

@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());
}

But, like BronzeJobCard, SilverJobCard also fails to accommodate the new 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());
}

Also, the method setJobType() does not restrict the passing of any object other than RepairJob and MaintenanceJob. This might result in erroneous code during the development stage.

4. Create Objects Using Class Object and Generics

Earlier, we saw how raw objects are affecting the quality of the code. In this section, we’ll address it. But first, take a look at the class diagram:

 

Gold

This time, we got rid of the raw objects. GoldJobCard takes the type parameter and makes use of Generics in the method setJobType(). Let’s check the implementation:

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();
    }
}

Interestingly, startJob() is now invoking the method on the object using the Reflection API. Finally, we also got rid of the need for type-casting. Let’s see how it behaves:

@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());
}

Here, it handles the PaintJob as well.

But still, we’re not able to restrict the objects passed into the startJob() method during the development phase. As a result, it would fail for an object that does not have the getJobType() method like MaintenanceJob, RepairJob, and PaintJob.

5. Create Objects Using Type Parameter Extends

It’s time to address the issue raised earlier. Let’s start with the customary class diagram:

 

Platinum

We’ve introduced the Job interface, which all Job objects must implement. Furthermore, PlatinumJobCard now accepts only Job objects, indicated by the T extends Job parameter.

Actually, this approach closely resembles the Factory Design Pattern. We can introduce a JobCardFactory that can handle the creation of the Job objects.

Moving on, we can now look at the implementation:

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();
    }
}

We got rid of the Reflection API and the type-casting from the startJob() method by introducing the Job interface. Thankfully, now PlatinumJobCard will be able to handle future Job types without any modification to it. Let’s see it in action:

@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. Conclusion

In this article, we explored the various ways to create objects with the class name and the Class object. We showed how related objects can implement a base interface. Then, it can be used further to streamline the object creation process. With this approach, there’s no need for type-casting, and also, it ensures the use of the Job interface, enforcing type-checking during development.

As usual, the code used in this article can be found over on GitHub.

Course – LS (cat=Java)
announcement - icon

Get started with Spring Boot and with core Spring, through the Learn Spring course:

>> CHECK OUT THE COURSE

res – REST with Spring (eBook) (everywhere)