java - texto - ¿Múltiples llamadas a un método nulo utilizando el parámetro de lista como valor de retorno es mejor que un método que devuelve una lista?




texto en jlabel (4)

En resumen, mi pregunta es: si un método se llama varias veces, ¿es mejor en términos de consumo de memoria para hacerlo nulo y usar un parámetro List como para devolver sus valores? En caso de que realmente ahorre memoria, ¿no es una mala práctica ya que el código es más difícil de leer?

Déjame dar un ejemplo para hacerlo más claro. Supongamos que tengo un automóvil de clase y que cada automóvil debe pertenecer a una marca . Tengo un método que devuelve todos los automóviles de una lista de marcas y este método utiliza un método foreach y un método que recupera todos los automóviles de una marca. Como el siguiente código:

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;
}

Un colega mío defiende que el método getCarsBySingleBrand debe reescribirse para que sea nulo y usar un parámetro List as y esta lista contendría todos los coches como puede ver a continuación:

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
}

Él argumenta que de esta manera consume menos memoria, porque no crea una lista cada vez que se llama al método getCarsBySingleBrand . Creo que este es un tipo de optimización innecesaria y es una mala práctica porque el código es más difícil de entender.


Como dijo @dkatzel, la segunda opción podría ser mejor. Pero eso es solo porque en la primera opción, en el método getCarsBySingleBrand(Brand brand) , está copiando innecesariamente el resultado de retrieveCarsByBrand(brand) a una nueva lista de ArrayList . Esto significa que existen 3 listas diferentes antes de que el método getCarsBySingleBrand(Brand brand) regrese:

  • El que tiene el resultado final
  • La sublista con los autos de una sola marca, devuelta por retrieveCarsByBrand(brand)
  • El nuevo ArrayList que está utilizando para copiar la lista devuelta por retrieveCarsByBrand(brand)

Si, como lo sugiere @Narkha, en la primera opción, simplificó el método getCarsBySingleBrand(brand) al no copiar la lista de autos devueltos por retrieveCarsByBrand(brand) a una nueva ArrayList , o simplemente al alinear el método retrieveCarsByBrand(brand) , luego ambas opciones serían las mismas , en términos de consumo de memoria. Esto se debe a que en ambos enfoques, la lista de autos recuperados para una sola marca se copiaría en la lista que contiene el resultado final una vez por marca. Esto significa que solo 2 listas existirían al mismo tiempo:

  • El que tiene el resultado final
  • La sublista con los autos de una sola marca, devuelta por retrieveCarsByBrand(brand)

Como lo expresaron otros, este tipo de optimización casi nunca se necesita. Sin embargo, debes saber que hay una manera más óptima de hacerlo, si usas Guava (¡deberías!).

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);
}

Esta solución almacena una lista de sublistas de automóviles, donde cada sublista contiene los automóviles de cada marca, y al final decora la lista de sublistas de automóviles en una sola Iterable de automóviles. Si realmente necesita devolver una List lugar de una Iterable , entonces puede modificar la última línea de la siguiente manera:

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

La palabra clave aquí es decorar , lo que significa que no se realiza ninguna copia real de ninguna sublista. En su lugar, se crea un iterador de propósito especial, que atraviesa cada elemento de la primera sublista, y cuando llega a su final, 'Salta' de forma automática y transparente al primer elemento de la segunda sublista, hasta que llega a su fin, y así encendido, hasta que alcanza el último elemento de la última sublista. Consulte la documentación del método Iterables.concat (...) de Guava para obtener más detalles.


Él puede ser correcto.

Vamos a explicar POR QUÉ .

así que en el primer ejemplo para cada marca está creando una nueva lista en el método getCarsBySingleBrand y debe agregar autos de la lista recién creada en la lista de resultados en getCarsByBrands .

result.add(getCarsBySingleBrand(brand))

en esta línea, solo obtienes la lista de getCarsBySingleBrand y agregas todos esos elementos al resultado.

Pero en el segundo caso, agrega autos directamente a la lista getCarsByBrands , por lo que realiza menos operaciones.

También es correcto decir que el primer ejemplo consume un poco más de memoria. Pero creo que aquí el problema es simplemente operaciones, no memoria.

Conclusión

En mi opinión, el segundo método no es realmente un mal estilo. Entonces puede hacer tales optimizaciones si es necesario.


La segunda opción PODRÍA ser más óptima.

El problema es la primera forma en que no solo debe crearse otro objeto List para cada marca, que luego se descarta, pero si hay muchos automóviles para una marca, entonces Java redimensionará la lista (que se inicializa al tamaño predeterminado) de 16) muchas veces. La operación de cambio de tamaño requiere copiar la matriz. Esto podría ser costoso si cambia el tamaño muchas veces.

La segunda opción solo tiene una lista y dado que el cambio de tamaño generalmente duplica la capacidad, debería tener que cambiar el tamaño menos veces que la primera opción.

Sin embargo, esto se está convirtiendo en micro-optimizaciones. No me preocuparía este tipo de cosas a menos que esté notando un problema de rendimiento y haya hecho análisis para determinar que esto es un cuello de botella.

Si le preocupa el nombre del método, creo que tener la palabra "obtener" en el nombre lo está tirando porque generalmente implica devolver algo. Nombrar algo así como addBrandOfCarsTo() podría hacer que se lea más como una oración:

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

Supongo que puedes usar ambos mundos. La única ventaja en el segundo es que consume menos memoria. Hacer un método funcional en lugar de uno vacío aumentará la claridad, como se dijo anteriormente, y ayudará en la prueba unitaria (puede simular el valor devuelto). Así es como lo haría:

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;
}






return-type