使用list参数作为返回值多次调用void方法比返回List的方法更好?

时间:2015-01-14 20:35:39

标签: java methods return-type

简而言之,我的问题是:如果一个方法被多次调用,在内存消耗方面是否更好,使它 void 并使用 List 作为参数返回它的价值?如果真的能节省内存,那么代码难以阅读是不是一种糟糕的做法?

让我举一个例子,让它更清楚。假设我有一个 Car 类,每辆车必须属于品牌。我有一个方法可以从品牌列表中返回所有汽车,此方法使用 foreach 和从一个品牌检索所有汽车的方法。如下代码:

private List<Car> getCarsByBrands(List<Brand> brands) {
    List<Car> result = new Arraylist<>;
    for(Brand brand : brands) {
        result.add(getCarsBySingleBrand(brand))
    }
    return result;
}

private List<Car> getCarsBySingleBrand(Brand brand) {
    List<Car> result = new Arraylist<>;
    result.add(retrieveCarsByBrand(brand)) // retrieveCarsByBrand omitted
    return result;
}

我的一位同事辩称, getCarsBySingleBrand 方法应该被重写为 void 并使用 List 作为参数,此列表将包含你可以在下面看到所有的汽车:

private List<Car> getCarsByBrands(List<Brand> brands) {
    List<Car> result = new Arraylist<>;
    for(Brand brand : brands) {
        getCarsBySingleBrand(result, brand))
    }
    return result;
}

private void getCarsBySingleBrand(List<Car> cars, Brand brand) {
    cars.add(retrieveCarsByBrand(brand)) // retrieveCarsByBrand omitted
}

他认为这种方式消耗的内存较少,因为每次调用方法 getCarsBySingleBrand 时,他都不会创建List。我认为这是一种不必要的优化,并且是一种不好的做法,因为代码更难理解。

5 个答案:

答案 0 :(得分:8)

第二个选项 MIGHT 更优化。

问题是第一种方式,不仅要为每个品牌制作另一个List对象,然后扔掉,但如果品牌有很多车,那么Java将调整列表的大小(多次初始化为默认大小16)。调整大小操作需要复制数组。如果你多次调整大小,这可能会变得昂贵。

第二个选项只有一个列表,因为调整大小通常会使容量增加一倍,所以它必须调整的次数比第一个选项少。

然而,这正在进行微观优化。除非你注意到性能问题并做了分析以确定这是一个瓶颈,否则我不会担心这种事情。

如果您对方法名称感到担心,我认为有一个单词&#34; get&#34;这个名字会让你失望,因为它通常意味着要回归一些东西。将其命名为addBrandOfCarsTo()可能会使其更像是一个句子:

for(Brand brand : brands) {
    addBrandOfCarsTo(result, brand));
}

答案 1 :(得分:4)

这两个选项都很好,主要取决于您的偏好:

  • 有些人更喜欢清晰(每个人都有自己的风格)。
  • 其他人更喜欢表演,无论多么小。

如果是您的情况,您也可以直接致电retrieveCarsByBrand并使用其结果:

private List<Car> getCarsByBrands(List<Brand> brands) {
    List<Car> result = new Arraylist<>;
    for(Brand brand : brands) {
        result.add(retrieveCarsByBrand(brand))
    }
    return result;
}

或简化getCarsBySingleBrand并使用其结果:

private List<Car> getCarsByBrands(List<Brand> brands) {
    List<Car> result = new Arraylist<>;
    for(Brand brand : brands) {
        result.add(retrieveCarsByBrand(brand));
    }
    return result;
}

private List<Car> getCarsBySingleBrand(Brand brand) {
    return retrieveCarsByBrand(brand);
}

或在更改名称getCarsBySingleBrand

的清晰度和效果之间做出妥协
private List<Car> getCarsByBrands(List<Brand> brands) {
    List<Car> result = new Arraylist<>;
    for(Brand brand : brands) {
        getCarsBySingleBrand(result, brand))
    }
    return result;
}

