Blog-Archiv

Donnerstag, 6. Oktober 2016

Java Access Modifiers

It is stunning to see how many programmers simply ignore valuable language features like access modifiers. That's a pity. It takes some time to get familiar with them, but it pays, for sure.

The Java access modifiers are, in order of their encapsulation strength, descending, following reserved words:

  • private
  • using no modifier is called "default access", or "package-visible"
  • protected
  • public

They can be mixed with two more keywords that define the nature of a field, method or class more exactly:

  • final
  • static

Ignoring access modifiers means writing code that lacks robustness. You must make clear what of the class is public, what is to be inherited only, and what must not be seen from outside at all. Thus access modifiers should be means of expression of your daily programming work. Encapsulation is the target. And the target of encapsulation is to reduce complexity by exposing just what's needed to the outer world. Complexity is what makes our life hard.

You might use an IDE that provides the auto-complete action Ctrl-Space. This enables fast and safe programming, without reading thick books before using an API. When the authors of that API used access modifiers in a correct way, you will, in your auto-complete, see just methods that really can be called or overridden, not all the confusing internals. Thus access modifiers are a prerequisite for a good auto-complete functionality.

Any modern programming language must provide some means for encapsulation, else you wouldn't be able to implement big applications with it.

Private

If I would have designed the Java language, I would have made private the default access modifier. Stating nothing about access (default access) means "stay in context", and the context is mostly the class, not the package.

Make as much as possible private in your application, that way you hide complexity from the outer world. Private methods and fields can not be accessed by other classes, not even by sub-classes. When having a hierarchy of classes, e.g. Animal, Mammal extends Animal, Cat extends Mammal, each of these could have a private move() method implementation of its own, not overriding the super-class implementation. A private thing is usable only within its own class.

The private modifier can be used on methods, fields and inner classes, but not on top-level classes.

Final

As this modifier plays together with all of the following, I describe it here and now. A final field can not be modified any more after it got its value once. It is a constant, or an immutable field. Such fields must get their value directly at definition, in a static- or instance-initializer, or in a constructor. Try to use final on fields as much as possible, it will make your implementation more robust and easier to understand.

Methods that are final can not be overridden. Private methods need not to be set final, because they can not be overridden by nature. Referring to the anti-pattern DoNotCallOverridableMethodsInConstructor I would recommend to make final all methods that are called from a constructor.

Classes that are final can not be derived (extended to sub-classes). This is very useful for utility classes that contain just stateless static methods. Additionally declare a private constructor on them and nobody will be able to wrongly instantiate such a class.

(No Modifier)

The so-called default-access expresses "package-visibility". A method or field with no access modifier can be read and written by classes in same package only. It is not visible outside the package, even sub-classes can not see it when they are in a different package.

This access modifier is rarely used, but it makes sense when you want to encapsulate several top-level classes into a package, instead of using inner classes (because the outer class would get too big then). Typically you would have some package-visible classes, and one public facade-class that uses all of them.

Default-access can be used on methods, fields, inner classes and top-level classes.

Protected

This is for inheriting to sub-classes. In other words, you do not want that to be accessible by any class except a sub-classes. Unfortunately the Java protected modifier also allows read / write access by all classes in same package, but this mostly is not a problem.

While the final keyword does not make sense with the private modifier (is final automatically), it makes a lot of sense with protected methods. You should distinguish between protected methods that are made to be overridden, and such ones that are made to be called by sub-classes (utility methods). The last should be final. This makes life easier for developers that derive the class and wonder what they need to override now.

The protected modifier can be used on methods, fields and inner classes, but not on top-level classes. I would not recommend to make fields protected, you lose their encapsulation to sub-classes.

Public

Visible for the whole world. Fields that are public can be modified by everyone (avoid them). Methods that are public can be called by everyone, which may be what you want. Make them final when their overriding could break the "state" of the class. Classes that are public can be used by everyone, although you can restrict their instantiation by the access modifiers of their constructors.

Try to avoid public as much as possible. If all methods of a class are public, you missed encapsulation. Exceptions prove the rule, so anemic classes may have public on all methods.

The public modifier can be used on methods, fields, inner classes and top-level classes. Mind that you should never have public fields that are not final. Mutable non-final fields make up the "state" of an object, and thus are primary subjects for encapsulation. But public final fields are OK. You don't need to write getter-methods then. Just keep in mind that some reflection utilities might not be happy with that.

Static

This modifier binds to the class, not to the object-instance (created from that class). Fields that are static are the same for all instances of a class. Always make them final. A static final field is what is called constant in other programming languages.

Code in static methods is not reusable and overridable. Try to avoid that. Static methods always should be stateless, meaning the only fields they are allowed to use are their parameters.

Instances of static inner classes are not bound to an instance of the outer class. In other words, they do not have an implicit pointer to the enclosing object, like instances of non-static inner classes have. This may be a disadvantage in some cases, but in general it makes garbage collection easier, and that pointer to the outer instance may not be needed at all in many cases. So static inner classes are the preferable ones.

The static modifier makes no sense on top-level classes.


Shadowing

What happens when you have a class Animal, and a class Mammal extends Animal, and both define a protected field with name "mouth"? This is an ambiguity that you should avoid. Always make fields private. If sub-classes need to see them, create protected getter-methods for them, and try to avoid the parallel setter. Fields that are protected and not final can quickly get out of control.


My Recommendations

  • Try to minimize visibility by selectively using access modifiers; hiding complexity means preventing its abuse.
  • Fields always should be private; in any other case (default access, protected, public) they should be final.
  • Make all non-private methods called from a constructor final, to avoid initialization bugs from overrides.
  • Use default-access as much as possible for hiding things within the package that the outer world doesn't need to see.
  • Distinguish between protected methods that are to be overridden, and such that should be just callable by sub-classes (make the latter final).
  • Avoid static methods, their code is not reusable in an object-oriented way; consider using singletons instead.
  • Prefer static inner classes, they don't have possibly unneeded dependencies to the enclosing object.
  • Make classes that contain just static stateless utility methods final, and define a private do-nothing constructor on them; this makes a wrong instantiation impossible.



Keine Kommentare: