Sundarrajk's Weblog

Archive for the ‘Effective Java’ Category

This is the ninth and the last in the series of posts based on the book Effective Java by Joshua Bloch. In the previous post we saw best practices in using Threads in Java.

This post talks about the best practices in Serialization. This topic may not seem to appeal and/or be relevant to the many developers who are today writing Web Applications directly. What the developers need to keep in mind is that everytime they are using EJB, they are dealing with Serialization, everytime they are faced with session replication, they are dealing with serialization, everytime they are planning to cache something using an application like memcached or many of the other NoSQL databases they are dealing with Serialization. Serialization creeps in without the knowledge of the developer and so it is important to understand the best practices.

Implement Serialization Judiciously

Making a class Serializable, theoretically only requires only implementing the interface Serializable.
1.       Once a class is made serializable then it becomes very difficult to change the class without supporting all the earlier releases and this will be a tedious exercise. To circumvent this problem one will need to design and implement one’s own custom serialize method.
2.       The second problem is that everytime one deserializes the class one needs to be wary about bad data coming into the class. If not checked correctly one can end up with security issues.
3.       The testing of the class with new version of the class becomes more and more difficult.
If an alternate form of serialization like conversion to XML is available that should be considered as opposed to serialization of bytes.
Consider using a custom serialized form

If we use the default serializable format then one can never change the class.
1.       Do not accept the default serialized form without first considering whether it is appropriate
2.       Even if you decide that the default serialized form is appropriate, you often must provide a readObject to ensure invariants and security
3.       Declare an explicit serial version UID in every serializable class you write.
Write readObject methods defensively

A readObject method will be required to be implemented to validate that the class is in the right state. A malicious client can provide data which would normally not have been accepted via the constructor. Even if one validate the data after the stream a malicious program can provide the input stream for creation of the object and later modify the attributes of the class to invalid data or different data. To avoid this make a copy of the objects in the readObject method so that the original read objects can no longer be altered by the client that provided the stream.
Provide a readResolve method when necessary

A singleton class no longer remains a singleton if it implements serializable as now one will be able to create another instance by deserializing the earlier serialized class. To prevent this one needs to implement the readResolve method
1.       A readResolve method is required not only for Singletons but all classes that control instance creation on its own.
2.       Instead of creating the instances as suggested in the readObject method in the earlier topic one could return a new instance from readResolve to prevent the same problem as mentioned earlier.
Conclusion
With this we come to the end of the series of posts based on the book Effective Java by Joshua Bloch. It is wonderful book and should be read by all the Java programmers. The author comes with tons of experience in Java and the advice that they proffer in the book are extremely important to all developers.
This is the eighth post in the series of postings based on the book Effective Java by Joshua Bloch. In the previous post we read about best practices in Exception handling in Java.

This post covers the best practices in writing threaded applications. Threads are not understood properly by many and if not used properly can lead to a variety of problems. It is important to understand and use the best practices outlined here.
Synchronized access to shared mutable data

It is better to use an available library which is thread safe rather than trying to write one’s own thread safe classes.
When a method that mutates the object can be accessed by more than one thread at a time it is better to synchronize the method rather than except it to work.
Usage of volatile to overcome the problems of synchronization is questionable and do not use it.
Avoid Excessive Synchronization

While synchronization is necessary avoid excessive synchronization. Excessive synchronization will lead to poor performance and in worst case scenarios lead to race conditions or deadlocks.
To safeguard against deadlocks never invoke an abstract method in a synchronized block.
To keep performance acceptable, do as little work as possible inside the synchronized block.
Take as granular a lock as possible.
Never invoke wait outside a loop

The object.wait method is used to make a thread wait for some condition. It must be invoked inside a synchronized region that locks the object on which it is invoked. The right use of wait is as follows:
synchronized(obj) {
    while() {
        obj.wait();
    }
    //Perform action appropriate to condition
}
Note that the condition needs to be checked before entering the wait state. If a method enters a wait state without checking for the condition it is not guaranteed that it will get the notify or notifyAll event to wake it from its wait state. This will mean that the thread can go into an hang state and never come out of it.
Don’t depend on the thread scheduler

Do not depend on the thread scheduler to stop work in a thread and hand over control to other threads. Voluntarily stop working by invoking an Object.wait or a Thread.sleep.
Document Thread Safety

