Общее наследование Java «непроверенное приведение»

#java #oop #inheritance #design-patterns #factory

#java #ооп #наследование #шаблоны проектирования #фабрика

Вопрос:

Я реализовал простую систему обработки платежей с использованием Java generics. Он компилируется и работает во время выполнения, но меня смущает предупреждение «непроверенное приведение».

 enum PaymentType {
    CARD, SAVED_CARD
}

interface PayData {
}

class CardPayData implements PayData {
    private String cardNumber;
    private String cvc;
}

class SavedCardPayData implements PayData {
    private String cardId;
}

interface PayService<T extends PayData> {
    void pay(T payData);
}

class CardPayService implements PayService<CardPayData> {
    @Override
    public void pay(CardPayData cardPayData) {
        // i need concrete class CardPayData here
    }
}

class SavedCardPayService implements PayService<SavedCardPayData> {
    @Override
    public void pay(SavedCardPayData payData) {
        // i need concrete class SavedCardPayData here
    }
}

class PayServiceFactory {
    private CardPayService cardPayService = new CardPayService();
    private SavedCardPayService savedCardPayService = new SavedCardPayService();

    public PayService getService(PaymentType paymentType) {
        if (paymentType.equals(PaymentType.CARD))
            return cardPayService;
        else
            return savedCardPayService;
    }
}

class PaymentProcessor {
    PayServiceFactory payServiceFactory = new PayServiceFactory();

    public void serveRequest(PayData payData, PaymentType paymentType) {
//        here i have 'unchecked cast' warning
        PayService<PayData> payService = (PayService<PayData>) payServiceFactory.getService(paymentType);
        payService.pay(payData);
    }
}
  

Любые попытки отойти от такого предупреждения приводят меня к ошибкам компиляции. Например, я попытался вернуть общий тип из моего фабричного метода и не получил ничего, кроме ошибки компиляции:

 // return generic
public PayService<? extends PayData> getService(PaymentType paymentType) { ... }
  
  public void serveRequest(PayData payData, PaymentType paymentType) {
        PayService<? extends PayData> payService =  payServiceFactory.getService(paymentType);
// error here:
// pay (capture <? extends PayData>) in PayService cannot be applied to (PayData)
        payService.pay(payData);
    }
  

Комментарии:

1. getService может возвращать только PayService<?> . Не забудьте подстановочный знак, иначе это необработанный тип. И тогда у вас возникает проблема, которая payData может быть неправильного типа. В принципе, этот подход не может быть безопасным для типов.

Ответ №1:

Дизайн ваших классов по своей сути небезопасен для типов. В частности, serveRequest метод:

 public void serveRequest(PayData payData, PaymentType paymentType) {
    PayService<PayData> payService = (PayService<PayData>) payServiceFactory.getService(paymentType);
    payService.pay(payData);
}
  

Нет никакого способа обеспечить, чтобы тип среды выполнения payData был совместим с тем, что payService.pay ожидается. В зависимости от paymentType , getService может возвращаться PayService<Anything> во время выполнения, поэтому payService.pay можно ожидать любой тип во время выполнения. Поскольку тип, который payData должен быть, может быть известен только во время выполнения, вы не можете применить его во время компиляции.

Лучшее, что вы можете сделать, это поставить SuppressWarnings и сделать payData generic:

 @SuppressWarnings("unchecked")
public <T extends PayData> void serveRequest(T payData, PaymentType paymentType) {
    PayService<T> payService = (PayService<T>) payServiceFactory.getService(paymentType);
    payService.pay(payData);
}
  

Комментарии:

1. Спасибо за ваш ответ. Вы сказали, что нет никакого способа обеспечить это, так называется ли это «Стирание типа» ?

2. @IlyaPechenkin Удаление типа просто ссылается на тот факт, что во время выполнения дженериков вообще нет. Все универсальные типы «стираются» и превращаются в приведения. Например, во время выполнения payService.pay примет объект` в качестве параметра, а затем приведет параметр к любому подходящему типу. Я не думаю, что ваша проблема очень связана со стиранием типа.