Метод Java, который возвращает различные типы общих списков

В настоящее время я пытаюсь написать метод, который просматривает список объектов Ant и возвращает список AntScouts, которые расширяют Ant. В общем, List<Ant> может содержать множество различных Объектов, которые наследуются от Ant.

У меня также есть перечисление для разных видов муравьев:

public enum AntType {
    QUEEN,WARRIOR,GATHERER,SCOUT;

    public Class getClass(AntType type){
        return switch (type) {
            case QUEEN -> AntQueen.class;
            case WARRIOR -> AntWarrior.class;
            case GATHERER -> AntGatherer.class;
            case SCOUT -> AntScout.class;
        };
    }
}

Это перечисление вызывает предупреждение:

Raw use of parameterized class 'Class'

И это метод, который в настоящее время возвращает List<Ant>.

public List<Ant> getAntsType(AntType type){
    return ants.stream().filter(ant -> ant.getType() == type).toList();
}

Как я могу написать метод, чтобы он получал перечисление AntType в качестве аргумента и возвращал List<AntScout> или List<AntWarrior>, соответствующие перечислению? Я ДЕЙСТВИТЕЛЬНО не хочу использовать Class<T> clazz в качестве аргумента, так как это противоречит смыслу перечисления. (Я также использую это перечисление в другом месте, поэтому не могу от него избавиться)

Как я могу написать метод, чтобы он получал перечисление AntType в качестве аргумента и возвращал список или список, соответствующий перечислению?

Обновлено: этот комментарий, вероятно, ближе всего подходит к желаемому решению: Метод Java, который возвращает различные типы общих списков

Самый правильный ответ - тот, который прислал Александр Иванченко. Этот подход можно использовать для разрешения внутренних классов, принадлежащих членам иерархии классов. Скажем, например, что вместо того, чтобы интересоваться иерархией классов Ant, вы заинтересованы в получении каждого из построителей объектов Ant, которые являются внутренним атрибутом каждого класса. Если это так, вы не сможете использовать какие-либо из предложенных здесь предложений, кроме того, что предложил Александр. Если вы хотите изучить это подробнее, вы можете посмотреть это видео по теме.

hfontanez 08.11.2022 04:46
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
В компьютерном программировании биты играют важнейшую роль в представлении и манипулировании данными на двоичном уровне. Побитовые операции...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
3
1
83
4
Перейти к ответу Данный вопрос помечен как решенный

Ответы 4

Я бы изменил ваш метод перечисления AntType, чтобы он действовал как фильтр в вашем потоке. Мне пришлось угадать остальную часть иерархии классов, но это может дать вам отправную точку.

import java.util.List;
import static java.util.stream.Collectors.toList;
public class Demo {
    private List<Ant> ants = List.of(
            new AntQueen(),
            new AntScout(),
            new AntGatherer(),
            new AntWarrior());

    public static void main(String[] args) {
        var demo = new Demo();
        System.out.println(demo.getAntsType(AntType.QUEEN));
    }

    public List<Ant> getAntsType(AntType type) {
        return ants.stream().filter(type::matches).collect(toList());
    }
}

class Ant {}
class AntQueen extends Ant {}
class AntWarrior extends Ant {}
class AntGatherer extends Ant {}
class AntScout extends Ant {}

enum AntType {
    QUEEN, WARRIOR, GATHERER, SCOUT;

    public boolean matches(Ant a) {
        return switch (this) {
            case QUEEN -> a instanceof AntQueen;
            case WARRIOR -> a instanceof AntWarrior;
            case GATHERER -> a instanceof AntGatherer;
            case SCOUT -> a instanceof AntScout;
        };
    }
}

Спасибо за ваше время. Это выглядит как интересный подход, но, к сожалению, он не решает основную проблему для меня. Я хотел бы вернуть не просто List<Ant>, а список конкретных типов муравьев на основе заданного перечисления AntType. Поэтому, когда я вызываю getAntsType(AntType.WARRIOR), я хочу вернуть List<AntWarrior>. То же самое для AntScout и так далее.

Wewius 08.11.2022 00:15

Есть несколько способов сделать это.

Во-первых, исправьте метод в вашем перечислении:

public enum AntType {
    QUEEN,WARRIOR,GATHERER,SCOUT;

    public Class<? extends Ant> getImplClass(){
        return switch (this) {
            case QUEEN -> AntQueen.class;
            case WARRIOR -> AntWarrior.class;
            case GATHERER -> AntGatherer.class;
            case SCOUT -> AntScout.class;
        };
    }
}

Поскольку это нестатический метод, вам не нужно принимать тип в качестве аргумента. В вашем примере непонятно, откуда берется список, но если я добавлю его в качестве аргумента, он будет выглядеть так:

