Sonny Mad
Sonny Mad

Reputation: 31

Why no compile error at CustomerService service = ServiceFactory.getInstance().getServiceType(ServiceType.CUSTOMER);

SuperService:

package service;

public interface SuperService {
}

CustomerService:

package service.custom;

import dto.Customer;

import java.util.List;

public interface CustomerService{
    boolean add(Customer customer);
    Customer search(String id);
    boolean update(String id, Customer customer);
    boolean delete(String id);
    List<Customer> getAll();
}

CustomerServiceImpl:

package service.custom.impl;

import dto.Customer;


public class CustomerServiceImpl{
    public boolean add(Customer customer) {
        return false;
    }
}

ServiceFactory:

package service;

import service.custom.impl.CustomerServiceImpl;
import service.custom.impl.ItemServiceImpl;
import service.custom.impl.OrderServiceImpl;
import util.ServiceType;

public class ServiceFactory {
    private static ServiceFactory instance;

    private ServiceFactory(){

    }

    public static ServiceFactory getInstance(){
        if(instance==null){
            instance= new ServiceFactory();
        }
        return instance;
    }

    public <T extends SuperService> T getServiceType(ServiceType type){
        switch (type){
            case CUSTOMER:return (T) new CustomerServiceImpl();
            case ITEM:return (T) new ItemServiceImpl();
            case ORDER:return (T) new OrderServiceImpl();
        }
        return null;
    }
}

CustomerFormController:

package controller;

import com.jfoenix.controls.JFXButton;
import com.jfoenix.controls.JFXTextField;
import dto.Customer;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import service.ServiceFactory;
import service.custom.CustomerService;
import util.ServiceType;

public class CustomerFormController {

    @FXML
    private JFXButton btnAdd;

    @FXML
    private JFXButton btnDelete;

    @FXML
    private JFXButton btnReload;

    @FXML
    private JFXButton btnSearch;

    @FXML
    private JFXButton btnUpdate;

    @FXML
    private TableColumn<?, ?> colAddress;

    @FXML
    private TableColumn<?, ?> colId;

    @FXML
    private TableColumn<?, ?> colName;

    @FXML
    private TableColumn<?, ?> colSalary;

    @FXML
    private TableView<?> tblCustomers;

    @FXML
    private JFXTextField txtAddress;

    @FXML
    private JFXTextField txtId;

    @FXML
    private JFXTextField txtName;

    @FXML
    private JFXTextField txtSalary;



    @FXML
    void btnAddOnAction(ActionEvent event) {
        String idText = txtId.getText();
        String nameText = txtName.getText();
        String addressText = txtAddress.getText();
        double salary = Double.parseDouble(txtSalary.getText());

        Customer customer = new Customer(idText, nameText, addressText, salary);
        CustomerService service = ServiceFactory.getInstance().getServiceType(ServiceType.CUSTOMER);
        service.add(customer);
    }

    @FXML
    void btnDeleteOnAction(ActionEvent event) {

    }

    @FXML
    void btnReloadOnAction(ActionEvent event) {

    }

    @FXML
    void btnSearchOnAction(ActionEvent event) {

    }

    @FXML
    void btnUpdateOnAction(ActionEvent event) {

    }
}

This is a layered architecture design and here I have tried to use the factory design pattern to pass a CustomerServiceImpl object from the service layer to the controller/presentation layer. I was trying to understand bounded type generics and decided to do an experiment to understand the concept then only i ran into the problem. Can you help me understand why there is no compile error at CustomerService service = ServiceFactory.getInstance().getServiceType(ServiceType.CUSTOMER); since the CustomerService doesn't extend SuperService and CustomerServiceImpl doesn't implement CustomerService so shouldn't the compile understand that a CustomerService reference cannot be used for a CustomerServiceImpl object?

Upvotes: 2

Views: 96

Answers (3)

jewelsea
jewelsea

Reputation: 159546

This answer is slightly off-topic but related. The direct answers to your question are provided by other answers. What the answer offers is a relatively type-safe alternative to the code you have provided in your question.

TypeReferences

You may be interested in TypeReferences, which provides a mechanism to encode generic type information with data so that it can be referenced at runtime if needed (including type data such as the type of list elements that can be erased during the compilation process).

