Java Best Practices

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://javarevisited.blogspot.com/2013/06/why-favor-composition-over-inheritance-java-oops-design.html

https://en.wikipedia.org/wiki/Single_responsibility_principle







 

        


To view or add a comment, sign in

More articles by Anil Verma

Others also viewed

Explore content categories