2. Ordering Class Members by Scopes
3. Class Members should be private
4. Using Underscores in Numeric Literals
6. Using StringBuilder or StringBuffer instead of String Concatenation
7. Using Enums or Constant Class instead of Constant Interface
8. Avoid Redundant Initialization (0-false-null)
9. Using Interface References to Collections
10. Avoid using for loops with indexes
Watch the overview video and then read the article below:public class StudentManager { protected List<Student> listStudents; public int numberOfStudents; private String errorMessage; float rowHeight; float columnWidth; protected String[] columnNames; private int numberOfRows; private int numberOfColumns; public String title; }According to this best practice, the member declaration above should be sorted out like this:
public class StudentManager { private String errorMessage; private int numberOfColumns; private int numberOfRows; float columnWidth; float rowHeight; protected String[] columnNames; protected List<Student> listStudents; public int numberOfStudents; public String title; }And the members in each group are sorted by alphabetic order. This private-first and public-last style helps us quickly locate member variables when the list grows up over times.
public class Student { public String name; public int age; }The problem with this poor design is that anyone can change the values of the fields inappropriately. For example:
Student student = new Student(); student.name = ""; student.age = 2005;Of course we don’t want the name to be empty and the age to be unrealistic. So this practice encourages us to hide the fields and allow the outside code to change them the through setter methods (or mutators). Here’s an example of a better design:
public class Student { private String name; private int age; public void setName(String name) { if (name == null || name.equals("")) { throw new IllegalArgumentException("Name is invalid"); } this.name = name; } public void setAge(int age) { if (age < 1 || age > 100) { throw new IllegalArgumentException("Age is invalid"); } this.age = age; } }As you can see, the fields name and age are declared to be private so the outside code cannot change them directly (information hiding). And we provide two setter methods setName() and setAge() which always check for valid arguments before actually updating the fields. This ensures the fields always get appropriate values.
int maxUploadSize = 20971520; long accountBalance = 1000000000000L; float pi = 3.141592653589F;And compare with this one:
int maxUploadSize = 20_971_520; long accountBalance = 1_000_000_000_000L; float pi = 3.141_592_653_589F;Which is more readable?So remember to use underscores in numeric literals to improve readability of your code.
public class Sum { public static void main(String[] args) { int a = 0; int b = 0; try { a = Integer.parseInt(args[0]); b = Integer.parseInt(args[1]); } catch (NumberFormatException ex) { } int sum = a + b; System.out.println("Sum = " + sum); } }Note that the catch block is empty. If we run this program by the following command line:
java Sum 123 456yIt will fail silently:
Sum = 123It’s because the second argument 456y causes a NumberFormatException to be thrown, but there’s no handling code in the catch block so the program continues with incorrect result.Therefore, the best practice is to avoid empty catch blocks. Generally, we should do the following things when catching an exception:
public class Sum { public static void main(String[] args) { int a = 0; int b = 0; try { a = Integer.parseInt(args[0]); b = Integer.parseInt(args[1]); } catch (NumberFormatException ex) { System.out.println("One of the arguments are not number." + "Program exits."); return; } int sum = a + b; System.out.println("Sum = " + sum); } }
public String createTitle(int gender, String name) { String title = "Dear "; if (gender == 0) { title += "Mr"; } else { title += "Mrs"; } return title; }This is perfectly fine since only few String objects involved. However, with code that involves in concatenating many Strings such as building a complex SQL statements or generating lengthy HTML text, the + operator becomes inefficient as the Java compiler creates many intermediate String objects during the concatenation process.Therefore, the best practice recommends using StringBuilder or StringBuffer to replace the + operator for concatenating many String objects together as they modify a String without creating intermediate String objects. StringBuilder is a non-thread safe and StringBuffer is a thread-safe version.For example, consider the following code snippet that uses the + operator to build a SQL query:
String sql = "Insert Into Users (name, email, pass, address)"; sql += " values ('" + user.getName(); sql += "', '" + user.getEmail(); sql += "', '" + user.getPass(); sql += "', '" + user.getAddress(); sql += "')";With StringBuilder, we can re-write the above code like this:
StringBuilder sbSql = new StringBuilder("Insert Into Users (name, email, pass, address)"); sbSql.append(" values ('").append(user.getName()); sbSql.append("', '").append(user.getEmail()); sbSql.append("', '").append(user.getPass()); sbSql.append("', '").append(user.getAddress()); sbSql.append("')"); String sql = sbSql.toString();See more here: Why Use StringBuffer and StringBuilder in Java
public interface Color { public static final int RED = 0xff0000; public static final int BLACK = 0x000000; public static final int WHITE = 0xffffff; }It’s because the purpose of interfaces is for inheritance and polymorphism, not for static stuffs like that. So the best practice recommends us to use an enum instead. For example:
public enum Color { BLACK, WHITE, RED }In case the color code does matter, we can update the enum like this:
public enum Color { BLACK(0x000000), WHITE(0xffffff), RED(0xff0000); private int code; Color(int code) { this.code = code; } public int getCode() { return this.code; } }In a complex project, we can have a class which is dedicated to define constants for the application. For example:
public class AppConstants { public static final String TITLE = "Application Name"; public static final int VERSION_MAJOR = 2; public static final int VERSION_MINOR = 4; public static final int THREAD_POOL_SIZE = 10; public static final int MAX_DB_CONNECTIONS = 50; public static final String ERROR_DIALOG_TITLE = "Error"; public static final String WARNING_DIALOG_TITLE = "Warning"; public static final String INFO_DIALOG_TITLE = "Information"; }So the rule of thumb is: Do not use interfaces for constants, use enums or dedicated classes instead.
public class Person { private String name = null; private int age = 0; private boolean isGenius = false; }This is also redundant:
public class Person { private String name; private int age; private boolean; public Person() { String name = null; int age = 0; boolean isGenius = false; } }Therefore, if you know the default initialization values of member variables, you will avoid unnecessary explicit initialization. See more here: Java default Initialization of Instance Variables and Initialization Blocks.
public class CollectionsRef { private HashSet<Integer> numbers; public ArrayList<String> getList() { return new ArrayList<String>(); } public void setNumbers(HashSet<Integer> numbers) { this.numbers = numbers; } }Look at the reference types which are collection implementation classes - this locks the code to work with only these classes HashSet and ArrayList. What if we want the method getList() can return a LinkedList and the method setNumbers() can accept a TreeSet?The above class can be improved by replace the class references to interface references like this:
public class CollectionsRef { private Set<Integer> numbers; public List<String> getList() { // can return any kind of List return new ArrayList<String>(); } public void setNumbers(Set<Integer> numbers) { // can accept any kind of Set this.numbers = numbers; } }
String[] names = {"Alice", "Bob", "Carol", "David", "Eric", "Frank"}; for (int i = 0; i < names.length; i++) { doSomething(names[i]); }As you can see, the index variable i in this for loop can be altered incidentally which may cause unexpected result. We can avoid potential problems by using an enhanced for loop like this:
for (String aName : names) { doSomething(aName); }This does not only remove potential issues but also make the code cleaner and more succinct. See more: The 4 Methods for Iterating Collections in Java