Internally, various libraries that need generic info at runtime use similar mechanisms, for example, within the JDK, Spring, Jackson, and some Google APIs.

Using code created by Bob Lee, the original Guice developer, based on Neal Gafter's concept:

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.List;

/**
 * References a generic type.
 *
 * @author [email protected] (Bob Lee)
 */
public abstract class TypeReference<T> {

    private final Type type;
    private volatile Constructor<?> constructor;

    protected TypeReference() {
        Type superclass = getClass().getGenericSuperclass();
        if (superclass instanceof Class) {
            throw new RuntimeException("Missing type parameter.");
        }
        this.type = ((ParameterizedType) superclass).getActualTypeArguments()[0];
    }

    /**
     * Instantiates a new instance of {@code T} using the default, no-arg
     * constructor.
     */
    @SuppressWarnings("unchecked")
    public T newInstance()
            throws NoSuchMethodException, IllegalAccessException,
                   InvocationTargetException, InstantiationException {
        if (constructor == null) {
            Class<?> rawType = type instanceof Class<?>
                ? (Class<?>) type
                : (Class<?>) ((ParameterizedType) type).getRawType();
            constructor = rawType.getConstructor();
        }
        return (T) constructor.newInstance();
    }

    /**
     * Gets the referenced type.
     */
    public Type getType() {
        return this.type;
    }
}

You can use this as a replacement for your ServiceFactory::getInstance method and your ServiceType enumerated type.

package application;

import java.util.ArrayList;
import java.util.List;

class CustomerService {};
class Customer {};
class Item {};
class Order {};

public class TypeReferenceApp {
    public static void main(String[] args) throws Exception {
        Customer customer = new TypeReference<Customer>() {}.newInstance();
        Item item = new TypeReference<Item>() {}.newInstance();
        Order order = new TypeReference<Order>() {}.newInstance();

        List<Customer> customerList = new TypeReference<ArrayList<Customer>>() {}.newInstance();

        // if uncommented will fail to compile with compiler error:
        //    java: incompatible types: application.Customer cannot be converted to application.CustomerService
        // CustomerService customerService = new TypeReference<Customer>() {}.newInstance();
    }
}

Note that the code can enforce compile time safety.

The code equivalent to the statement in your question:

compile error at CustomerService service = ServiceFactory.getInstance().getServiceType(ServiceType.CUSTOMER);

which would be:

CustomerService customerService = new TypeReference<Customer>() {}.newInstance(); 

generates a compile-time type error.

This kind of meta-data type information and the Java generic code related to it can be hard to understand in my opinion :-)

Recommendations (Opinions)

This kind of code is fine as a learning exercise, but, otherwise, it is better to use an established dependency injection framework, such as Guice or Spring, either of which can be integrated with JavaFX (and FXML if needed).

If all you need is the type reference style functionality, rather than a full dependency injection framework, then consider adopting the TypeToken from Guava.

For a third-party JavaFX UI control framework, JFoenix is not (currently) maintained, instead, consider MaterialFX (or just the standard JavaFX controls).

Upvotes: 3

Panagiotis Bougioukos
Panagiotis Bougioukos

Reputation: 19173

The answer from berse2212 is spot on. I will add another view on how you can see the compilation error.

Basically you are always casting to SuperService since you don't provide any specific type for this generic. This will blow up in runtime and not in compilation time as the previous answer explains.

However in case you provide a specific type for this Generic T when using the method, you will see the error during compilation.

CustomerService service = ServiceFactory.getInstance().<CustomerService>getServiceType(ServiceType.CUSTOMER);

Upvotes: 5

berse2212
berse2212

Reputation: 925

You don´t get a Compile Error because you are casting here:

public <T extends SuperService> T getServiceType(ServiceType type){
    switch (type){
        case CUSTOMER:return (T) new CustomerServiceImpl();
        case ITEM:return (T) new ItemServiceImpl();
        case ORDER:return (T) new OrderServiceImpl();
    }
    return null;
}

With type erasure in place you basically casting all the Impls to SuperService.

Now since SuperService is an interface you won´t get a compile error, but you will get a runtime error!

It basically boils down to this:

Casting to unrelated interfaces does not give you a compile error.

More info can be found here: Why does it compile when casting to an unrelated interface?

Upvotes: 5

Related Questions