Type Design Checklist, Classes
2.3.1. Minimize implementation dependencies
Details coming soon…
2.3.2. List public methods first
Rationale:
Simplicity. Implementation details are of no interest to callers. It is easier to ignore private or protected methods and fields if public methods are listed first in source code. The practice of listing (private) fields first, while widespread in implementation code, should not be used in public APIs.
2.3.3. Declare implementation methods private
Details coming soon…
2.3.4. Define at least one public concrete class which extends a public abstract class
Rationale:
For the same reasons why you should provide at least one concrete public implementing class for every interface. See item.2.4.1
2.3.5. Provide adequate defaults for the basic usage scenarios
Details coming soon…
2.3.6. Design classes with strong invariants
Details coming soon…
2.3.7. Group stateless, accessor and mutator methods together
Details coming soon…
2.3.8. Keep the number of mutator methods at a minimum
Details coming soon…
2.3.9. Consider providing a default no-parameter constructor
Rationale:
Simplicity. The simplest way to instantiate types in Java is with a no-parameter default constructor. Developers often prefer to instantiate and experiment with individual types before wiring them together. The ability to defer connecting types is also useful in scenarios like lazy initialization, reflection, custom serialization, persisting objects in relational databases, or caching. Constructors which take instances of other types as parameters have the disadvantage of imposing a strict ordering on type instantiation and can make certain scenarios harder to implement.
Exceptions:
Default constructors may be unnecessary if you need to set all the missing parameter value(s) before you can do anything else with the class. This shows that there is a strong dependency between the types by design and adding a default constructor does not eliminate it. On the contrary, it may be better to make the existence of such dependencies obvious by not providing a default constructor. For situations where you should not provide any constructors see item 2.3.15.
2.3.10. Consider overriding equals(), hashCode() and toString()
Rationale:
Behavior. Every class inherits these methods from java.lang.Object and callers expect the behavior to match to intended use of the class. Not surprisingly, the inherited behavior is often different from what callers expect. Consider the java.net.URL class as an example. Callers expect toString() to return the string representation of the URL, not the default combination of class name and instance identifier. Callers would be also quite puzzled if two instances of the URL class pointing to the same resource were not equal to each other, which requires equals() to be overridden. And every time equals() is overriden, hashCode() should be overriden as well to ensure consistent behavior. Forgetting to override these inherited methods is a common API design mistake.
2.3.11. Consider implementing Comparable
Rationale:
Simplicity. The Comparable interface makes it simpler for callers to order (sort) instances of a class. Objects which implement Comparator can be stored in ordered Java collections like SortedMap or SortedSet without providing a custom Comparator. From the perspective of the caller, there are no disadvantages of implementing Comparable; every class for which a meaningful natural (default) ordering exist can implement it.
Do this:
import com.company.product.api.ApiClass; import java.util.*; //Code fragment if ApiClass implements Comparable List<ApiClass> list = new ArrayList<ApiClass>(); list.add(new ApiClass("B")); list.add(new ApiClass("A")); list.add(new ApiClass("D")); list.add(new ApiClass("C")); Collections.sort(list); //list ordered A, B, C, D
Don’t do this:
import com.company.product.api.ApiClass; import java.util.*; //Needed if ApiClass does not implement Comparable class ClientDefinedApiClassComparator implements Comparator<ApiClass> { public int compare(ApiClass o1, ApiClass o2) { String s1 = o1.getName().toUpperCase(); String s2 = o2.getName().toUpperCase(); return s1.compareTo(s2); } } //Code fragment if ApiClass does not implement Comparable List<ApiClass> list = new ArrayList<ApiClass>(); list.add(new ApiClass("B")); list.add(new ApiClass("A")); list.add(new ApiClass("D")); list.add(new ApiClass("C")); Collections.sort(list, new ClientDefinedApiClassComparator()); //list ordered A, B, C,
2.3.12. Consider implementing Serializable
Rationale:
Simplicity. Java serialization is useful for persisting class instances or transferring them across process and class loader boundaries using Java RMI. From the perspective of the caller, there are no disadvantages of implementing java.io.Serializable, even if you don’t anticipate the need for it. If you implement Serializable, always make sure (test) that all the class’ internal state is serialized correctly.
Do this:
import com.company.product.api.ApiClass; import java.rmi.RemoteException; //Code fragment when ApiClass implements Serializable try { ApiClass result = clientDefinedRemoteCall(); result.apiMethod(); //and so on } catch (RemoteException e){}
Don’t do this:
import com.company.product.api.ApiClass; import java.rmi.RemoteException; /Needed if ApiClass does not implement Serializable class ClientDefinedSerializableClass implements Serializable { private Serializable field1; private Serializable field2; public ClientDefinedSerializableClass(ApiClass a) { field1 = a.getField1(); field2 = a.getField2(); } public ApiClass toApiClass() { return new ApiClass(field1, field2); } } //Code fragment when ApiClass does not implement Serializable try { ClientDefinedSerializableClass remoteResult = clientDefinedRemoteCall(); ApiClass result = remoteResult.toApiClass(); result.apiMethod(); //and so on } catch (RemoteException e){}
2.3.13. Consider making classes re-entrant
Details coming soon…
2.3.14. Consider declaring the class as final
Rationale:
Safety. You should either explicitly design for client extension or prevent it by declaring classes as final. Safe extension requires careful design: you need to think about which methods should be protected and which ones private; you need to think about which methods can be overridden and which ones cannot; you need to add additional error handling code to deal with the errors which may only appear in client implementations; you need to write additional tests. If you are not willing to spend the effort, ensure the safety of the API by declaring the class as final.
2.3.15. Consider preventing class instantiation by not providing a public constructor
Rationale:
Simplicity, Safety, and Behavior. From the perspective of the caller constructors are only useful when instances created are at least partially functional. For example, there is no point in creating instances of java.sql.ResultSet representing the result of SQL queries without executing the queries themselves. You should indicate that properly initialized instances can be only obtained using the appropriate method calls by declaring the constructors package scoped, thus making them inaccessible to callers.
2.3.16. Consider using custom types to enforce strong preconditions as class invariants
Details coming soon…
2.3.17. Consider designing immutable classes
Rationale:
Behavior and Safety. Instances of immutable classes cannot be modified. The internal state is fixed for the lifetime of the object. The core Java library contains several immutable classes, including String and BigInteger. Immutable classes are easier to use and less error prone than mutable classes. For example, immutable objects require no synchronization, being inherently thread safe. You can also share immutable instances between client code and implementation code, meaning that you can safely store immutable object references passed as method parameters and you can safely return references to internal immutable objects. If you are using mutable objects, you need to store and/or return a copy; otherwise the client may inadvertently change the state of the internal object. Compare for example the Java String (immutable) and Date (mutable) classes.
You make a class immutable by removing all methods which can change the internal state (for example, all set methods) and by declaring all internal fields as final. Instead of changing the class itself, methods should return a new instance of the class when required. There are obvious performance implications of doing this, so you won’t be able to make all API classes immutable. However, it is a good rule of thumb to make API classes immutable unless there is a good reason why you should make them mutable. Even when you cannot make the whole class immutable, it is useful to make as much of the internal state immutable as possible.
Do this:
import com.company.product.api.ApiClass; public class ClientClass { private ApiClass clientField; public ApiClass getClientField() { return clientField; //Safe, ApiClass immutable } public void getClientField(ApiClass value) { clientField = value; //Safe, ApiClass immutable } }
Don’t do this:
import com.company.product.api.ApiClass; public class ClientClass { private ApiClass clientField; public ApiClass getClientField() { return new ApiClass(clientField); //Copy needed, ApiClass mutable } public void setClientField(ApiClass value) { clientField = new ApiClass(value); //Copy needed, ApiClass mutable } }
2.3.18. Avoid static classes
Details coming soon…
2.3.19. Avoid using Cloneable
Details coming soon…
2.3.20. Do not add instance members to static classes
Details coming soon…
2.3.21. Do not define public constructors for public abstract classes clients should not extend
Rationale:
Safety. Concrete API classes not designed for extensions should be declared as final to prevent clients from extending them (see item 2.3.14). Certain abstract API classes aren’t designed for client extension either (see item 2.1.8), but in Java it is illegal to declare a class both abstract and final. Fortunately, there is another way for preventing clients from extending abstract classes: by making all constructors package scoped. While clients can work around this technical limitation by placing their own classes in the API package, it is enough to make it clear that what they are doing is unsupported and unsafe.
2.3.22. Do not require extensive initialization
Details coming soon…
This work is licensed under a Creative Commons Attribution-ShareAlike 2.5 Canada License.