【问题标题】:Is it a bad practice to use arrays as parameter(s) to return multiple values使用数组作为参数返回多个值是一种不好的做法吗
【发布时间】:2013-01-27 00:36:34
【问题描述】:

我有时(实际上,经常)发现自己使用单元素数组从一个方法返回多个值。像这样的:

public static int foo(int param1, int param2[], String param3[])
{
    // method body
    ....
    // set return values
    param2[0] = <some value>;
    param3[0] = <some value>;

    return <some value>;
}

这是一种不好的做法吗? (好像是因为我的一些朋友说他们2秒都不知道它在做什么!) 但我首先使用它的原因是因为它看起来最接近 C++ 中的 pass-by-reference。而且C++ 并没有劝阻这种做法,所以...

但如果这真的是一种错误的做事方式,知道如何以干净的方式重写它吗?

谢谢

【问题讨论】:

  • 我认为您的代码中有错字 - 您将 some value 分配给 param2[0] 两次。如果这真的是你在做什么,那么我不知道它是如何工作的......
  • 对象就是为此而生的:封装几个有意义的字段,连贯性
  • 仅表示“一些价值”。这是一个语法描述。当然并不表示它的两次出现应该具有相同的“值”
  • 有很多更好的方法来模拟在Java中通过引用传递。
  • @jahroy 你提到的更好的方法是什么?你能举几个例子吗?谢谢

标签: java


【解决方案1】:

创建一个包含要返回的数据的对象。

然后您可以返回该对象的一个​​实例。

class FooData {
    private int someInt;
    private int anotherInt;
    private String someString;

    public FooData(int a, int b, String c) {
        someInt = a;
        anotherInt = b;
        someString = c;
    }
}

public FooData foo() {
    // do stuff
    FooData fd = new FooData(blah, blahh, blahhh);
    return fd;
}

【讨论】:

  • 如果你在一个循环中调用foo(),你最终会创建多个FooDataobjects,是吗?我认为数组的使用只是 Java 在 C++ 中执行 pass-by-reference 的方式。
  • 是的,如果多次调用此方法,将创建多个 FooData 对象。我认为这没有问题。如果您愿意(我认为没有理由),您可以实例化一个 FooData 对象并将其传递给该方法以供重用。我认为没有理由这样做,但你可以。
  • 使用数组有很多很好的理由。您滥用它们作为您糟糕设计的解决方法。
  • 呃,内存分配开销?
  • @HotLicks - 我真的不知道 OP 的代码是如何工作的。我可能会调用三个单独的方法来获得 3 个单独的结果。如果我希望一个方法产生三个结果,我可能会创建一个对象来保存结果并从我的方法中返回它。我只建议了第二种方法(我不会使用),因为 OP 表达了对分配新对象的担忧。在大多数情况下,我不会有这种担心。我会 NOT 做的一件事是将数组作为参数传递 just 以便它可以修改 single int 值的值。
【解决方案2】:

虽然我同意这里的普遍观点,即将数组用于此目的是不好的做法,但我想补充几点。 您确定“通过引用传递”真的是您首先需要的吗? 许多人说你的代码风格不好,但现在让我告诉你为什么这是恕我直言。 “通过引用传递”主要是“副作用编程”的同义词,这是您一直想要避免的事情。它使代码更难调试和理解,并且在多线程环境中,这种态度的不良影响真的会给你带来沉重的打击。 要在 Java 中编写可伸缩和线程安全的代码,您应该尽可能使对象“只读”,即理想情况下,您同时创建一个对象并对其进行初始化,然后在整个应用程序中以这种不可修改的状态使用它.对状态的逻辑更改几乎总是可以被认为是新状态的“创建”,即创建一个初始化为然后需要的状态的新实例。许多现代脚本语言只允许您以这种方式工作,它使事情更容易理解。 与 C++ 相比,Java 在分配和释放短期对象方面效率更高,因此这里其他人的建议实际上并没有错:创建一个特殊类的实例来保存函数结果,只是为了达到目的的返回结果。即使您在循环中执行此操作,JVM 也将足够智能以有效地处理该问题。 Java 只会在需要时从操作系统中以非常大的块分配内存,并且会在内部处理对象创建和释放,而无需像 C/C++ 这样的语言所涉及的开销。 “通过引用传递”在 Java 中确实对您没有多大帮助。

编辑:我建议您在这个论坛或网络上搜索“副作用”、“函数式编程”或“不变性”等术语。这很可能会为您的问题打开一个新的视角。