If the user does not know if a method is thread-safe or not then we can end up with a code which has excessive synchronization or no synchronization and both will lead to problems in the system. One should always specify if a method is thread safe or not. It should specify one of the following:
1.       Immutable: This indicates that the class and its method are thread-safe by virtue of the class being immutable.
2.       Thread-safe: Although the class is mutable the methods have sufficient synchronization and so do not need any external synchronization.
3.       Conditionally thread-safe: This means that the class is thread-safe exception that there are some methods that must be invoked in sequence without interference from other threads. Examples are Hashtable and Vector whose iterators need to be invoked in an externally synchronized block.
4.       Thread-compatible: These are classes who are not thread safe by themselves by can be made thread-safe by invoking methods or series of methods of this class in a synchronized block.
5.       Thread-hostile – This means that this class should never be used in a multi-threaded environment. No amount of synchronization will help such classes.
Avoid Thread Groups

The concept of thread groups are obsolete and should not be used.

In the next and the last post we will read about best practices of Serialization.

This is the seventh post in the series of posts based on the book Effective Java by Joshua Bloch. In the previous post we read about General Programming practices to write good code in Java.

This section speaks about the best practices in handling Exception. This is one area that many do not understand properly and many of the problems arise due to lack of handling Exceptions.
Use Exceptions Only for Exceptional Conditions

