...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?
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
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; }
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
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();
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?
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
(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
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