【问题标题】:Is it more efficient to pass a List or individual List Items as method arguments in Java?在 Java 中将列表或单个列表项作为方法参数传递是否更有效?
【发布时间】:2017-02-28 10:31:22
【问题描述】:

假设我有一个包含 100 个字符串的列表。以下两种方法在性能上是否存在差异(或任何其他差异)

1.

List<String> myList = Arrays.asList("s1", "s2", "s3"....."s100");        
foo(myList);

private void foo(List<String> myList) {
    // logic to choose 2 strings from the list
    //do something with -- str1 and str2
}

2.

List<String> myList = Arrays.asList("s1", "s2", "s3"....."s100");    
// logic to choose 2 strings from the list
foo(str1, str2);

private void foo(String myStr1, String myStr2) {
    //do something with -- myStr1 and myStr2
}

由于 Java 传递列表或单个字符串的引用(作为值),我假设这些参数将只保存(副本)引用/地址。所以我倾向于通过整个列表。

上面将是一个实用方法,我想将“从列表中选择 2 个字符串的逻辑”放在 foo 方法中,这样调用者就不必在调用之前重复该逻辑;但我担心发送一个大列表作为参数。

感谢您的帮助。

** 编辑:** 是的,我正在寻找性能问题。

List&lt;String&gt; 可能不是正确的例子。将其视为Map&lt;String,Long&gt;(城市,温度)。逻辑将始终选择芝加哥和洛杉矶,foo 将根据温度运行。请注意,此逻辑将始终相同,调用者只会在方法 #2 中重复相同的代码。

【问题讨论】:

  • 真的应该是foos 负责选择两个字符串还是只对它们进行操作?
  • 如果不了解该方法的用例,我认为无法回答这个问题。除非您的问题是关于性能的,否则在这种情况下,答案是选择无关紧要。
  • @ChiefTwoPencils foo 将特别需要这两个字符串值。但是,执行逻辑以选择这些字符串并不一定是foos 的责任。但请注意,逻辑将是相同的,并且如果呼叫者被要求这样做,他们会重复。因此我想把它放在foo.
  • @VGR 是的,我的问题主要是关于性能的。所以发送一个巨大的列表作为参数不是问题吗?这是因为只有列表的引用会作为值传递,而不是整个列表?
  • 如果您的问题是关于性能的,那么这应该在主题行中。 “更好”是一个非常模棱两可的词。您在问题中也有“和任何其他差异”。

标签: java list methods parameter-passing pass-by-value


【解决方案1】:

我建议您忽略性能上的潜在差异(在这种情况下可以忽略不计),并完全专注于代码的清晰度。换句话说,问题不在于哪个更有效,而在于哪个更好地反映了您调用的方法的意图。如果foo 是一个自然需要两个参数的方法(例如storeFullName),那么它应该传递两个参数。如果它自然希望从列表中选择两个值(例如printTwoLongestStrings),那么它应该采用一个集合。

您还可以考虑给每个方法一个single responsibility 的原则。在这种情况下,使用单独的方法来选择两个值并对其进行处理可能是有意义的:

Pair<String, String> longestPair = getTwoLongestStrings(list);
printStrings(longestPair);

** 编辑 **

您的问题表明您特别关注性能,并且您提供了更多用例的详细信息。具体来说,您是在询问将列表或两个值传递给方法的性能是否存在差异。是,有一点不同。我用两个字符串参数和一个列表运行了一个调用空函数 100 亿次的试验。列表调用耗时 24 秒,单独的参数调用耗时 13 秒。其他物理硬件可能会显示不同的结果。也有可能是 JVM 优化没有使这成为一个公平的测试。

public class PerfTest {

    public static void main(String[] args) {
        List<String> list = Arrays.asList("a", "b");
        long time1 = System.currentTimeMillis();
        for (long i = 0; i < 1E10; i++) {
            func1("a", "b");
        }
        long time2 = System.currentTimeMillis();
        for (long i = 0; i < 1E10; i++) {
            func2(list);
        }
        System.out.println((time2 - time1) + "/" + (System.currentTimeMillis() - time2));
    }

    private static void func1(String s1, String s2) { }
    private static void func2(List<String> sl) { }
}

但是我上面的答案仍然成立:如果您调用一个函数 100 亿次,那么它可能值得优化,但最好的做法是从关注清晰度开始,然后使用分析来集中代码调整工作,如果那样的话是必需的。

【讨论】:

  • +1 代码清晰。在绝大多数情况下,清晰度是王道,除非您认为这是代码中的瓶颈,否则您可以在不牺牲可读性的情况下优化其他内容。
  • 感谢@sprinter。我想知道为什么有利于个别论点的差异?由于 Java 会将列表的“按值引用”而不是整个列表传递给方法;所以看起来像是传递 1 个引用与传递 2 个引用。并且通过 1 个参考应该花费更少的时间?
  • 性能差异,如果有的话,小到根本不用关心。不同之处在于将两个值存储在方法内部和外部;传递它们甚至不值得关心。
  • @LouisWasserman 我同意应该忽略差异。但是,我仍然对我编写的测试中的时间差的任何解释感兴趣。我不认为您的评论解释了为什么 List 引用要慢得多。
  • @Gadam 是的,我也很感兴趣,但并不是 JVM 内部工作的真正专家,因此无法真正回答这个问题。正如上面的 cmets 所指出的,我使用的方法非常狡猾(即只是比较系统时钟),所以它可能不是一个公平的测试。我将发布一个单独的问题,看看是否有人知道足以回答它。
【解决方案2】:

我认为您是在询问 List 参数是否通过引用传递。它是。在编译的机器代码中,对于列表参数,只有一个指向列表对象实例的指针将传递给函数。没有理由担心将大量列表作为论据。

【讨论】:

    【解决方案3】:

    我建议只使用List 作为参数,如果该方法对列表本身感兴趣,而不仅仅是它的项目,例如它计划对其进行修改。

    对于其他情况,如果你有固定数量的参数,你应该让每个参数都是它自己的参数。但是您的案例表明您有可变数量的参数。为此,您还可以使用可变参数:

    private void foo(String... strings) {
        // `strings` is an array now
    }
    

    这样做的好处是调用者可以选择使用单个对象或数组来调用您的函数:

    foo("Hello", "World");
    

    或者这个

    String[] strings = new String[]{"Hello", "World"};
    foo(strings);
    

    甚至这个:

    List<String> strings = ...;
    foo(strings.toArray(new String[0]));
    

    如果您担心将列表转换为数组,请坚持传递列表本身。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2021-09-12
      • 1970-01-01
      • 1970-01-01
      • 2013-01-24
      • 1970-01-01
      • 1970-01-01
      • 2013-11-06
      相关资源
      最近更新 更多