I currently am publishing a library for using swing with Processing. I declared almost everything in my library public with the motivation that if someone knows what they’re doing one can use these methods. However this has the effect that the javadoc is full of a few methods that aren’t supposed to be used when you don’t know what you’re doing.
Should I reduce the visibility of some methods so you need to access them with reflect or should I try to make clear they aren’t supposed to be used often?
I’d just publish it unabridged and let the end user make the decision to use them or not.
Excellent! I hate the private
keyword.
Please don’t! Reflect is such a slow boilerplate!
Actually there are 2 solutions for that!
Easiest 1 is to use comment tag @hidden
just before a member you want excluded from javadoc:
docs.Oracle.com/en/java/javase/11/docs/specs/doc-comment-spec.html#hidden
This way you can still have public
members w/o cluttering your generated doc.
However notice tag @hidden
was introduced in JDK 9.
So if you’re using JDK 8 to compile your library you still need a later version just to run javadoc.
- The other approach is to restrict members w/ keyword
protected
. - A
protected
member behaves similarly toprivate
restriction. - But it opens up an access exception inside another class which
extends
its class. - We can also access
protected
members inside a file w/ the samepackage
name as theirs. - Otherwise they behave as
private
members, not allowing direct access unless using reflection. - However javadoc includes both
public
&protected
members by default. - But it’s very easy to get around it by passing parameter “-public” in order to exclude
protected
members from its generated doc. - Also you can use both strategies, declaring less restricted members w/ comment tag
@hidden
and more restricted members w/ keywordprotected
in place ofprivate
. - But there’s still another caveat when using the
protected
keyword. - If some class happens to instantiate another class which has
protected
members in it, it’s always gonna do it on the original class, not from a user’s subclass. - That is, even if a user
extends
a class in order to access/modify itsprotected
members, the user can’t force the rest of your library to use that modified subclass. - In such cases you should create an extra call sig which allows a user to pass their subclass to it.
As an example, let’s say you have a class A which instantiates inside its constructor a class B:
“A.java”:
package pseudo.privated.members;
public class A {
/**
* @hidden
*/
public B b;
public A() {
b = new B();
}
}
However that class B contains a protected
method called protectedMethod():
“B.java”:
package pseudo.privated.members;
public class B {
protected String protectedMethod() {
return "protected: " + getClass().getSimpleName();
}
}
If a user desires to subclass class B in order to modify protectedMethod() it’s gonna be in vain, b/c your class A will always instantiate the original class B, never some subclass of it.
As a workaround the user could later reassign field b from class A w/ a modified class B of theirs.
However that’d feel like a poor hack, given field A::b is @hidden from javadoc.
A more streamlined solution would be to offer an alternative constructor signature which accepts a B subclass from the user:
“A.java”:
package pseudo.privated.members;
public class A {
/**
* @hidden
*/
public B b;
public A() {
this(null);
}
public A(final B subclass) {
b = subclass == null? new B() : subclass;
}
}
A regular user would simply instantiate class A using its 1st call signature.
On the other hand, an expert user would know how to use its 2nd signature.
Regardless, either class A instantiates its own class B or use the 1 passed to it.
You could even tag the alternative signature w/ @hidden so only experts would be aware of it:
/**
* @hidden
*/
public A(final B subclass) {
b = subclass == null? new B() : subclass;
}
Now I’m gonna leave you w/ my complete “Public Hidden Members” sketch example:
“Public_Hidden_Members.pde”:
/**
* Public Hidden Members (v1.0.0)
* GoToLoop (2022/May/27)
*
* https://Discourse.Processing.org/t/
* best-practice-regarding-visibility/37113/4
*/
import pseudo.privated.members.A;
A a;
void setup() {
println(a = new A()); // protected: B
println(a.b.hiddenMethod()); // hidden: B
println(a = new A(new BB())); // subclassed: BB
println(a.b.hiddenMethod()); // hidden: BB
exit();
}
import pseudo.privated.members.B;
class BB extends B {
@Override String protectedMethod() {
return "subclassed: " + getClass().getSimpleName();
}
}
“A.java”:
package pseudo.privated.members;
public class A {
/**
* @hidden
*/
public B b;
public A() {
this(null);
}
/**
* @hidden
*/
public A(final B subclass) {
b = subclass == null? new B() : subclass;
}
@Override public String toString() {
return b.protectedMethod();
}
}
“B.java”:
package pseudo.privated.members;
public class B {
/**
* @hidden
*/
public String hiddenMethod() {
return "hidden: " + getClass().getSimpleName();
}
protected String protectedMethod() {
return "protected: " + getClass().getSimpleName();
}
}
That’s nice but the problem is that you have no fine control over what the user might update / override.
Using private
, protected
and final
access specifiers do that and more. They help
- users avoid mistakes using the library
- the library creator trying to fix user errors when using parts of the library they shouldn’t
- prevent the user bypassing any validation that you might want to apply.
Here is an example of what I mean.
Start with our library class
package forum; // would be a library package name
public class LibraryClass {
protected int n = 0;
public LibraryClass() { }
public LibraryClass(int n) {
this.n = getValidN(n);
}
// private validation :- change number to positive
private int getValidN(int n) {
return Math.abs(n);
}
public void n(int n) {
this.n = getValidN(n);
}
public int n() {
return this.n;
}
public String toString() {
return this.getClass().getSimpleName()+ " { n = " + n + " }";
}
}
We can see that the attribute n
can only be positive. What if we wanted to modify the behaviour so n
can only be negative? Under no circumstances do we want to modify the library because it would adversely affect existing code that used the library. The answer is for the user to inherit from the LibraryClass
and override the private validation method like this
public class UserClass extends LibraryClass {
public UserClass(int n) {
super();
this.n = getValidN(n);
}
// private validation :- change number to negative
private int getValidN(int n) {
return -Math.abs(n);
}
// required to enable to ensure correct validity chack is applied
public void n(int n) {
this.n = getValidN(n);
}
public String toString() {
return this.getClass().getSimpleName()+ " { n = " + n + " }";
}
}
So in Processing we have
void setup() {
LibraryClass lc = new LibraryClass(-42);
println(lc);
LibraryClass uc = new UserClass(42);
println(uc);
}
Which produces the output
LibraryClass { n = 42 }
UserClass { n = -42 }
So we have an autonomous library class which we can inherit from to override private
access methods.
In other words we have an extensible, secure library which is surely a good thing