Upgrade to PRO for Only $50/Year—Limited-Time Offer! 🔥

Java - Best Practices & Mistakes to Avoid

Java - Best Practices & Mistakes to Avoid

Lean how to avoid the Java common mistakes and how to improve your code in terms of readability and performance by applying the best practices

https://youtu.be/_xAqVYrp6GM

Matteo Bertozzi

July 16, 2023
Tweet

More Decks by Matteo Bertozzi

Other Decks in Programming

Transcript

  1. Avoid Resources Leaks The try-with-resources Statement Closeable xyz = open()

    ...do something... xyz.close() try (Closeable xyz = open()) { ...do something... } Closeable xyz = null; try { xyz = open() ...do something... } finally { if (xyz != null) xyz.close() } use fi nally to make sure close() is always called try-with-resources use OutputStream out; out = new FileOutputStream(“/file“); out.write(65); out.close(); ... stmt = dbConn.preparedStatement(sql); ResultSet rs = stmt.executeQuery(); while (rs.next()) { String v = rs.getString(1); ... } rs.close(); stmt.close() What if it throws exception? What if it throws exception? try (OutputStream out = new FileOutputStream("/file")) { out.write(65); } try (Statement stmt = dbConn.preparedStatement(sql)) { try (ResultSet rs = stmt.executeQuery()) { while (rs.next()) { String v = rs.getString(1); ... } } } What if it throws exception?
  2. Avoid Silent Catches Ignoring Your Problems Won't Make Them Go

    Away try { // ...do something... } catch (Exception e) { // THIS IS WRONG IN 99.999% of the cases Logger.error(e, "meh, something went wrong... ignore it"); } // ...do something more // AGAIN THIS IS WRONG IN 99.999% of the cases // you just ignored what happened above try { // ...do something... } catch (Exception e) { // THIS IS WRONG IN 99.999% of the cases e.printStackTrace(); } try { // ...do something... } catch (Exception e) { throw new MyBetterException("this happened because…”, e); } try { db.execute("INSERT INTO table...") } catch (Exception e) { // THIS IS WRONG! you are hoping // that you get an exception for duplicate key // but you may get something else (e.g. network failure) db.execute("UPDATE table...") } If you don't know what to do with the exception, don't catch it! someone else up in the stack may be able to handle it. try { // ...do something... } catch (Exception e) { http.respond(HTTP_INTERNAL_SERVER_ERROR); return; } Enrich the exception Fail & Let the user know something went wrong
  3. Object Scope Reduction Declare Variables near where they are used

    List<String> myBadFunction() { int a = 0; ArrayList<String> data = new ArrayList<>(); HashMap<String, String> map = new HashMap<>(); if (...) { // map can be declared inside this block, nobody else use it // here we may know how many elements will be in the map and preallocate for (...) { map.put(key, value); } data.addAll(map.values()); } if (...) { // a is used only inside this block // declaring a variable near where it is used improves code readability for (...) { a += ...; } data.add(a > 10 ? "aaa" : "bbb"); } return data; } List<String> myImprovedFunction() { ArrayList<String> data = new ArrayList<>(); if (...) { HashMap<String, String> map = new HashMap<>(...); for (...) { map.put(key, value); } data.addAll(map.values()); } if (...) { int a = 0; for (...) { a += ...; } data.add(a > 10 ? "aaa" : "bbb"); } return data; }
  4. The “Final” Keyword To Improve Code Comprehension class Foo {

    private static final float THRESHOLD = 0.5f; private final String key; private int count; public Foo(final String key) { this.key = key; this.count = 0; } public List<String> bar() { final ArrayList<String> data = new ArrayList<>(); int index = 0; for (int i = 0; i < 10; i++) { if (Math.random() > THRESHOLD) { data.add("test-" + index); index++; } else { data.add(this.key); } } for (int i = 0; i < 5; i++) { data.add("test-" + index); index += 2; } this.count++; return data; } } The “key” parameter is not reassigned inside the method “count” will be changed by some methods of the class The “key” member variable Is assigned in the constructor And not changed by anyone else “THRESHOLD” is a constant The “data” variable is not reassigned inside the method. NOTE that the array Is NOT Immutable The “index” variable is reassigned inside the method. Improve Readability By knowing from the declaration If the variable will be reassiged
  5. Util Classes Class with Only static Methods public final class

    MyUtil { private MyUtil() { // no-op } public static void doStuff() { // ... } } a final class can NOT be inherited/extended Block instance creation: new MyUtil() The constructor MyUtil() is not visible MyUtil.doStuff();
  6. Avoid Unnecessary Allocations Delay allocations & Preallocate when possible private

    List<ModelB> convert(final List<ModelA> input) { if (input == null || input.isEmpty()) { return Collections.emptyList(); } final ArrayList<ModelB> output = new ArrayList<>(input.size()); for (final ModelA model: input) { output.add(convert(model)); } return output; } private List<ModelB> convert(final List<ModelA> input) { final ArrayList<ModelB> output = new ArrayList<>(); if (input != null) { for (final ModelA model: input) { output.add(convert(model)); } } return output; } Preallocate the list With the expected size Use a Preallocated “Empty List” Reduce Allocation, and Preallocate What is going on Under the hood?
  7. Avoid Unnecessary Allocations Delay allocations & Preallocate when possible ArrayList<String>

    data = new ArrayList<>(); size = 0 capacity = 2 private List<ModelB> convert(final List<ModelA> input) { if (input == null || input.isEmpty()) { return Collections.emptyList(); } final ArrayList<ModelB> output = new ArrayList<>(input.size()); for (final ModelA model: input) { output.add(convert(model)); } return output; } private List<ModelB> convert(final List<ModelA> input) { final ArrayList<ModelB> output = new ArrayList<>(); if (input != null) { for (final ModelA model: input) { output.add(convert(model)); } } return output; } Preallocate the list With the expected size Use a Preallocated “Empty List” Reduce Allocation, and Preallocate
  8. Avoid Unnecessary Allocations Delay allocations & Preallocate when possible ArrayList<String>

    data = new ArrayList<>(); data.add(“A"); A size = 1 capacity = 2 private List<ModelB> convert(final List<ModelA> input) { if (input == null || input.isEmpty()) { return Collections.emptyList(); } final ArrayList<ModelB> output = new ArrayList<>(input.size()); for (final ModelA model: input) { output.add(convert(model)); } return output; } private List<ModelB> convert(final List<ModelA> input) { final ArrayList<ModelB> output = new ArrayList<>(); if (input != null) { for (final ModelA model: input) { output.add(convert(model)); } } return output; } Preallocate the list With the expected size Use a Preallocated “Empty List” Reduce Allocation, and Preallocate
  9. Avoid Unnecessary Allocations Delay allocations & Preallocate when possible ArrayList<String>

    data = new ArrayList<>(); data.add(“A"); data.add(“B”); A B size = 2 capacity = 2 private List<ModelB> convert(final List<ModelA> input) { if (input == null || input.isEmpty()) { return Collections.emptyList(); } final ArrayList<ModelB> output = new ArrayList<>(input.size()); for (final ModelA model: input) { output.add(convert(model)); } return output; } private List<ModelB> convert(final List<ModelA> input) { final ArrayList<ModelB> output = new ArrayList<>(); if (input != null) { for (final ModelA model: input) { output.add(convert(model)); } } return output; } Preallocate the list With the expected size Use a Preallocated “Empty List” Reduce Allocation, and Preallocate
  10. Avoid Unnecessary Allocations Delay allocations & Preallocate when possible ArrayList<String>

    data = new ArrayList<>(); data.add(“A"); data.add(“B”); data.add(“C”); A B size = 2 capacity = 2 There is no space for the new item “C” private List<ModelB> convert(final List<ModelA> input) { if (input == null || input.isEmpty()) { return Collections.emptyList(); } final ArrayList<ModelB> output = new ArrayList<>(input.size()); for (final ModelA model: input) { output.add(convert(model)); } return output; } private List<ModelB> convert(final List<ModelA> input) { final ArrayList<ModelB> output = new ArrayList<>(); if (input != null) { for (final ModelA model: input) { output.add(convert(model)); } } return output; } Preallocate the list With the expected size Use a Preallocated “Empty List” Reduce Allocation, and Preallocate
  11. Avoid Unnecessary Allocations Delay allocations & Preallocate when possible ArrayList<String>

    data = new ArrayList<>(); data.add(“A"); data.add(“B”); data.add(“C”); A B size = 2 capacity = 4 A new Array is created Double the size private List<ModelB> convert(final List<ModelA> input) { if (input == null || input.isEmpty()) { return Collections.emptyList(); } final ArrayList<ModelB> output = new ArrayList<>(input.size()); for (final ModelA model: input) { output.add(convert(model)); } return output; } private List<ModelB> convert(final List<ModelA> input) { final ArrayList<ModelB> output = new ArrayList<>(); if (input != null) { for (final ModelA model: input) { output.add(convert(model)); } } return output; } Preallocate the list With the expected size Use a Preallocated “Empty List” Reduce Allocation, and Preallocate
  12. Avoid Unnecessary Allocations Delay allocations & Preallocate when possible ArrayList<String>

    data = new ArrayList<>(); data.add(“A"); data.add(“B”); data.add(“C”); A B size = 2 capacity = 4 A B The Current Data Is Copied Over private List<ModelB> convert(final List<ModelA> input) { if (input == null || input.isEmpty()) { return Collections.emptyList(); } final ArrayList<ModelB> output = new ArrayList<>(input.size()); for (final ModelA model: input) { output.add(convert(model)); } return output; } private List<ModelB> convert(final List<ModelA> input) { final ArrayList<ModelB> output = new ArrayList<>(); if (input != null) { for (final ModelA model: input) { output.add(convert(model)); } } return output; } Preallocate the list With the expected size Use a Preallocated “Empty List” Reduce Allocation, and Preallocate
  13. Avoid Unnecessary Allocations Delay allocations & Preallocate when possible ArrayList<String>

    data = new ArrayList<>(); data.add(“A"); data.add(“B”); data.add(“C”); A B capacity = 4 size = 2 The old Array Is Replaced With the new one private List<ModelB> convert(final List<ModelA> input) { if (input == null || input.isEmpty()) { return Collections.emptyList(); } final ArrayList<ModelB> output = new ArrayList<>(input.size()); for (final ModelA model: input) { output.add(convert(model)); } return output; } private List<ModelB> convert(final List<ModelA> input) { final ArrayList<ModelB> output = new ArrayList<>(); if (input != null) { for (final ModelA model: input) { output.add(convert(model)); } } return output; } Preallocate the list With the expected size Use a Preallocated “Empty List” Reduce Allocation, and Preallocate
  14. Avoid Unnecessary Allocations Delay allocations & Preallocate when possible ArrayList<String>

    data = new ArrayList<>(); data.add(“A"); data.add(“B”); data.add(“C”); A B size = 3 capacity = 4 C Finally The Item Is Added private List<ModelB> convert(final List<ModelA> input) { if (input == null || input.isEmpty()) { return Collections.emptyList(); } final ArrayList<ModelB> output = new ArrayList<>(input.size()); for (final ModelA model: input) { output.add(convert(model)); } return output; } private List<ModelB> convert(final List<ModelA> input) { final ArrayList<ModelB> output = new ArrayList<>(); if (input != null) { for (final ModelA model: input) { output.add(convert(model)); } } return output; } Preallocate the list With the expected size Use a Preallocated “Empty List” Reduce Allocation, and Preallocate
  15. Avoid Unnecessary Allocations Delay allocations & Preallocate when possible ArrayList<String>

    data = new ArrayList<>(); data.add(“A"); data.add(“B”); data.add(“C”); data.add(“D”); A B size = 4 capacity = 4 C D private List<ModelB> convert(final List<ModelA> input) { if (input == null || input.isEmpty()) { return Collections.emptyList(); } final ArrayList<ModelB> output = new ArrayList<>(input.size()); for (final ModelA model: input) { output.add(convert(model)); } return output; } private List<ModelB> convert(final List<ModelA> input) { final ArrayList<ModelB> output = new ArrayList<>(); if (input != null) { for (final ModelA model: input) { output.add(convert(model)); } } return output; } Preallocate the list With the expected size Use a Preallocated “Empty List” Reduce Allocation, and Preallocate
  16. Avoid Unnecessary Allocations Delay allocations & Preallocate when possible ArrayList<String>

    data = new ArrayList<>(); data.add(“A"); data.add(“B”); data.add(“C”); data.add(“D”); data.add(“E”); size = 4 capacity = 8 A B C D A new Array is created Double the size private List<ModelB> convert(final List<ModelA> input) { if (input == null || input.isEmpty()) { return Collections.emptyList(); } final ArrayList<ModelB> output = new ArrayList<>(input.size()); for (final ModelA model: input) { output.add(convert(model)); } return output; } private List<ModelB> convert(final List<ModelA> input) { final ArrayList<ModelB> output = new ArrayList<>(); if (input != null) { for (final ModelA model: input) { output.add(convert(model)); } } return output; } Preallocate the list With the expected size Use a Preallocated “Empty List” Reduce Allocation, and Preallocate
  17. Avoid Unnecessary Allocations Delay allocations & Preallocate when possible ArrayList<String>

    data = new ArrayList<>(); data.add(“A"); data.add(“B”); data.add(“C”); data.add(“D”); size = 4 capacity = 8 C D A B C D A B The Current Data Is Copied Over private List<ModelB> convert(final List<ModelA> input) { if (input == null || input.isEmpty()) { return Collections.emptyList(); } final ArrayList<ModelB> output = new ArrayList<>(input.size()); for (final ModelA model: input) { output.add(convert(model)); } return output; } private List<ModelB> convert(final List<ModelA> input) { final ArrayList<ModelB> output = new ArrayList<>(); if (input != null) { for (final ModelA model: input) { output.add(convert(model)); } } return output; } Preallocate the list With the expected size Use a Preallocated “Empty List” Reduce Allocation, and Preallocate
  18. Avoid Unnecessary Allocations Delay allocations & Preallocate when possible ArrayList<String>

    data = new ArrayList<>(); data.add(“A"); data.add(“B”); data.add(“C”); data.add(“D”); data.add(“E”); capacity = 8 size = 4 A B C D The old Array Is Replaced With the new one private List<ModelB> convert(final List<ModelA> input) { if (input == null || input.isEmpty()) { return Collections.emptyList(); } final ArrayList<ModelB> output = new ArrayList<>(input.size()); for (final ModelA model: input) { output.add(convert(model)); } return output; } private List<ModelB> convert(final List<ModelA> input) { final ArrayList<ModelB> output = new ArrayList<>(); if (input != null) { for (final ModelA model: input) { output.add(convert(model)); } } return output; } Preallocate the list With the expected size Use a Preallocated “Empty List” Reduce Allocation, and Preallocate
  19. Avoid Unnecessary Allocations Delay allocations & Preallocate when possible ArrayList<String>

    data = new ArrayList<>(); data.add(“A"); data.add(“B”); data.add(“C”); data.add(“D”); data.add(“E”); capacity = 8 E size = 5 A B C D private List<ModelB> convert(final List<ModelA> input) { if (input == null || input.isEmpty()) { return Collections.emptyList(); } final ArrayList<ModelB> output = new ArrayList<>(input.size()); for (final ModelA model: input) { output.add(convert(model)); } return output; } private List<ModelB> convert(final List<ModelA> input) { final ArrayList<ModelB> output = new ArrayList<>(); if (input != null) { for (final ModelA model: input) { output.add(convert(model)); } } return output; } Preallocate the list With the expected size Use a Preallocated “Empty List” Reduce Allocation, and Preallocate
  20. Avoid Unnecessary Allocations Delay allocations & Preallocate when possible ArrayList<String>

    data = new ArrayList<>(); data.add(“A"); data.add(“B”); data.add(“C”); data.add(“D”); data.add(“E”); data.add(“F”); capacity = 8 E F size = 6 A B C D private List<ModelB> convert(final List<ModelA> input) { if (input == null || input.isEmpty()) { return Collections.emptyList(); } final ArrayList<ModelB> output = new ArrayList<>(input.size()); for (final ModelA model: input) { output.add(convert(model)); } return output; } private List<ModelB> convert(final List<ModelA> input) { final ArrayList<ModelB> output = new ArrayList<>(); if (input != null) { for (final ModelA model: input) { output.add(convert(model)); } } return output; } Preallocate the list With the expected size Use a Preallocated “Empty List” Reduce Allocation, and Preallocate
  21. Avoid Unnecessary Allocations Delay allocations & Preallocate when possible ArrayList<String>

    data = new ArrayList<>(); data.add(“A"); data.add(“B”); data.add(“C”); data.add(“D”); data.add(“E”); data.add(“F”); capacity = 8 E F size = 6 A B C D new ArrayList<>(6) private List<ModelB> convert(final List<ModelA> input) { if (input == null || input.isEmpty()) { return Collections.emptyList(); } final ArrayList<ModelB> output = new ArrayList<>(input.size()); for (final ModelA model: input) { output.add(convert(model)); } return output; } private List<ModelB> convert(final List<ModelA> input) { final ArrayList<ModelB> output = new ArrayList<>(); if (input != null) { for (final ModelA model: input) { output.add(convert(model)); } } return output; } Preallocate the list With the expected size Use a Preallocated “Empty List” Reduce Allocation, and Preallocate
  22. Avoid Unnecessary Map Lookups Use Variables! // SLOW: two lookup

    on the same key if (myMap.contains("key")) { MyObject v = myMap.get("key"); // ... } final MyObject v = myMap.get("key"); if (v != null) { // equivalent to myMap.contains("key") // ... } Avoid one lookup final HashMap<String, MyObject> myMap = new HashMap<>(); // ... for (int i = 0; i < N; ++i) { // SLOW: each iteration there are 3 lookups for the same key final String key = "key-" + i; myMap.get(key).doSomething(); myMap.get(key).doMore(myMap.get(key).getData()); } for (int i = 0; i < N; ++i) { final MyObject value = myMap.get("key-" + i); value.doSomething(); value.doMore(value.getData()); } Use Variables, to Avoid Extra Lookups final Map<String, MyObject> myMap; // ... final MyObject obj; if (myMap.containsKey(key)) { // if present, get the object obj = myMap.get(key); } else { // not present, compute the new object obj = new MyObject(); // add to it the map myMap.put(key, obj); } obj.doStuff() // do something with the object final Map<String, MyObject> myMap; // ... MyObject obj = myMap.computeIfAbsent(key, k -> new MyObject()); obj.doStuff() // do something with the object Use Compute If Absent
  23. How to Iterate a Map Avoid the extra lookup for

    (final String key: map.keySet()) { // SLOW: each iteration does a lookup MyObject value = map.get(key); // ... } for (final Map.Entry<String, MyObject> entry: map.entrySet()) { final String key = entry.getKey(); final MyObject value = entry.getValue(); // ... } for (final String key: map.keySet()) { // ... } final Iterator<Map.Entry<String, MyObject>> it = map.entrySet().iterator(); while (it.hasNext()) { final Map.Entry<String, MyObject> entry = it.next(); if (entry.getValue().points() < 500) { it.remove(); } } // find the keys to remove and add them to the 'toDelete' list final ArrayList<String> toDelete = new ArrayList<>(); for (final Map.Entry<String, MyObject> entry : map.entrySet()) { if (entry.getValue().points() < 500) { toDelete.add(entry.getKey()); } } // remove entries from the map for (final String key : toDelete) { map.remove(key); } for (final Map.Entry<String, MyObject> entry : map.entrySet()) { if (entry.getValue().points() < 500) { map.remove(entry.getKey());! } } ConcurrentModi fi cationException for (final MyObject value: map.values()) { // ... } Iterate & Remove Iterate Keys Only Iterate each Entry (Key/Value) Iterate Values Only Use Iterator and .remove() Use .entrySet() to avoid the extra lookup
  24. Singleton Pattern Let the JVM Handle The Concurrency Problem //

    Singleton.getInstance().doStuff() public final class Singleton { private static class HoldInstance { private static final Singleton INSTANCE = new Singleton(); } public static Singleton getInstance() { return HoldInstance.INSTANCE; } private Singleton() {} public void doStuff() { /* do stuff... */ } } Lazy initialization // Singleton.INSTANCE.doStuff() private final class Singleton { public static final Singleton INSTANCE = new Singleton(); private Singleton() {} public void doStuff() { /* do stuff... */ } } Eager initialization public class BadSingleton { private static BadSingleton instance = null; public static BadSingleton getInstance() { if (instance == null) { // The caller thread may be paused here, // if another thread calls getInstance() // you end up with 2 instances instance = new BadSingleton(); } return instance; } } Lack of Thread Safety Multiple threads can create multiple instances simultaneously. No guarantee of a single instance being created. // Singleton.INSTANCE.doStuff() public enum Singleton { INSTANCE; private Singleton() {} public void doStuff() { /* do stuff... */ } } Enum initialization