Возврат обобщенного суперкласса

У меня возникли проблемы с моей структурой наследования для группы классов-оболочек с дженериками. Это в основном структура:

public abstract class SuperWrapper<E extends Super>{
    private E wrappedObject;
    public E getWrappedObject(){
        return wrappedObject;
    }
}

public abstract class MiddleWrapper<E extends Middle> extends SuperWrapper<E>{}

public class SubAWrapper extends MiddleWrapper<SubA>{}

public class SubBWrapper extends MiddleWrapper<SubB>{}

Обернутые классы следуют одной и той же структуре наследования, поэтому в основном:

public class Super{}

public class Middle extends Super{}

public class SubA extends Middle{}

public class SubB extends Middle{}

Эти обернутые классы не мои и не могут быть изменены (что является причиной классов-оболочек). Эта структура очень хорошо работает для большинства целей, однако возникают некоторые проблемы, когда я хочу, чтобы метод возвращал либо SubAWrapper, либо SubBWrapper.

Это то, что я пробовал до сих пор:

public static MiddleWrapper<?> findMiddle(String id){
    SubAWrapper a = new SubAWrapper();
    SubBWrapper b = new SubBWrapper();
    if(returnSubA){
        return a;
    } else{
        return b;
    }
}

Это компилируется и работает, но SonarQube предупреждает об использовании подстановочного знака в возвращаемом типе, и, судя по моим гуглениям, это не одно из предупреждений, которые следует игнорировать.

Другой путь:

public static <E extends Middle> MiddleWrapper<E> findMiddle(String id){
    SubAWrapper a = new SubAWrapper();
    SubBWrapper b = new SubBWrapper ();
    if(returnSubA){
        return (MiddleWrapper<E>) a;
    } else{
        return (MiddleWrapper<E>) b;
    }
}

Это компилируется и работает с текущим кодом, НО это кажется опасным. Если вызывающая сторона использует этот метод следующим образом: MiddleWrapper<SubA> middle = findMiddle("1"); он будет скомпилирован нормально, но если findMiddle попытается вернуть SubBWrapper, во время выполнения возникнет исключение ClassCastException. То, что я хотел бы работать, но не было:

public static MiddleWrapper<Middle> findMiddle(String id){
    SubAWrapper a = new SubAWrapper ();
    SubBWrapper b = new SubBWrapper ();
    if(returnSubA){
        return (MiddleWrapper<Middle>) a; //Compile error here
    } else{
        return (MiddleWrapper<Middle>) b; //and here
    }
}

Итак, мой вопрос в основном заключается в том, есть ли правильный способ написать метод findMiddle, который компилируется, запускается и следует лучшим практикам?

В качестве дополнительного кредита, есть ли способ написать метод, чтобы он возвращал список, содержащий как SubA, так и SubB. Вот так, но без подстановочного знака:

public static List<MiddleWrapper<?>> getAllMiddle(){
    List<MiddleWrapper<?>> ret = Lists.newArrayList();
    ret.add(new SubAWrapper());
    ret.add(new SubBWrapper());
    return ret;
}

пс. На данный момент мы все еще используем Java 6, но обновление до Java 8 запланировано на следующий год, поэтому решения, использующие функциональность Java 7-8, в конечном итоге все равно будут полезны.


person MatsT    schedule 21.07.2017    source источник


Ответы (2)


MiddleWrapper — это общий класс, параметризованный классом Middle:

public abstract class MiddleWrapper<E extends Middle> extends SuperWrapper<E>{}

Если вы хотите предоставить единственный метод, который возвращает List из MiddleWrapper, по крайней мере, с двумя различными универсальными типами для MiddleWrapper, у вас не так много вариантов.

Если вам не нужно устанавливать какое-либо ограничение для подкласса Middle, который может быть возвращен.
Показанный вами метод подходит:

public static List<MiddleWrapper<?>> getAllMiddle() {
  List<MiddleWrapper<?>> ret = Lists.newArrayList();
  ret.add(new SubAWrapper());
  ret.add(new SubBWrapper());
  return ret;
}

Ты сказал :

Это компилируется и работает, но SonarQube предупреждает об использовании подстановочного знака в возвращаемом типе, и, судя по моим гуглениям, это не одно из предупреждений, которые следует игнорировать.

Почему использование ? в MiddleWrapper<?> было бы плохой практикой?
Класс MiddleWrapper является универсальным, и в вашем методе вы можете вернуть любой тип Middle, используемый в качестве универсального.
Это способ сделать для этого варианта использования.

Теперь, если вам нужно установить ограничение на подкласс Middle, который может быть возвращен.
Например, какой-то подкласс Middle, но не некоторые другие.

Вы можете ввести промежуточный класс, который расширяет Middle, заставляет SubA и SubB расширять его и вводить List вместе с ним в методе getAllMiddle().

public class ValidMiddle extends Middle {...}

public class SubA extends ValidMiddle{...}

public class SubB extends ValidMiddle{...}

И метод должен быть:

public static List<MiddleWrapper<? extends ValidMiddle>> getAllMiddleWithMoreSpecificType() {
  List<MiddleWrapper<? extends ValidMiddle>> ret = Lists.newArrayList();
  ret.add(new SubAWrapper());
  ret.add(new SubBWrapper());
  return ret;
}

Обратите внимание, что этот способ немного более подробный для клиента, который должен манипулировать этим объявленным типом:

 List<MiddleWrapper<? extends ValidMiddle>> listOfWrappers = getAllMiddle();
person davidxxx    schedule 21.07.2017
comment
Причина, по которой это было бы плохой практикой, немного объясняется здесь: sonarcloud.io/organizations/default /rules#q=wildcard Также по различным вопросам переполнения стека, таким как stackoverflow.com/questions/22815023/. Могу ли я использовать MiddleWrapper‹?› или MiddleWrapper‹? extends Middle› в возвращаемых типах решается легко, вопрос в том, можно ли без него. - person MatsT; 21.07.2017
comment
Для этого нужно создать экземпляр MiddleWrapper без указания каких-либо общих свойств. Удалив общий слой, вы могли бы. Наконец, это компромисс между общими ограничениями и простотой использования API. - person davidxxx; 21.07.2017

В примере, указанном в параграфе "Другой способ", ClassCastException можно избежать, принудительно возвращая тип:

public static <E extends Middle> MiddleWrapper<E> findMiddle(String id, Class<E> clazz){
        SubAWrapper a = new SubAWrapper();
        SubBWrapper b = new SubBWrapper();
        if(returnSubA){
        //check here if a/b is instance of clazz
            return (MiddleWrapper<E>) a;
        } else{
            return (MiddleWrapper<E>) b;
        }
    }

И вызов метода будет:

MiddleWrapper<SubA> middle = findMiddle("1", SubA.class);
person krisp    schedule 21.07.2017