【讨论】:

  • 感谢您的周到回答。
  • 如果 Java 对小对象分配如此高效,为什么 Doug Lea 在这里使用通过引用传递 java.util.concurrent.atomic.AtomicStampedReference#get(int[] stampHolder)?
  • 我认为这个例子是一个非常特殊的用例。顾名思义,AtomicStampedReference 表示对实例的引用,因此“get()”方法返回引用的实例以外的东西是不一致的。然后该类的作者选择通过副作用使与实例关联的“标记”可用,因此“get”方法仍然可以返回实例本身,而不是某个返回值对象,因为这是您所期望的一个名为“...Reference”的类的“get”方法。我怀疑他是否出于性能原因决定这样做。
  • 对象创建当然会产生 一些 开销,但它并不像在 C/C++ 中那么重要,因为“new”或“malloc”实际上是系统调用,系统调用总是对性能造成巨大影响,主要是因为内核/用户空间转换。 Java 很少调用“malloc”来获取大块内存,而所有 Java 堆操作都在内部处理。担心代码的可维护性和可读性确实比在大多数情况下不会让代码运行得更快时尝试优化性能更有意义。
  • 当然,理论上你可以在 C/C++ 中做的比在 Java 中做的更多。但是,Java 的内存管理和垃圾收集的实现已经投入了很多人年的工作,当有像 Java 这样的东西时,在商业应用程序中这样做简直太疯狂了。不过,Java 是一种通用语言,因此如果使用正确,API 中有一些概念可以可能更快地编写代码。您来自并发 API 的示例不是唯一的,只需想到“System.arrayCopy”即可。我只是认为这不是你在做商业应用时通常应该担心的事情。
【解决方案3】:

我认为使用作为方法参数的单元素数组“返回”值是一种不好的做法。

这里是关于这个主题的another SO question。简而言之,它对可读性非常不利。

有一个简单的解决方法:将您希望返回的所有值包装在为此目的而专门定义的类中,并返回该类的实例。

return new ValueHolder(someValue1, someValue2, someValue3);

【讨论】:

    【解决方案4】:

    这不是很惯用的java。通常有更好的软件设计方法。

    你真正用“单元素数组”做的是创建一个可变对象(因为 String 是不可变的,像 int 这样的原语也是不可变的)并通过引用传递它。修改这个可变对象称为该方法的“副作用”。一般来说,您应该尽量减少可变性 (Effective Java Item 15),并且您的方法应该没有副作用。这里有几种方法。

    1。将方法拆分为两个(或三个)都采用相同参数的方法:

    public static int foo1(int param1)
    {
        // method body
        ....
        return <some value>;
    }
    

    同样,你可能有

    public static int foo2(int param1) { ... }

    public static String foo3(int param1) { ... }.

    2。返回一个复合对象。

    public Container {
        private final int originalReturn;
        private final int param2;
        private final String param3;
    
        public Container(int originalReturn, int param2, String param3) {
            this.originalReturn = originalReturn;
            this.param2 = param2;
            this.param3 = param3;
        }
    
        // getters
    }
    
    public static Container foo(int param1, int param2[], String param3[])
    {
        // method body
        ....
        // set return values
        return new Container(<some value>, <some value>, <some value>);
    }
    

    【讨论】:

    • 所以你投了反对票,因为我提供了参考资料而不是仅仅陈述我的意见?
    • 如果我投了反对票,那是因为你正在使用食谱编程。
    【解决方案5】:

    如果值不相关,这确实是一种不好的做法。这通常是一个指示符,您可以将该函数拆分为两个,每个返回一个值。

    编辑:

    我假设您要返回在数组中的方法中计算的两个值。不是这样吗?

    例如

    public int[] getStatistics(int[] nums)
    {
        //code
    
        int[] returns = new int[2];
        returns[0] = mean;
        returns[1] = mode;
    
        return returns;
    }
    

    上面的函数可以拆分成getMean()getMode()

    【讨论】:

    • 我更喜欢 public int[] getStatistics(int[] nums, int[] out ){ ... if( out == null ) out = new ...; ...返回; } 因为客户端可以选择在多次调用 getStatistics 时将自己实例化一次数组
    • @Aubin 我不明白你的评论。
    • 您不理解的评论与手头的问题无关。
    【解决方案6】:

    通过引用传递变量允许函数“合法地”改变它们的值。请参阅this article 以消除在 Java 中什么时候可以实现这一点的困惑,什么时候不可以......

    【讨论】:

    • Java 从不通过引用传递“变量”。这确实是 OP 试图解决的问题。
    【解决方案7】:

    如果值是不同类型和不同实体的,这是不好的做法,例如名称和地址等。可以创建具有相同数据类型的数组,例如地址列表。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2011-02-27
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-11-13
      • 1970-01-01
      • 2017-05-26
      相关资源
      最近更新 更多