Java Best Practices
If you are a Java Developer, or a manager managing/mentoring your developers, by following these best practices you would ensure that your software has less bugs, it is easier to change and easier to read.. We need to be familiar with customary and effective ways to structure our code. These guidelines will help you how to structure your code so that it works well, so that other people can understand it, so that future modifications and improvements are less likely to break your existing code, your programs will be elegant. Use these guidelines while writing your own code, or reviewing someone else's code.
Rule 1 - Consider static factory methods over constructors.
Why - Unlike constructor they have names. Also unlike constructors they are not required to create a new object each time they are invoked. Also unlike constructors, they can return object of any subtype of their return type. They reduce the verbosity of creating parameterized type instances.
Why not - When you need subclasses, then don’t use this. As providing only static factory methods without public or protected constructors prevents subclassing.
Some Good name suggestions of static factory methods.
- valueOf
- of
- getInstance
- newInstance
- getType
- newType
Rule 2 - Avoid creating non-necessary objects
String s = new String("sjkfklrklfr"); // DON'T DO THIS!
String s = "sjkfklrklfr"; // The right way
Prefer primitives to boxed primitives.
Rule 3 - Avoid finalizers
Finalizers are unpredictable, dangerous and mostly unnecessary. Never do anything time critical in finalizer. Never depend on finalizer to update critical persistent state. There is a huge performance penalty when using finalizers.
Rule 4 - Always override hashcode when you override equals
Do not override equals that depends on unreliable sources. While overriding equals, ensure that all objects are unequal to null. Always check if your equals method is
reflexive (x.equals(x) must return true),
transitive (if x.equals(y) returns true, then y.equals(x) must return true) and
consistent (multiple invocations of x.equals(y) should consistently return true or false).
Equal objects must have equal hashcodes.
// The worst possible legal hash function - never use!
@Override public int hashCode() { return 23; }
// A good hashcode implementation
@Override public int hashCode() {
int result = 19;
result = 31 * result + locationCode;
result = 31 * result + suffix;
result = 31 * result + lineNor;
return result;
}
Rule 5 - Always override toString
Providing a good toString implementation makes your class much more pleasant to use. The toString method should return info about the object.
Rule 6 Implement Comparable for Objects to be used in Collections
Implement this interface when you are planning to use this object anywhere in collections.
public interface Comparable<T> {
int compareTo(T t);
}
Rule 7 Default everything to private except setters and getters and one public API/method in class
Make each class or member as inaccessible as possible. Instance fields should never be public. Classes with public mutable fields are not thread safe.
Rule 8 Immutable classes are highly preferable over mutable classes
- Don’t provide any methods that modify object’s state.
- Ensure that class can’t be extended (u can make class final)
- Make all fields final
- Make all fields private
Biggest benefit of Immutable objects is that they are inherently thread safe. No synchronization is required. They can be shared freely among various threads.
Remember the rule of thumb, Classes should be immutable unless there is a very good reason to make them mutable. If the class can’t be made immutable, limit the mutability as much as possible.
Rule 9 Use composition over inheritance
Unlike method invocation, inheritance violates encapsulation (here we are talking about class inheritance and subclassing, not implementation inheritance). Inheritance is powerful, but it is problematic because it violates encapsulation. Use composition and forwarding instead of inheritance, especially if an appropriate interface to implement a wrapper class exists. Not only are wrapper classes more robust than subclasses, they are also more powerful.
Rule 10 Use interfaces instead of Abstract Classes
Existing classes can be easily updated to implement a new interface.
Interfaces are ideal for creating mixins. Use of interfaces is ideal to define types.
Rule 11 Do not use Raw Types when writing new code
//This is a raw collection. Don’t do this
private final Collection employees = ... ;
// Now a raw iterator type - don't do this!
for (Iterator i = eployees.iterator(); i.hasNext(); ) {
Employees = (Employee) i.next(); // Throws ClassCastException
... // Do something with the Employee
}
// Parameterized collection type - typesafe - This is the right way.
private final Collection<Employee> employees = ... ;
// for-each loop over a parameterized collection - typesafe
for (Employee e : employees) { // No cast
... // Do something with the Employee
}
// for loop with parameterized iterator declaration - typesafe
for (Iterator<Employee> i = employees.iterator(); i.hasNext(); ) {
Employee s = i.next(); // No cast necessary
... // Do something with the Employee
}
If you use raw types, you lose all the safety and expressiveness benefits of generics.
Rule 12 Prefer lists to arrays
if Sub is a subtype of Super, then the array type Sub[] is a subtype of Super[]. Generics, by contrast, are invariant: for any two distinct types Type1 and Type2, List<Type1> is neither a subtype nor a supertype of List<Type2>
// Fails at runtime!
Object[] objectArray = new Long[1];
objectArray[0] = "Some tandom text"; // Throws ArrayStoreException
but this one is not:
// Won't compile!
List<Object> ol = new ArrayList<Long>(); // Incompatible types
ol.add("Another random text");
Rule 13 Favor generic types and methods
See a simple generic example below.
public static <E> Set<E> merge(Set<E> s1, Set<E> s2) {
Set<E> result = new HashSet<E>(s1);
result.addAll(s2);
return result;
}
Rule 14 Always use Override annotation
Override annotation helps avoid silly mistakes and helps find problems during compile time itself.
@Override public boolean equals(Bigram b) {
return b.first == first && b.second == second;
}
Use Override annotation on every method declaration that you believe to override a super class declaration.
Rule 15 While writing methods, Always check parameters for validity.
Each time you write a method, you should think about what restrictions exist on its parameters. You should document these restrictions and enforce them with explicit checks at the beginning of the method body. That will help you immensely when a dumb usage of your method occurs by client (or even a fellow developer) and a validation fails.
Rule 16 For Parameter types prefer interfaces over classes
If there is an appropriate interface available, use it in favor of a class for parameter type. There is no need to write a method which takes ArrayList as input, use List instead.
Avoid method overloading. It makes, the code less readable. Don’t use varargs. They again make the code less readable and less maintainable.
Rule 17 Return empty array or Collections, not nulls
There is no reason ever to return null from an array or collection valued method instead of returning an empty array or collection.
Rule 18 Reduce scope of local variables
The most powerful technique for minimizing the scope of a local variable is to declare it where it is first used. Almost always, every local variable declaration should contain an initializer. And a very good technique to minimize the scope of local variables is to keep methods small and focussed.
Rule 19 Prefer traditional for-each loops to traditional for loops
// The preferred idiom for iterating over collections and arrays
for (Element e : elements) {
doSomething(e);
}
Rule 20 Avoid using String concatenation
Using the string concatenation operator repeatedly to concatenate n strings requires time quadratic in n. To achieve acceptable performance, use a StringBuilder in place of a String
// This Performs real bad!
public String generator() {
String result = "";
for (int i = 0; i < numItems(); i++)
result += lineForItem(i); // String concatenation
return result;
}
//This performs much better.
public String statement() {
StringBuilder b = new StringBuilder(numItems() * LINE_WIDTH);
for (int i = 0; i < numItems(); i++)
b.append(lineForItem(i));
return b.toString();
}
Rule 21 Refer to objects by their interfaces
If appropriate interface types exist, then parameters, return values, variables, and fields should all be declared using interface types.
// Good - uses interface as type
List<Subscriber> subscribers = new Vector<Subscriber>();
rather than this:
// Bad - uses class as type!
Vector<Subscriber> subscribers = new Vector<Subscriber>();
If you get into the habit of using interfaces as types, your program will be much more flexible. It is entirely appropriate to refer to an object by a class rather than an interface if no appropriate interface exists.
Rule 22 Never ignore exceptions
Avoid these anti patterns in your code. An empty catch block defeats the purpose of Exceptions.
// Empty catch block ignores exception - Worst possible way of catching an exception
try {
...
} catch (SomeException e) {
}
Above example will catch the exception and do nothing, so the end user or developer will never know why your software is not working as intended.
try {
...
} catch (SomeException e) {
e.printStackTrace();
}
This is equally bad, this will print stack trace on console or your container console, but will never persist the logs in a log file.
// This is a good way. This will write the whole stack trace in log file and can be reviewed for root causing the issues.
try {
...
} catch (SomeException e) {
log.error(“Failed to do desired work”, e);
}
Rule 23 Follow Single Responsibility Principle - One Class should have one and only one responsibility
If your class is a utility class, it should have just one API, public method to be used by others.
// Code not following Single Responsibility Principle
public class UserSettingService
{
public void changeEmail(User user)
{
if(checkAccess(user))
{
//Grant option to change
}
}
public boolean checkAccess(User user)
{
//Verify if the user is valid.
}
}
Corrections in the code to follow single responsbility principle
public class UserSettingService
{
public void changeEmail(User user)
{
if(SecurityService.checkAccess(user))
{
//Grant option to change
}
}
}
public class SecurityService
{
public static boolean checkAccess(User user)
{
//check the access.
}
}
References
https://www.oreilly.com/library/view/effective-java-3rd/9780134686097/
https://www.baeldung.com/java-constructors-vs-static-factory-methods
https://en.wikipedia.org/wiki/Single_responsibility_principle