DEV Community

Cover image for Tips para escribir mejor código en Java
Jordi Ayala
Jordi Ayala

Posted on • Originally published at asjordi.dev

Tips para escribir mejor código en Java

Cada uno de los siguientes tips/trucos por sí solos pueden parecer insignificantes, pero en conjunto permiten tener un código más fácil de leer, más mantenible y menos propenso a errores, a la vez que hacen uso de características modernas de Java. En cada uno de los ejemplos se muestra una versión "mala" y una versión "buena" del código, donde la segunda es la recomendada.

1. try-with-resources

En lugar de tener que preocuparse por cerrar recursos manualmente, ya sea comprobando si han sido abiertos o utilizando un bloque finally, podemos utilizar un bloque try-with-resources que se encarga de cerrar automáticamente los recursos una vez que ya no se están utilizando. Esto es especialmente útil para manejar archivos, conexiones de red, bases de datos, etc.

BufferedReader br = null;

try {
    br = new BufferedReader(new FileReader("data.txt"));
    String line;

    while ((line = br.readLine()) != null) System.out.println(line);
} catch (IOException e) {
    e.printStackTrace();
} finally {
    if (br != null) try {
        br.close();
    } catch (IOException e) {
        e.printStackTrace();
    }
}
Enter fullscreen mode Exit fullscreen mode
try (BufferedReader br = new BufferedReader(new FileReader("data.txt"))) {
    String line;
    while ((line = br.readLine()) != null) System.out.println(line);
} catch (IOException e) {
    e.printStackTrace();
}
Enter fullscreen mode Exit fullscreen mode

2. StringBuilder

Dado que un String es inmutable, cuando se concatena utilizando el operador + se crean múltiples objetos String en memoria, lo que puede ser ineficiente. En su lugar, podemos utilizar un StringBuilder, que es mutable y permite concatenar cadenas de manera más eficiente. Incluso el propio IDE puede llegar a realizar esta recomendación de manera automática.

String res = "";

for (int i = 0; i < 10000; i++) {
    res += i;
}

System.out.println(res);
Enter fullscreen mode Exit fullscreen mode
StringBuilder res = new StringBuilder();

for (int i = 0; i < 10000; i++) {
    res.append(i);
}

System.out.println(res);
Enter fullscreen mode Exit fullscreen mode

3. Early Return

En lugar de anidar múltiples bloques if y else, podemos utilizar un retorno anticipado para simplificar el flujo del código. Esto mejora la legibilidad y mantenibilidad del código.

private void processOrder(Order o) {
    if (o != null) {
        if (o.isValid()) {
            if (o.hasValidItems()) {
                // process order
            } else {
                // handle invalid items
            }
        } else {
            // handle invalid order
        }
    } else {
        // handle null order
    }
}
Enter fullscreen mode Exit fullscreen mode
private void processOrder(Order o) {
    if (o == null) {
        // handle null order
        return;
    }

    if (!o.isValid()) {
        // handle invalid order
        return;
    }

    if (!o.hasValidItems()) {
        // handle order with invalid items
        return;
    }

    // Process order
}
Enter fullscreen mode Exit fullscreen mode

4. Enums

Los enums proveen seguridad de tipos, permiten crear un código más autodocumentado y evitan el uso de constantes mágicas, incluso permiten agregar métodos y atributos a los mismos. En lugar de utilizar constantes enteras o cadenas de texto, podemos utilizar enums para representar un conjunto fijo de valores.

class Status {
    public static final int ACTIVE = 1;
    public static final int INACTIVE = 0;
}

public void process(int status) {
    if (status == Status.ACTIVE) {
        // do something
    }
}
Enter fullscreen mode Exit fullscreen mode
public enum Status {
    ACTIVE,
    INACTIVE
}

public void process(Status status) {
    if (status == Status.ACTIVE) {
        // do something
    }
}
Enter fullscreen mode Exit fullscreen mode

5. Optional

El uso de Optional permite evitar las posibles comprobaciones respecto de null, las excepciones NullPointerException y el uso de bloques if-else, lo que mejora la legibilidad y mantenibilidad del código. En lugar de devolver null para indicar la ausencia de un valor, podemos utilizar Optional para representar un valor que puede o no estar presente.

public static String getNormalizedName(Person p) {
    if (p != null && p.getName() != null) {
        return p.getName().toUpperCase();
    }

    return null;
}
Enter fullscreen mode Exit fullscreen mode
public static String getNormalizedName(Person p) {
    return Optional.ofNullable(p)
            .map(Person::getName)
            .map(String::toUpperCase)
            .orElse("UNKNOWN");
}
Enter fullscreen mode Exit fullscreen mode

6. Lambdas y Referencias de Métodos

Utilizar lambdas y referencias de métodos permite escribir código más conciso y legible, especialmente al trabajar con colecciones y la API de Streams. De esta manera se pueden eliminar clases anónimas, bucles innecesarios y código verboso.

List<String> names = Arrays.asList("John", "Mary", "Peter", "Paul", "Anna");