Do not use exceptions to handle situations which can be handled without exceptions. E.g
try {
    while(true) {
        a[i++] .f();
    }
catch(ArrayIndexOutOfBoundsException e) {
}
Instead use
for (i = 0; i < a.length; i++) {
    a[i].f();
}
Exceptions as the name indicates should be used to handle Exceptional situations; they should never be used for ordinary control flow.
Use checked exceptions for recoverable conditions and run-time exceptions for programming errors

There are three types of exceptions in Java
1.       Checked Exceptions: These are exceptions that can be handled by the client. E.g. IOException, FileNotFoundException.  The compiler will force the user of the API to handle such exceptions. I.e. the user needs to either declare it in the methods throws clause or needs to put the code within a try – catch block where the Exception is handled.
2.       Run Time Exceptions: E.g. ArrayIndexOutOfBoundsException, NullPointerException. These are subclasses of java.lang.RuntimeException. The compiler will not force the user of the API to handle such exceptions.
3.       Errors: These are serious errors from which the users cannot be excepted to recover. OutOfMemoryError is an example of this. These are subclasses of java.lang.Error.
The Checked Exceptions should be mentioned in the throws clause so that the user of the API can handle it or pass it on to its caller to be handled by it.
Runtime exceptions should normally not be thrown or handled. But run-time can be used to indicate programming errors. E.g. one could throw an IllegalArgumentException if any of the parameters passed to the method does not meet the preconditions expected of it.
Errors should normally never be thrown or handled by any method.
Avoid unnecessary use of checked exceptions

 A method should throw as less a number of checked exceptions as possible. Throwing more checked exceptions makes it difficult for the user of the API to use the API and the user ends up handling a generic Exception. Throw checked exceptions if and only if the user of the API can recover from the exception that has been raised.
If some exceptions can be prevented by providing APIs for checking conditions that can throw the exceptions provide this function so that the user can use this API instead of handling a RunTimeException. E.g. if a method of a class can be invoked conditionally then provide a method by which the user can check if the method can be invoked before invoking the method so that we can throw a Run Time exception from the method if there is an issue.
E.g. instead of forcing the user to write a code as follows:
try {
    obj.action(args);
} catch(MethodAccessDeniedException e) {
    //Handle Exception
}
We could provide a method called has Access and allow the user to write  code as
if (obj.hasAccess(args)) {
    obj.action(args);
} else {
    //Handle no access situation
}
If a user invokes obj.action(args) without first checking for has Access then the user may get an RunTimeException.
Throw Exceptions Appropriate to the Abstraction

Sometimes if APIs throw Exceptions which are seemingly out of place it can be very confusing. This typically happens if one were to throw an Exception generated at the lower level directly to the upper level. To avoid this, higher layers should catch lower level exceptions and in their place throw exceptions that are explainable In terms of the higher-level abstraction. When doing this it is important to ensure that the lowest level of Exception and the stack trace associated with it is not lost. This can be achieved by using Exception chaining.
try {
    ….
} catch(LowerLevelException e) {
    throw new HigherLevelException(e);
}

Document all Exceptions thrown by each Method

Always document the exceptions that will be thrown by method. Never use a generic exception like java.lang.Exception or java.lang.Throwable to indicate the Exception thrown. This gives the user of API no idea of what actually is the Exception that is being thrown from the API.
1.       Document every checked Exception that the method can throw using the @throws tag and include them in the throws keyword in the method declaration.
2.       Document every unchecked Exception that the method can throw using the @throws tag, but do not declare it in the throws keyword along with the checked exceptions in the method declaration.
3.       If an exception is thrown by many methods in a class for the same reason then it is not wrong to document this at the class level indicating the reason for the methods throwing this exception.
Include failure-capture information in detail messages

When a program fails due to an unhandled exception the system automatically prints the stack trace which includes the detailed message in the Exception. This will typically be the only information somebody has to figure out what went wrong. So to make this comprehensive the message should contain the all the parameter values and field values that contribute to the exception. One way to ensure that the Exception has all the details required is to create a constructor which forces the user to provide the required information by default.
Strive for failure atomicity

Generally speaking, a failed method should leave the object in the state that it was in prior to the invocation. A method with this property is said to be failure atomic.
The ways to achieve this are
1.       Make the class immutable. This will ensure that the object is never altered irrespective of the exception thrown from any method
2.       Check the parameters and fields for validity before proceeding with the method execution. This will ensure the exception is thrown even before the object is mutated.
3.       Another option is to write a recovery code which can be invoked before an exception is throw to restore the object the state prior to the invocation of the method that is throwing the exception.
4.       The last option is to make a temporary copy of the object and work on it and if no exception occurs restore the new state to the original object. This can be an expensive operation and should be done if an only if really necessary.
This should be avoided if it makes the code more complex or if it drastically reduces the performance of the system.
Don’t ignore Exception

Of an exception is caught then it should be handled correctly. One should not have a code as follows:
try {
    …
} catch (SomeException e) {
}
An empty catch block defeats the purpose of exceptions which is to force the user to handle exceptional situations.
At the minimum have a comment explaining why the exception is being ignored.

In the next post we will read about best practices of using Threads in Java.

This is the sixth post based on the book Effective Java by Joshua Bloch. In the previous post we read about best practices to write good methods in Java.

This post covers General Programming practices which will help writing good quality code.

Minimize the scope of the local variables

Declare variables at the last possible moment. Defining variables before they are really required can mean that the scope of the variable may turn out to be more than necessary, it may inadvertently be used before it is logically initialized, it can be used accidently after its scope has finished. All of these can have disastrous impact on the application. Also somebody reading the code can tend to forget the type of the variable if it is used far away from where it is declared.
Every local variable should contain an initializer. The only exceptions can be variables which are required in the finally block when exceptions are handled.
Know and use the libraries

It is important to learn the libraries available and leverage them. Libraries including standard Java library are changing and becoming more powerful as time progresses and unless one keeps track of the changes one would end up performing unnecessary coding to achieve something which can be better done by some library method.
Understand and then keep abreast of changes to at least java.lang, java.util and java.io.
Avoid float and double if exact answers are required

The float and double types will not give precise values. They are not suited for monetary calculations. Rounding off will also not help. Use BigDecimal or int or long for monetary calculations.
Also when using BigDecimal ensure to instantiate as follows:
BigDecimal tenPaise = new BigDecimal(“0.1”);
not
BigDecimal tenPaise = new BigDecimal(0.1);
Note that in the second case we end up using a float value inadvertently and this will result in the BigDecimal getting a value close to 0.1 and not exactly 0.1, whereas in the first case since we have used a String as the parameter it will give a precise value of 0.1.
Avoid Strings where other types are more appropriate

Do not use String type variables to represent data which is actually of a different data type. E.g. when data is received from a browser or read from a file it typically is in the form of a string and one tends to carry forward the same till it is necessary to deal with the actual data type. Instead convert the strings to the correct data type at the first opportunity and convert them back into strings only at the last possible moment.
Do not concatenate string representation of strings to form a key for a Map. Instead use a helper class which implements the correct equals and hashCode method to contain the fields that are required for the key.
Beware the Performance of String Concatenation

Using the “+” operator to concatenate strings is a big overhead. Instead use a StringBuffer if one needs the variable to be thread safe or better still use StringBuilder if one is sure that it is not necessary for the variable to be thread safe.
Additionally prime the StringBuffer or StringBuilder with the expected size it is expected to grow to if it is possible.
Prefer Interfaces to Reflection

Reflection in Java is a very powerful feature which allows one to interact with the objects at runtime by discovering what the object supports and using them.
But this comes at a cost and should be avoided as far as possible. Instead of reflection one should consider use of Interfaces.
Reflection comes with the following disadvantages:
1.       All benefits of compile time checking is lost.
2.       The code required to perform reflective access is clumsy and verbose.
3.       Performance of code using reflection will be slower than code that does not use reflection
If the issue to determine the class at runtime then one should define an interface, use the interface in the code and at run time use reflection to instantiate the class (factory pattern). This way we can keep reflection to the minimum while retaining benefits that accrue with usage of interfaces.
Note that all Object Relation Mapping libraries use reflection.

Use Native Methods Judiciously

Java provides a way to execute native code. This was used to get better performance and/or to use some native library functions. But given the progress that JVM has made one should use native calls if and only if that is the last resort to use a library that is available only in the native format. It is not worth the trouble of using native calls for improved performance.
Optimize Judiciously

Many a times the developers have the tendency to optimize as they are writing the code. Here are some aphorisms about optimization that should be known and adhered to by all:
1.       More computing sins are committed in the name of efficiency (without necessarily achieving it) than for any other single reason – including blind stupidity. William A Wulf
2.       We should forget about small efficiencies, say about 97% of the time: premature optimization is the root of all evil. Donald E. Knuth
3.       We follow two rules in matter of optimization
a.       Rule 1: Don’t do it
b.      Rule 2 (for experts only): Don’t do it yet – that is not until you have a perfectly clear and unoptimized solution.
M. A. Jackson
Corollary is “Strive to write good programs rather than fast ones”.
But the same time
1.       Strive to avoid design decisions that limit performance. It is difficult to change APIs, wire-level protocols, and persistent data formats. These should be considered carefully at design time to provide good performance right at the start.
2.       Consider performance consequences of your API decisions.
3.       Measure performance before and after each attempted optimization.
Adhere to generally accepted naming convention

There is a generally accepted naming convention for the Java language. Adhere to this naming conventions and avoid violating these unless there is real reason to do so. It will make the program understandable and maintainable by a larger audience. Some conventions are
1.       Package names should be in lower case
2.       Package names can have abbreviated words
3.       Class names should start with a Capital letter
4.       Method, Attribute and Variable names should start with lower case
5.       Typically Acronyms used in Class, Method, Attribute or Variable names are capitalized although there is no general agreement on this
6.       Generally Class, Method, Attribute and Variable names should have full words
7.       Use a Camel Hump notation to separate words in classname, methods, attributes and variables.
8.       Underscores are used only for constants
9.       Constants are defined with a set of Capital letters and Underscores
10.   Interfaces are named as classes or as adjectives ending with “able” or “ible”
11.   One could follow the convention of naming all interfaces starting with an “I” although this is not a convention that is followed by all. This avoids the necessity of adding an “Impl” to indicate implementation of the interface.
12.   All methods that return a Boolean value start with “is”.

In the next post we will read about best practices in Exception Handling in Java.

This is the fifth in the series of posts based on the book Effective Java by Joshua Bloch. The previous post was about best practices in converting C structures to Java.

This post gives tips and best practices on how to write good methods in the classes.

Check Parameters for Validity

Methods that expect parameters expect the parameters to adhere to certain rules. In all public methods one should validate the parameters to ensure that they adhere to the expected rules and if not they should throw the right exception. It is also important that the constraints on the parameters be well documented.
For private methods we should ensure that we pass only the right attributes and instead of checking and throwing exceptions use Assertions.
It is very important that the parameters to the constructors are validated correctly.
The only exception under which one should not validate the parameters is, when there is a huge overhead in the validation.
Make Defensive Copies when needed

Normally a class should not expect an external class to be able to change the values of its attributes without its knowledge. But when a class contains mutable objects it is easy to allow clients to change the attributes without the class knowing it.
E.g. consider the following class:
public final class Period {
    private final Date start;
    private final Date end;
    public Period(Date start, Date end) {
        if (start.compareTo(end) > 0) {
            throw new IllegalArgumentException(start + “ after “ + end);
        }
        this.start = start;
        this.end = end;
    }
    public Date start() {
        return start;
    }
    public Date end() {
        return end;
    }
}
This class seems to be immutable as the only way to set the values of the start and end dates are through the constructors. But the Date class itself is a mutable class and there are two ways in which the values of the class can be changed:
1.       A client which passes the start and end date to the constructor can hold on the instance of the date and then at a later stage can change the value of these dates using the setYear, setMonth or any of the other methods.
2.       Similarly there is nothing to stop a client from invoking something as Period.start.setYear(someYear).
To avoid these we need to make copies of such mutable classes. In the constructor we can create new Date objects and store them instead of the passed date objects.
    public Period(Date start, Date end) {
        this.start = new Date(start.time());
        this.end= new Date(end.time());
        if (this.start.compareTo(this.end) > 0) {
            throw new IllegalArgumentException(start + “ after “ + end);
        }
    }
Now even if the client changes the objects that it passed to the constructor the values of the dates in the class will not change. Note that the validation was done after the copy was done. This is to prevent the situation where the value of the dates are changed after the validation and before the creation of the new dates in some other thread.
Also the clone method is not used as Date can be subclassed and a subclassed Date cannot be trusted.
Similarly we need to change the start and end methods to:
    public Date start() {
        return start.clone();
    }
    public Date end() {
        return end.clone();
    }
This will ensure that no client will be able to change the start and end attributes of this class. Note that clone has been used here as we are sure that we are using the java.util.Date and not a untrusted subclass of Date.
This is very important if we are writing a library for use by a large audience over which we have no control.
Defensive copying can be avoided if we are working with a set of trusted clients.
Design Method Signatures carefully

The following should be kept in mind when writing methods:
1.       Choose method names carefully: A method name should convey to its user the exact operation it is performing. It should not be necessary to further explain it in the method comments. What will be required in the method comments will be to specify how it can be used and what the parameter constraints are.
2.       Do not go overboard and provide too many convenience methods: Too many makes understanding the class that much difficult and that much more difficult to test. Provide convenience calls only when it is expected that the method will be used frequently. If in doubt do not provide.
3.       Avoid long parameter list: It will be very difficult to use a method which has a large number of parameters even if the users have the benefit of an IDE which prompts for parameters. This is even more problematic if there is sequence of parameters of the same type. Consider creating a helper class or splitting the methods into multiple methods which requires lesser number of parameters.
4.        When a parameter being passed to a method has an interface define the parameter as the interface rather than the concrete class that implements that interface. E.g. if a list is expected as an input specify the parameter of type List rather than specifying it of type ArrayList.
5.       Use Function Objects judiciously: Function Objects have their place in software but when overused it can make understandability of the program very difficult. It is not be shunned but it is to be used only in situations where it makes sense and allows others to understand it easily.
Function Objects are Objects which contain only Functions and no attributes. These are typically used to handle callbacks in Java. E.g. a GUI event handlers, or a class that implements Comparator interface and implements only the compare method.
Use Overloading Judiciously

One needs to be careful when overloading functions and very clear. We need to keep the following rule when we use overloading “selection among overloaded methods is static (i.e. determined at compile time), while selection among overridden methods is dynamic”. So if we overload methods then the method to be executed will be determined at compile time and irrespective of the actual run time datatype the predetermined method will be invoked.
As far as possible avoid having overloaded methods with the same number of parameters. This makes overloading very safe. If one needs to write overloaded methods with the same number of parameters then the parameter types should be very different from each other to avoid confusion.
Return Zero-length Arrays and not nulls

If a method returns an Array and conditionally it returns a null then client that invokes this method needs to check for nulls before proceeding. And if some client were to forget checking for null we will end up with Null Pointer Exception.
Instead if we return a zero length array as the output then no client needs to check for the validity of the array and we can avoid Null Pointer Exceptions.
Note that in most case the clients would have a loop which would be looping through the array and doing some processing and this will be unaffected if one returns a zero-length array.
Write doc comments for all exposed API elements

It is important to document all the public methods and attributes of a class. This will help the users of the class use it in a much better way. It is advisable to also document the protected and private methods if one wishes the code to maintainable.
The method comments must have the following:
1.       What the method does. This should pretty much be clear from the name of the method but in certain scenarios it will help if there is some additional information provided. It should never mention how the method functions.
2.       Do not have two methods or constructors with the same summary description.
3.       It should mention all of the preconditions, i.e. all the conditions that should be true before the method can be invoked.
4.       It should mention all of the postconditions, i.e. all of the things that will be true after the method has been invoked.
5.       It should mention any side effects of invoking the method
6.       The thread safety of the method should be specified especially if it is not thread safe
7.       The purpose of the parameter along with its constraints should be specified
8.       All the exceptions that the method can throw should be documented and if possible with the condition under which it can be thrown
9.       Do not have comments for getters and setters unless something unusual is being done in the method.

In the next post we will read about best General Programming practices.

This is the fourth part in the series of posts based on the book Effective Java by Joshua Bloch. In the previous post we saw best practices of writing good classes and interfaces.

This section talks of how to convert C constructs to Java. The important section that everybody needs to look into is the Replace Enum constructs with Classes. This will apply to everybody trying to create an Enum.

Replace structures with Classes

The world of C has structures to represent complex data types. The equivalent of that in the Object Oriented world are classes. If a structure is translated to a class it will be a simple/trivial class in the Object Oriented world which will have only private attributes will potential getters and setters. Ideally one should not make the members public and use getters and setters as indicated above. Exception can be made if a class is private to a package or to another class the attributes may be made public as any change in the structure of the class will only have limited impact.

Replace unions with class hierarchies

In C it is possible to define Union which can hold more than one type of data or a set of bytes can be interpreted differently depending on the requirement. It is not possible to do this is Java in the same manner, i.e. no class can be used store different types of data at different points in time. Instead one should use a class hierarchy to represent unions. This will ensure that while we have a common interface using which we can access the set of classes each class can hold the different types of data. Some common data may be held in the common class.
Replace enum constructs with classes

Enums are used in C to represent constants values. It is also used to ensure that that only a right parameter value is passed to a function.
Concerting an enum of the type
typedef enum {SPADES, CLUBS, HEARTS, DIAMONDS} suits;
One way this can be implemented in Java is
public class PlayingCard {
    public static final int SUIT_SPADES = 1;
    public static final int SUIT_CLUBS = 2;
    public static final int SUIT_HEARDS = 3;
    public static final int SUIT_DIAMONDS = 4;
}
But in places where we need to pass a Suit Type to a function or get back a suit type we will end up sending an integer or getting back an integer. This does not ensure that we will get a right integer all the time. This problem can be avoided by using classes to define the suits.
E.g. we could create a class as follows:
public class Suit {
    private final String name;
    private Suit(String name) { this.name = name;}
    public static final Suit SPADES = new Suit(“Spades”);
    public static final Suit CLUBS = new Suit(“Clubs”);
    public static final Suit HEARTS = new Suit(“Hearts”);
    public static final Suit DIAMONDS = new Suit(“Diamonds”);
}
Since the constructor is private the developers cannot extend the class and cannot create instance of classes. Now in the places where we need to pass a Suit type or return a Suit type we can use this class. Also since there are only four instances of this class which we have defined we can be assured that we will never get a class other than one of these four. This also means that we can use the simple == operator for comparisons.
Replace Function Pointers with Classes and Interfaces

C has function pointers. This can be used to pass a comparator function to algorithms like qsort. But java does not support function pointers. The solution will involve defining an interface and then implementing a class that implements the interface. So for a qsort algorithm we can use the Comparable interface and define a class which defines the Comparable interface and defines the compare function which can be used by the qsort algorithm implementation.

In the next section we will see best practices in writing good methods in Java.

This is the third part of the series of posts based on the book Effective Java by Joshua Bloch. In the previous post we read about best practices in writing methods which are common to all classes.

This post is all about how to design classes and when and how to leverage the interfaces.

One of the suggestions is to use composition over hierarchy. This will definitely ruffle the feathers of many a hardcore Object Oriented followers. But after about 10 years and more in the Object Oriented world I tend to fully agree with the suggestion. It makes more sense to use composition over inheritance. Inheritance complicates more than it eases the problems. Composition helps keep the code base simple and uncomplicated.

Although the suggestions are basically for Java many of the suggestions applies also to the other Object Oriented Languages.

Minimize the accessibility of classes and members

In a module hide as much internal details as possible from the external world. Expose only that much as is required for the outside world to communicate with this module. This will ensure that that the module is isolated from the other modules interacting with it, making it easier to make changes to this without impacting the others.

Rules for good design of Class

1.       Make the classes and members as inaccessible as possible.
2.       All attributes other than constants (public static final) should be private to the class
3.       It is nearly always wrong to have public static final array field as the contents of the array are still modifiable. Ensure that objects referenced by public static final fields are immutable.

Favour Immutability

Whenever possible create immutable classes. This will ensure that they can be safely shared across multiple threads without worries about synchronization or race conditions.

Rules to make a class immutable

The make a class immutable the following rules need to be adhered to:
1.       Do not provide any method that can modify the object. Typically have only get methods and no set methods.
2.       Ensure that no method can be overridden. This will prevent the sub classes from breaking this rule.
3.       Make all fields final. This will ensure that one cannot change the value of the fields even from within the class.
4.       Make all fields private.
5.       Ensure exclusive access to any mutable components. If the class has to refer to a mutable class then ensure that this mutable class is never exposed outside of the class. If an instance of such an object needs to be returned from the class then make a copy and return the copy so that the original never changes.

Advantage

1.       Immutable classes need not ever be synchronized
2.       Their internals can be exposed without any problem

Disadvantage

1.       The only disadvantage of immutable class is that one needs to instantiate object everytime one needs the object with a different value. Creating too many objects can be a problem.

Best Practices

1.       Classes should be immutable unless there is a very good reason to make them mutable.
2.       If a class cannot be made immutable then the mutability should be limited.
3.       Constructors should create fully initialized objects with all of their invariants established
Favour composition over inheritance

A sub class depends on the implementation details of the super class. If the implementation of the super class changes the functionality of the sub-class is likely to be broken. Given this scenario it is good to go for composition rather than inheritance, except in cases where the class is meant to be sub-classed.

Some rules to follow

1.       Subclass only within a package so that a single set of developers has visibility into both the classes.
2.       Extend when the class is documented and specifically designed to be sub-classed.
3.       Add forwarding methods to expose the methods of the contained class with or without custom logic depending on requirement
Design and document for inheritance or prohibit it

If a class is expected to be a super class then document its methods properly otherwise prohibit inheritance by defining it as final.

Some rules to follow when creating sub-classable classes

1.       Constructors must not invoke overridable methods, directly or indirectly.
2.       Avoid implementing Serializable and Cloneable in a overridable class. If implemented neither clone or readObject may invoke an overridable method directly or indirectly.
3.       Make the readResolve or writeReplace methods protected instead of private so that they can be extended by the subclass if required.

Prefer Interfaces to Abstract Classes

Extending abstract class will have the same limitations as inheritance. As opposed to Abstract classes interfaces provide a lot of flexibility and abilities.

Advantages

1.       Existing classes can be easily retrofitted to implement a new interface
2.       Allow for nonhierarchical type framework
3.       Ideal for defining mixin. (A mixin is a type that allows a class to exhibit extra behavior in addition to its “primary type”. E.g. a class that implements Comparable states that it can be ordered).
4.       Interfaces enable safe, powerful functionality enhancements.
5.       It promotes loose coupling between modules.

Disadvantages

1.       The one disadvantage Interfaces have against the Abstract classes is that one can evolve abstract classes by adding concrete methods to the abstract class as and when required. This is not possible with interfaces as all the classes that implement the interface will now need to implement this method.
Use Interfaces only to define types

In short do not define an interfaces with no methods. Do not use interfaces which only define constants. It is confusing to have an Interface which does nothing.
Favour static member classes over nonstatic

A class defined within another class is a nested class. One can have one of the following types of nested  classes:
1.       static member classes
2.       nonstatic member classes
3.       anonymous classes
4.       local classes.
A static member class has access to all the members of the class in which it is declared including the private members.
Syntactically a non-static is same as the static class except for the absence of the static keyword. Every instance of a non-static class can refer to the instance of the enclosing class using the qualified this. One classic use-case of usage of non-static member class is Iterators in the Collection and Map classes.
If a member class does not require access to the enclosing instance then put the static modifier.
Anonymous classes are classes without name which are used for a particular call. Comparator is a good use-case where an anonymous class is created and passed as the comparator object to be used for sorting.
Local classes can be declared anywhere, where a local variable may be declared and they follow the same scope as a local variable. They have the characteristics of a non-static inner class and have access to the enclosing instance.

In the next post we will read about the best practices of converting C structures to Java.


Categories