public static List<Ant> getAntsType(AntType type, List<Ant> ants){
    return ants.stream().filter(ant -> ant.getClass() == type.getImplClass()).toList();
}

Второй способ сделать это — добавить метод getType() в класс Ant, который возвращает переменную типа, установленную конструктором.

public class Ant {
    private AntType type;

    protected Ant(AntType type) {
        this.type = type;
    }

    public AntType getType() {
        return type;
    }
}

Затем вы устанавливаете тип в каждом из конструкторов подкласса:

public class AntQueen extends Ant {
    protected AntQueen() {
        super(AntType.QUEEN);
    }
}

Тогда код фильтрации выглядит так:

public static List<Ant> getAntsType(AntType type, List<Ant> ants){
    return ants.stream().filter(ant -> ant.getType() == type).toList();
}

Спасибо за ваше время и ответ. Я действительно пробовал ваши решения раньше, и они работают, но тип возврата getAntsType по-прежнему List<Ant>. Проблема, которую я пытаюсь решить, заключается в том, что я хочу, чтобы возвращаемый тип метода был List<AntWarrior>, List<AntQueen> и так далее. В зависимости от аргумента AntType, переданного методу.

Wewius 08.11.2022 00:03

Извините, это не было ясно в вашем вопросе. То, о чем вы просите, невозможно, поскольку компилятор Java должен заранее знать, какой тип будет возвращать метод. Невозможно динамически возвращать другой тип из метода, и на самом деле вы бы этого не хотели, поскольку основным преимуществом наследования является полиморфизм. Могу я спросить, почему вы хотите, чтобы возвращаемый тип был динамическим? Может быть, я все еще не понимаю вашей потребности. Вы можете ввести возвращаемое значение List<? расширяет Ant>, но нет возможности заранее связать его с конкретным экземпляром Ant.

Jared Renzullo 08.11.2022 00:13

Я полагал, что это невозможно по той причине, о которой вы упомянули. Думаю, я все еще слишком неопытен и подумал: «Может быть, есть способ обойти это, и мой профессор просто не сказал нам, чтобы мы не путали нас» (я учусь в университете) В настоящее время мне поручено задание и должны реализовать муравьев, которые перемещаются по графу и выполняют разные действия. У меня есть класс AntColony, который содержит List<Ant> и должен вызывать разные методы для разных видов муравьев. Поэтому мне нужно получить List<AntScout> из List<Ant> и так далее для каждого типа муравья.

Wewius 08.11.2022 00:24

Ах, в таком случае вы можете сделать это. Вы можете сделать это небезопасно следующим образом: .getType() == тип).toList(); } Затем используйте его следующим образом: List<AntQueen> antQueens = getAntsType(AntType.QUEEN, ants); Проблема в том, что во время компиляции нет проверки правильности того, что вы делаете. Другими словами, вы можете сделать это, и компилятор решит, что это нормально: List<AntGatherer> antQueens = getAntsType(AntType.QUEEN, ants);

Jared Renzullo 08.11.2022 00:48

Главное, что мешает нам сделать его безопасным при компиляции, — это ограничение на то, что перечисления не могут иметь универсальные типы. Поэтому, если вы будете следовать решению Виктора, вы можете сделать его безопасным при компиляции, передав AntType<T>.

Jared Renzullo 08.11.2022 00:56

Это могло бы быть возможно, если бы enums могли быть общими, но они не могут. Однако это не имеет большого значения. Просто используйте конечный класс с кучей полей public static final и приватным конструктором. Конечно, немного многословно, но так же эффективно, как enum.

Кроме того, ваш метод getClass() должен быть либо статическим методом с switch, либо методом экземпляра без switch. Последний намного лучше, поэтому пошел по этому пути. Кроме того, называть его getClass() не очень хорошая идея, поскольку он не связан с методом Object.getClass(). Так я назвал это getAntTypeClass().

И это результат:

public class Main {
    public static void main(String[] args) {
        System.out.println(AntType.QUEEN.getAntTypeClass().getName());
        System.out.println(AntType.SCOUT.getAntTypeClass().getName());
    }
}

final class AntType<T extends Ant> {
    public static final AntType<AntQueen>    QUEEN    = new AntType<>(AntQueen.class   );
    public static final AntType<AntWarrior>  WARRIOR  = new AntType<>(AntWarrior.class );
    public static final AntType<AntGatherer> GATHERER = new AntType<>(AntGatherer.class);
    public static final AntType<AntScout>    SCOUT    = new AntType<>(AntScout.class   );

    private final Class<T> antTypeClass;

    private AntType(Class<T> antTypeClass) {
        this.antTypeClass = antTypeClass;
    }

    public Class<T> getAntTypeClass() {
        return antTypeClass;
    }
}