Collections.sort(names, new Comparator<String>() {
    @Override
    public int compare(String o1, String o2) {
        return o1.compareTo(o2);
    }
});
Enter fullscreen mode Exit fullscreen mode
List<String> names = Arrays.asList("John", "Mary", "Peter", "Paul", "Anna");

names.sort(String::compareTo);
Enter fullscreen mode Exit fullscreen mode

7. Static Factory Methods

En ocasiones puede resultar factible usar métodos de fábrica estáticos en lugar de constructores, ya que permiten crear instancias de una clase de manera más legible y flexible. Esto es especialmente útil cuando se trabaja con clases inmutables o cuando se desea proporcionar diferentes formas de crear instancias.

public class Car {

    public Car (String model, int year, String color) {
        // logic
    }

}

public static void main(String[] args) {
    var car = new Car("Fiat", 2020, "red");
}
Enter fullscreen mode Exit fullscreen mode
public class Car {

    private String model;
    private int year;
    private String color;

    private Car (String model, int year, String color) {
        this.model = model;
        this.year = year;
        this.color = color;
    }

    public static Car of(String model, int year, String color) {
        // Extra validation or logic
        return new Car(model, year, color);
    }

}

public static void main(String[] args) {
    var car = Car.of("Toyota", 2020, "Red");
}
Enter fullscreen mode Exit fullscreen mode

8. Logging

En lugar de utilizar System.out.println para imprimir mensajes de depuración o información, es recomendable utilizar un framework de logging como Log4j, SLF4J o java.util.logging. Esto permite un mejor control sobre los niveles de logging, la configuración y la salida de los mensajes. Incluso algunos frameworks, como SLF4J, permiten un logging parametrizable, o el uso de Supplier para evitar la creación de cadenas innecesarias.

System.out.println("Attempting to read last fetch date");
Enter fullscreen mode Exit fullscreen mode
LOGGER.log(Level.INFO, () -> "Attempting to read last fetch date");
Enter fullscreen mode Exit fullscreen mode

9. Objects.requireNonNull

Objects.requireNonNull es un método estático que permite verificar si un objeto es null y lanzar una NullPointerException con un mensaje personalizado. Esto es útil para validar argumentos en métodos y constructores, evitando la necesidad de escribir bloques if adicionales.

public class Person {

    private String name;

    public void setName(String name) {
        if (name == null) {
            throw new IllegalArgumentException("Name cannot be null");
        }

        this.name = name;
    }

}
Enter fullscreen mode Exit fullscreen mode
import java.util.*;

public class Person {

    private String name;

    public void setName(String name) {
        this.name = Objects.requireNonNull(name, "Name cannot be null");
    }

}
Enter fullscreen mode Exit fullscreen mode

10. computeIfAbsent en Map

El método computeIfAbsent de la interfaz Map permite calcular un valor y agregarlo al mapa solo si la clave no está presente. Esto es útil para evitar comprobaciones adicionales e inicializar valores de manera más concisa.

String str = "hello";
Map<String, List<String>> map = new HashMap<>();

if (!map.containsKey("key")) {
    map.put("key", new ArrayList<>());
}

map.get("key").add(str);
Enter fullscreen mode Exit fullscreen mode
String str = "hello";
Map<String, List<String>> map = new HashMap<>();
map.computeIfAbsent("key", k -> new ArrayList<>()).add(str);
Enter fullscreen mode Exit fullscreen mode

11. merge en Map

El método merge de la interfaz Map permite combinar valores de manera más concisa y eficiente. Esto es útil para evitar comprobaciones adicionales y simplificar la lógica de combinación.

String str = "hello";
Map<Character, Integer> map = new HashMap<>();

for (char c : str.toCharArray()) {
    if (!map.containsKey(c)) {
        map.put(c, 1);
    } else {
        map.put(c, map.get(c) + 1);
    }
}
Enter fullscreen mode Exit fullscreen mode
String str = "hello";
Map<Character, Integer> map = new HashMap<>();

for (char c : str.toCharArray()) {
    map.merge(c, 1, Integer::sum);
}
Enter fullscreen mode Exit fullscreen mode

12. Records

Los records son una característica relativamente nueva que permite crear clases inmutables de manera más concisa. Esto es útil para representar datos sin necesidad de escribir código adicional para constructores, métodos equals, hashCode y toString, es decir, evitar el típico boilerplate de Java. Pueden resultar muy útiles para representar objetos de valor o DTOs (Data Transfer Objects), y también pueden ser utilizados con Pattern Matching.

public class Person {
    private final String name;
    private final int age;

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public int getAge() {
        return age;
    }
}
Enter fullscreen mode Exit fullscreen mode
public record Person(String name, int age) {}
Enter fullscreen mode Exit fullscreen mode

Conclusión

Estos son solo algunos de los muchos tips y trucos que pueden ayudarte a escribir código más limpio y eficiente en Java. A continuación puedes encontrar enlaces a diferentes post donde se ven a profundidad algunos de estos temas.

Top comments (1)