Personally, I used to use final
always (these days, from-time-to-time) only to ensure variables are not being re-assigned; nothing more, nothing less.
The advantage of not reassigning variables is important to me actually, as it discourages the 'crutch' of temporary variables. This in turn encourages me to re-inspect my code for repetitive code (replacing them with inlined methods, or even to re-structure them) and eliminates potential bugs due to careless re-assignments.
The latter is more helpful in long do-it-all methods, but once broken down into smaller methods, it's easier to see the scope of variables and therefore also to less likely to re-assign them in the first place. Hence my personal preference to use them lesser now.
To address your concern about accepting mutable/immutable objects, my suggestion is to make one the default approach for your library/codebase, stick with that and then document the exception. In other words, if it's better code-wise to be passing immutable objects by default, then you can make that clear so from 'Getting Started'-kind of documentation to address this codebase-wide. You can then make use of @assylias's suggestion to document the methods to say something like:
/**
* This method works with mutable arguments as well.
*/
Or
/**
* The side-effect of this method modifies the mutable arguments.
*/
You can also play with your classes name by calling them MyImmutableType
(for example). I generally avoid this as their names don't 'roll well', but YMMV.
// this has to return a new MyImmutableType instance
public MyImmutableType doSomething(MyImmutableType input);
// this possibly mutates the mutable argument, which the caller will then use
public void doSomething(MyMutableType input);
Perhaps more importantly, final
your arguments' classes too so that if callers/users of your codebase decide to check your source code, then can also immediately tell that the class is immutable.
public final class Wrapper<T> {
private final int id;
private final T payload;
public Wrapper(int id, T payload) {
this.id = id;
this.payload = payload;
}
}
In conclusion, if I see a final
keyword in the method signature, all I know is that the developer is doing something right by not (potentially) reassigning method arguments within the method. It should not be relied upon to indicate whether arguments should/must be be immutable.