Несколько параметров типа в методах Java, включая существующие классы и примитивные типы данных.

У меня есть класс с кодом, подобным следующему, где я хочу, чтобы использование любого класса/типа, представляющего число, было тривиальным. Я обнаруживаю, что определяю большое количество методов, например следующие:

public class Range {
    private BigDecimal inferior = new BigDecimal(0);
    private BigDecimal superior = new BigDecimal(1);

    public Range(BigDecimal inferior, BigDecimal superior) {
        if (inferior.compareTo(superior) == -1) {
            this.inferior = inferior;
            this.superior = superior;
        }
    }
    public Range(int inferior, int superior) {
        this(new BigDecimal(inferior), new BigDecimal(superior));
    }
    public Range(Integer inferior, Integer superior) {
        this(new BigDecimal(inferior), new BigDecimal(superior));
    }
    public Range(float inferior, float superior) {
        this(new BigDecimal(inferior), new BigDecimal(superior));
    }
    public Range(double inferior, double superior) {
        this(new BigDecimal(inferior), new BigDecimal(superior));
    }
}

Я даже не написал все возможные комбинации! Например, тот, который принимает float и double, или int и BigDecimal.

Как этого можно достичь чистым способом, чтобы были параметры, действительные для нескольких классов/типов данных, которые уже предопределены или даже примитивы? Я рассматривал адаптеры и прокси-серверы, но я регулярно обнаруживаю, что не понимаю объяснений, и я не могу понять, подходят ли они для моего варианта использования, и если да, то как - на этот вопрос, возможно, уже был дан ответ на SO, но если да, то по крайней мере Я хотел бы посмотреть, может ли кто-нибудь объяснить мне это в соответствии с этим конкретным примером.


person Xerz    schedule 23.09.2019    source источник
comment
Вы можете изучить использование дженериков. Однако дженерики не будут работать с примитивами, поэтому вам придется использовать соответствующие классы-оболочки для примитивных типов. Это потенциально может позволить вам расширить диапазоны за пределы чисто числовых классов.   -  person Mihir Kekkar    schedule 24.09.2019
comment
Используйте BigDecimal(String). Смотрите мой ответ для более подробной информации.   -  person Dioxin    schedule 25.09.2019


Ответы (3)


Используйте шаблон Builder. Создайте вложенный статический класс, который принимает каждый из отличительных типов данных для каждого из двух чисел. Примитивные типы от byte до long будут расширены до long и от float до double. BigIntegers можно преобразовать в BigDecimals, и BigDecimal ссылок будут скопированы.

public static class Builder {
    BigDecimal first;
    BigDecimal second;

    public void setFirst(long value) { first = new BigDecimal(value); }
    public void setFirst(double value) { first = new BigDecimal(value); }
    public void setFirst(BigInteger value) { first = new BigDecimal(value); }
    public void setFirst(BigDecimal value) { first = value; }
    public void setSecond(long value) { second = new BigDecimal(value); }
    public void setSecond(double value) { second = new BigDecimal(value); }
    public void setSecond(BigInteger value) { second = new BigDecimal(value); }
    public void setSecond(BigDecimal value) { second = value; }
    public Range build() {
        if (first == null || second == null) {
            throw new IllegalArgumentException("Must supply both values.");
        }
        return new Range(first, second);
    }
}

Шаблон Builder позволяет выполнять проверку перед созданием желаемого объекта и обходит «взрыв конструктора», который может произойти при попытке охватить все возможные комбинации. С n возможными типами у вас есть 2 * n методов установки конструктора вместо n2 конструкторов.

Я включил long, хотя его можно расширить до double на законных основаниях из соображений точности, потому что существуют очень высокие значения типа long, которые могут не может быть точно представлен как doubles.

Затем ваш конструктор становится:

public Range(BigDecimal first, BigDecimal second) {
    if (first.compareTo(second) < 0) {
        this.inferior = first;
        this.superior = second;
    }
    else {
        this.inferior = second;
        this.superior = first;
    }
}

Я изменил == -1 на < 0, чтобы он соответствовал контракту compareTo, и добавил случай else, который переключает их при необходимости.

person rgettman    schedule 23.09.2019
comment
Много шаблонов. - person mentallurg; 24.09.2019
comment
Объект построителя предназначен для дополнительной конфигурации. Использование его для конфигурации required может привести к тому, что требования не будут настроены, что может привести к ошибкам в коде. - person Dioxin; 25.09.2019
comment
@VinceEmigh Я думал, что IllegalArgumentException было брошено за это - person Xerz; 30.09.2019
comment
@Xerz Да, но это превращает то, что должно быть ошибкой компилятора, в ошибку времени выполнения. Построители были разработаны, чтобы позволить клиентам передавать необязательные значения. Люди склонны использовать его по умолчанию, так как это простое решение - посмотрите этот ответ. Я написал то же самое: строители легко злоупотребляют, что приводит к упущению реального решения. Особенно в этом случае любые новые типы потребуют от вас модификации компоновщика, нарушая O/C. Это не масштабируемо. Вам нужен правильный интерфейс: Number, значения которого могут быть подтипами. - person Dioxin; 28.12.2019

Используйте класс Number:

public Range(Number inferior, Number superior)

Integer, Long, Double - все они являются подклассами Number.

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

public class Range<T> {
    private T inferior;
    private T superior;