interface Ant {}
class AntWarrior implements Ant {}
class AntGatherer implements Ant {}
class AntScout implements Ant {}
class AntQueen implements Ant {}

Посмотрите, как это работает на ideone.

Ответ принят как подходящий

Используйте силу полиморфизма

Как я могу написать метод, чтобы он получал перечисление AntType в качестве аргумента и возвращал список или список, соответствующий перечислению?

Вы перепроектируете свой код без уважительной причины.

Когда вы используете наследование, ваши классы должны быть спроектированы таким образом, чтобы можно было извлечь выгоду из полиморфизма.

т.е. используя супертип Ant для всех ваших объектов и взаимодействуя с ними через переопределенное поведение без необходимости делать различие между конкретными реализациями и работой с помощью приведения типов.

Поэтому ваш метод, возвращающий List<Ant>, вполне подходит.

И даже если вы хотите получить List<AntQueen> или List<AntScout> в результате выполнения метода, вам потребуется использовать переменную универсального типа T или, скорее, T extends Ant, а это будет означать, что вам нужно средство представления T. И enum не поможет вам с этой задачей, потому что в Java перечисления не могут быть универсальными. Вам нужно предоставить в качестве аргумента метода экземпляр T или Class<T>.

public <T extends Ant> List<T> getAntsByType(Class<T> tClass) {
    return ants.stream().filter(tClass::isAssignableFrom).toList();
}

Но я бы посоветовал придерживаться первоначальной версии, возвращающей список супертипа Ant, объявляя метод getType(), который возвращает экземпляр перечисления AntType.

public List<Ant> getAntsByType(AntType type) {
    return ants.stream().filter(ant -> ant.getType() == type).toList();
}

И, как я уже сказал, Java-перечисления не могут быть универсальными, через них невозможно получить Class<T>. Следовательно, вы можете удалить надуманный метод getClass() из AntType.

public enum AntType {
    QUEEN, WARRIOR, GATHERER, SCOUT;
}

Имитация собственного типа

Но если вы все еще убеждены, что логика вашего приложения требует возможности генерировать список конкретного типа, такого как List<AntScout>, из списка супертипа, то вы можете использовать привязку рекурсивного типа.

Для этого вам нужно определить супертип как Ant<T extends Ant<T>>.

Этот подход также называется идиомой имитации собственного типа, и его можно наблюдать в объявлении родительского типа всех перечислений java.lang.Enum<E extends Enum<E>> и в некоторых других частях JDK, таких как метод Collections.sort(List<T>) где T определяется как <T extends Comparable<? super T>>.

Давайте применим идиому самотипа для этого случая.

Рассмотрим супертип Ant, определенный как интерфейс, объявляющий самовозвратный метод (вы можете перейти в абстрактный класс, если вам нужно объявить некоторые скелетные реализации и общие поля):

interface Ant<T extends Ant<T>> {
    T self();
    AntType getType();
}

И вот пара конкретных классов:

public static class AntWarrior implements Ant<AntWarrior> {
    
    @Override
    public AntWarrior self() {
        return this;
    }
    
    @Override
    public AntType getType() {
        return AntType.WARRIOR;
    }
}

public static class AntScout implements Ant<AntScout> {
    @Override
    public AntScout self() {
        return this;
    }
    
    @Override
    public AntType getType() {
        return AntType.SCOUT;
    }
}

Вот как мы можем выполнить преобразование с помощью метода self():

@SuppressWarnings("unchecked")
public static <T extends Ant<T>> List<T> getAntsByType(List<Ant<?>> ants,
                                                       AntType type) {
    return ants.stream()
        .filter(ant -> ant.getType() == type)
        .map(ant -> (T) ant.self())
        .toList();
}

Пример использования:

public static void main(String[] args) {
    List<Ant<?>> ants = List.of(new AntWarrior(), new AntScout());

    // compiles and runs without issues
    List<AntWarrior> antWarriors = getAntsByType(ants, AntType.WARRIOR);
    System.out.println(antWarriors);

    // compiles and runs without issues
    List<AntScout> antScouts = getAntsByType(ants, AntType.SCOUT);
    System.out.println(antScouts);
}

Вывод:

[AntWarrior{}]
[AntScout{}]

Ссылка на онлайн демо

Этот подход выглядит знакомым :) +1

hfontanez 08.11.2022 03:44

Иван, я исправил пример, чтобы использовать antScouts. Вы используете antWarriors дважды. Но вам придется внести изменения на сайте JDoodle.

hfontanez 08.11.2022 03:49

@hfontanez Спасибо, исправлено. Это побочный продукт ночного кодирования.

Alexander Ivanchenko 08.11.2022 04:54

Другие вопросы по теме