【问题标题】:Multiple type parameters in Java methods, including existing classes and primitive data typesJava 方法中的多个类型参数,包括现有的类和原始数据类型
【发布时间】:2020-01-24 00:39:15
【问题描述】:

我有一个代码如下所示的类,我希望在其中使用任何代表数字的类/类型都是微不足道的。我发现自己定义了大量的方法,如下所示:

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 上得到了回答,但如果是这样的话,至少我想看看是否有人可以根据这个特定的例子向我解释。

【问题讨论】:

  • 您可能想查看using generics。不过,泛型不适用于基元,因此您必须为基元类型使用相应的包装类。这也可能允许您将 Ranges 扩展到纯数字类之外。
  • 使用BigDecimal(String)。有关详细信息,请参阅我的答案。

标签: java oop adapter dynamic-proxy


【解决方案1】:

使用类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);

【讨论】:

  • BigDecimal 没有采用Number 的构造函数。
  • 为什么要像BigDecimal那样有劣有优?
  • 哦,我知道,让我们为每个数字类型创建一个子类。样板文件要少得多。
  • 子类仍然意味着样板。使用泛型。见上面的例子。
【解决方案2】:

使用Builder Pattern。创建一个嵌套静态类,该类接受两个数字中的每一个的不同数据类型。从bytelong 的基本类型将扩大到long,并将float 扩大到doubleBigIntegers 可以转换为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,因为有very high values of type long that can't be precisely represented as 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 更改为&lt; 0 以匹配compareTo 合约,并添加了else 大小写,以便在需要时切换它们。

【讨论】:

  • 很多样板文件。
  • 构建器对象用于可选配置。将其用于必需配置可能会导致未配置需求,从而导致代码出现错误。
  • @VinceEmigh 我认为IllegalArgumentException 是为此而抛出的
  • @Xerz 是的,但是它将应该是编译器错误变成了运行时错误。构建器旨在允许客户端传递可选值。人们倾向于默认它,因为它是一个“简单”的解决方案 - 查看this answer 我写的表达同样的事情:建设者很容易被滥用,导致忽视真正的解决方案。特别是在这种情况下,任何新类型都需要您修改构建器,违反 O/C。它不可扩展。你需要一个合适的接口:Number,其中的值可以子类型化。
【解决方案3】:

来自BigDecimal(String)的文档:

这通常是将floatdouble 转换为BigDecimal 的首选方法,因为它不会受到BigDecimal(double) 构造函数的不可预测性的影响。


接受Number,使用String.valueOf转成String,再传入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 的类,包括您引入的新类型。

【讨论】:

  • 哦,我会尽快研究一下,看起来很有希望!如果我很惊讶您需要转换为 Strings 以避免不可预测性,这似乎是一个不必要的步骤
  • 当您使用的实际类型是 Integer 或 Long 时,由于 BigDecimal 导致内存和 CPU 使用效率低下,您会获得很多开销。如果这是在学校的家庭作业中使用的,那就没关系了。但如果它用于某些实际应用中,这可能是个问题。
  • @mentallurg 以什么方式?你有任何基准来证明这一点吗?由于潜在的装箱和解析为String,可能会导致性能下降,但如果值超过 32 位,BigDecimal(long) 会将值解析为 BigInteger,因此性能下降可能不如可扩展接口那么显着.
  • 关于内存:您需要什么样的基准测试?查看 BigDecimal 的内部结构并将其与其他类型进行比较。关于性能:写一个测试,你会看到。示例:java-performance.info/…。其中一项测试表明,使用 BigDecimal 实现的相同逻辑慢了 8 倍,对于其他操作,它慢了 200 倍,4.1 秒对 0.018 秒。如果您的应用程序需要 1 小时或 200 小时(超过一周)来完成相同的操作,情况会有所不同。
  • @mentallurg 这是比较BigDecimal 上的操作和原始long 上的操作...他使用的是BigDecimal,而不是long。这些统计数据不适用于此处。展示一个基准,证明 BigDecimal(long) 的使用比 BigDecimal(String) 快 x 倍
猜你喜欢
  • 2018-03-29
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-02-12
  • 2016-01-21
  • 2015-02-14
  • 1970-01-01
相关资源
最近更新 更多