private void addBrandCars(List<Car> cars, Brand brand) {
    cars.add(retrieveCarsByBrand(brand)) // retrieveCarsByBrand omitted


}

答案 2 :(得分:1)

我想你可以同时使用这两个世界。 第二个的唯一优势是它消耗更少的内存。如前所述,将函数方法改为void函数将增加清晰度,并将有助于单元测试(您可以模拟返回的值)。 我将如何做到这一点:

private List<Car> getCarsByBrands(List<Brand> brands) {
    List<Car> result = new Arraylist<>;
    for(Brand brand : brands) {
        getCarsBySingleBrand(result, brand))
    }
    return result;
}

private List<Car> addBrandCars(List<Car> cars, Brand brand) {
    cars.add(retrieveCarsByBrand(brand)) // retrieveCarsByBrand omitted
    return cars;
}

答案 3 :(得分:0)

他可能是对的。

我们来解释为什么

因此,在每个品牌的第一个示例中,您要在 getCarsBySingleBrand 方法中创建新列表,而不必在 {{中的结果列表中添加新创建的列表中的汽车1}} 的。

getCarsByBrands

在此行中,您只需从 result.add(getCarsBySingleBrand(brand)) 获取列表,然后将所有这些元素添加到结果中。

但在第二种情况下,您将直接的汽车添加到 getCarsBySingleBrand 列表,这样您就可以减少操作次数。

同样说第一个示例消耗更多内存也是正确的。但我认为这里更多的问题只是操作,而不是记忆。

<强>结论

在我看来,第二种方法并不是一种糟糕的风格。因此,如果需要,您可以进行此类优化。

答案 4 :(得分:0)

正如@dkatzel所说,第二种选择可能更好。但这只是因为在第一个选项中,在方法getCarsBySingleBrand(Brand brand)中,您不必要地将retrieveCarsByBrand(brand)结果复制到新的ArrayList。这意味着在方法getCarsBySingleBrand(Brand brand)返回之前存在3个不同的列表:

  • 保存最终结果的那个
  • retrieveCarsByBrand(brand)
  • 返回的一个品牌汽车的子列表
  • 您用于复制ArrayList
  • 返回的列表的新retrieveCarsByBrand(brand)

如果按照@Narkha的建议,在第一个选项中,您通过不将getCarsBySingleBrand(brand)返回的汽车列表复制到新的retrieveCarsByBrand(brand),或者通过以下方法简化方法ArrayList只是内联retrieveCarsByBrand(brand)方法,然后两个选项在内存消耗方面是相同的。这是因为在这两种方法中,针对单个品牌检索的汽车列表将被复制到每个品牌保存最终结果的列表。这意味着同时只存在2个列表:

  • 保存最终结果的那个
  • retrieveCarsByBrand(brand)
  • 返回的一个品牌汽车的子列表

正如其他人所说,几乎不需要这种优化。但是,如果你使用Guava(你应该!),你应该知道有更好的方法来做到这一点。

private Iterable<Car> getCarsByBrands(List<Brand> brands) {
    Iterable<Iterable<Car>> carsGroupedByBrand = new Arraylist<>();
    for(Brand brand : brands) {
        carsGroupedByBrand.add(retrieveCarsByBrand(brand));
    }
    return Iterables.concat(carsGroupedByBrand);
}

此解决方案存储汽车的子列表列表,其中每个子列表包含每个品牌的汽车,最后装饰汽车的子列表列表为单个Iterable汽车。如果你确实需要返回List而不是Iterable,那么你可以修改最后一行,如下所示:

    return Lists.newArrayList(Iterables.concat(carsGroupedByBrand));

这里的关键词是 decorates ,这意味着不会执行任何子列表的实际副本。相反,会创建一个特殊用途的迭代器,它遍历第一个子列表的每个元素,当它到达终点时,它会自动透明地“跳转”到第二个子列表的第一个元素,直到它到达终点,所以on,直到它到达最后一个子列表的最后一个元素。有关详细信息,请参阅Guava的Iterables.concat(...) method documentation