Generally, any class or member of a class should have the lowest possible access level, which still allows for its functionality to work.
Take the case of an externally exposed API, where the interface represents all public accessible methods. Any implementation of this interface should only have public access level on the implemented methods.
The additional members of the implementing class should never be made public. Ideally private or package-private if required for unit testing.
In the case of the exposed API, any publicly available member of the implementation will have to be supported forever, if they have once been published. If members are degraded to a lower access level, it will ruin the API’s backwards compatibility.
If a class is publicly exposed, no fields should be made public. Instead they should be accessed through getter-methods.
Making the fields retrievable through getter-methods, allows for alterations within the same public method, whereas a direct access to a field cannot be changed.
Immutable objects require five rules to be followed:
Essentially you should always strive to use independent object compositions instead of inheriting unless you really plan for it. (see 19.)
Inheritance creates fragile architecture, since subclasses must rely on the functionalities of the superclass.
It can be difficult to gain an overview of the change’s effects of a superclass, when multiple subclasses extend it.
Many design patterns are for this reason based on composition. (e.g. decorator, strategy, and composite)
If you plan to create a inheriting class structure, it is highly recommended to plan out the intended use cases and document the proper way of utilizing the extendable class.
Which methods should be available for overriding? How are we making sure that none of the overridable methods are not being called within the constructors? Do all overridable method have the proper documentation.
Both interfaces and abstract classes are designed to enforce method implementation on the classes which inherit from them.
However, abstract classes are more constraint since Java only allows for one class extension. Whereas for interfaces, multiple can easily be implemented on the same class.
Since Java 8, the default method has been introduced, and with it all the possible scenarios which could go wrong.
The default method is intended to define a concrete method for all implementations of the interface to use.
However, unlike the regular interface method, which are defined by the implementation, the default method controls how and what should happen within all its implementations.
This might never become a compile error, but it might create runtime errors. This is because it will be difficult
to manage how all the implementations handle the functionality of the default method.
Similar to what have been said in item 21, interfaces have many possible ways of implementing functionalities or fields into all inheriting classes. (e.g. default methods and constant fields)
Regardless of these functionalities, it is still recommended to view an interface as a type declaration for the implementing class.
For example: all classes implementing a user-interface, can be considered a variation of a user. (e.g. admins, customers, editors etc.)
Thereby promising that any class implementing a given interface will also have the same methods.
Methods inside an implementation can be changed over time. Whereas, constant fields and default methods cannot after they once have been published to the public.
A tag-class is a class which serves multiple purposes based on which constructor is invoked or what arguments it is given. (e.g. a shape-class, could both be invoked as a circle or square)
Regardless of how you want to avoid this (composition or inheritance), actions should be taken, so that you never end up in a scenario where a class can serve multiple purposes.
Whenever you create a class member, which does not require any reference to the instance’s other members, it should be made static.
Static methods ensures that you do not need to make a new instance of the member’s class whenever you want to use it.
Within any source file in Java, it is allowed, by the compiler to have more then one top-level class. (aka. non-inner-classes)
However, regardless of this being possible, it is highly not recommended to do so, since it only provides
If you need two classes, make two source files.
My name is Daniel H. Jacobsen and I’m a dedicated and highly motivated software developer with a masters engineering degree within the field of ICT.
I have through many years of constantly learning and adapting to new challenges, gained a well-rounded understanding of what it takes to stay up to date with new technologies, tools and utilities.
The purpose of this blog is to share both my learnings and knowledge with other likeminded developers as well as illustrating how these topics can be taught in a different and alternative manner.
If you like the idea of that, I would encourage you to sign up for the newsletter.