    public Range(T inferior, T superior) {
        this.inferior = inferior;
        this.superior = superior;
    }
}

Использование:

Range<Long> rangeLong = new Range<>(0L, 1000000000L);
Range<Double> rangeDouble= new Range<>(0d, 457.129d);
person mentallurg    schedule 23.09.2019
comment
BigDecimal не имеет конструктора, который принимает Number. - person Johannes Kuhn; 24.09.2019
comment
Почему вы хотите, чтобы у BigDecimal было меньше и больше? - person mentallurg; 24.09.2019
comment
О, я знаю, давайте создадим подкласс для каждого типа чисел. Это гораздо меньше шаблонов. - person Johannes Kuhn; 24.09.2019
comment
Подклассы по-прежнему означают шаблон. Используйте дженерики. См. пример выше. - person mentallurg; 24.09.2019
comment
Я, вероятно, не приму этот ответ, так как я не чувствую, что он отвечает на общую проблему параметров нескольких типов, НО Number работает очень хорошо и позволяет использовать примитивные ints, floats, doubles и longs! - person Xerz; 30.09.2019
comment
Использование одного параметра типа не позволило бы ему сделать то, что ему нужно: Я даже не написал все возможные комбинации! Например, тот, который принимает float и double, или int и BigDecimal.. Вам потребуется 2, но даже в этом случае значения не будут равны BigDecimal к тому времени, когда они будут назначены полю, чего и хочет OP - это не сработает. Кроме того, нет объяснения, как использовать Range(Number, Number). - person Dioxin; 04.10.2019

Из документов BigDecimal(String):

Как правило, это предпочтительный способ преобразования float или double в BigDecimal, поскольку он не страдает от непредсказуемости конструктора BigDecimal(double).


Примите Number, используйте String.valueOf для преобразования в строку, затем перейдите в BigDecimal:

public class Range {
    private BigDecimal inferior;
    private BigDecimal superior;

    public Range(Number inferior, Number superior) {
        this.inferior = new BigDecimal(String.valueOf(inferior));
        this.superior = new BigDecimal(String.valueOf(superior));
    }
}

Любые классы, расширяющие Number, включая введенные вами новые типы, будут автоматически поддерживаться.

person Dioxin    schedule 24.09.2019
comment
О, я посмотрю на это, как только смогу, кажется многообещающим! Во всяком случае, я удивлен, что вам нужно преобразовать в Strings, чтобы избежать непредсказуемости, кажется ненужным шагом - person Xerz; 29.09.2019
comment
Когда вы используете реальные типы Integer или Long, вы получаете много накладных расходов из-за неэффективного использования памяти и ЦП из-за BigDecimal. Если это используется в домашней работе в школе, то не беда. Но если он используется в каком-то реальном приложении, это может быть проблемой. - person mentallurg; 30.09.2019
comment
@mentallurg Каким образом? У вас есть тесты, подтверждающие это? Может быть падение производительности из-за потенциальной упаковки и синтаксического анализа в String, но BigDecimal(long) анализирует значение в BigInteger, если значение превышает 32 бита, поэтому падение производительности может быть не таким значительным, как масштабируемый интерфейс. - person Dioxin; 02.10.2019
comment
Касательно памяти: Какой бенчмарк вам нужен? Посмотрите на внутреннюю структуру BigDecimal и сравните ее с другими типами. По поводу производительности: напишите тест и увидите. Пример: java-performance.info/. Один из тестов показывает, что та же логика, реализованная с помощью BigDecimal, работает медленнее в 8 раз, для другой операции — медленнее в 200 раз, 4,1 с против 0,018 с. Имеет значение, требуется ли вашему приложению 1 час или 200 часов (более недели) для одной и той же операции. - person mentallurg; 02.10.2019
comment
@mentallurg Это сравнение операций над BigDecimal с операциями над примитивом long... Он использует BigDecimal, а не long. Эта статистика здесь не работает. Покажите тест, доказывающий, что использование BigDecimal(long) в x раз быстрее, чем BigDecimal(String) - person Dioxin; 02.10.2019
comment
Нас не волнует BigDecimal(long) vs BigDecimal(String) или любой другой BigDecimal. Мой первый комментарий касается простых типов, таких как Long (или Double, или Float) против BigDecimal. Это проблема вашего подхода. - person mentallurg; 02.10.2019
comment
@mentallurg Он не использует Long, он использует BigDecimal. Ему нужно BigDecimal, поэтому ваша статья здесь не применима. Он не использует примитивы, которые использовались для версии 0.018. В статье не показано 200-кратного замедления с моим подходом. - person Dioxin; 04.10.2019
comment
@Xerz Это соответствовало вашим потребностям? - person Dioxin; 04.10.2019
comment
@VinceEmigh Эй, извините за мой поздний ответ! Это было для небольшого примера домашнего задания, поэтому для этого все было хорошо, однако я не уверен, какой вариант лучше всего подходит для более профессиональной кодовой базы. - person Xerz; 25.12.2019
comment
@Xerz Использование BigDecimal(String) является обязательным для получения желаемых результатов от значений с плавающей запятой. Что касается того, как вы разрабатываете интерфейс, зависит от вас. Построитель не будет масштабируемым, так как поддержка новых типов требует от вас модификации построителя, что нарушает принцип открытия/закрытия. Пока любые новые типы расширяют Number, Range будет поддерживать этот тип. - person Dioxin; 26.12.2019