如何防止在同一个 class 中直接访问 getters/setters 之外的私有成员?
How to prevent direct access to private members outside of getters/setters in the same class?
挑战
给定一个具有重要 getter 的 class 和访问相同 private
成员字段的其他一些内部方法:
有效示例
class TestType {
private String value;
public String getValue() {
return this.value == null || this.value.isEmpty() ? "default" : this.value;
}
public void setValue(String newValue) {
this.value = newValue;
}
public int getValueLength() {
return this.getValue().length();
}
}
我想禁止绕过 getValueLength()
方法中的 getter 。
getValueLength()
的以下实现应该会产生某种错误(单元测试失败、checkstyle 错误或任何其他可以以通用方式自动执行的错误):
无效方法
public int getValueLength() {
// ERROR: possible NullPointerException
return this.value.length();
}
背景
在我的实际代码中,getter 内部进行了一些延迟加载,仅在首次访问时才加载实际值。首先调用getValueLength()
可能会直接导致NullPointerException
或者根据缺失值执行错误操作。
这样做的目的是为了确保未来的开发人员不会忘记只使用 getter 方法并且永远不要直接访问成员。由于这是一个孤立的问题(即只有在专门添加这种延迟加载的情况下),如果需要一些额外的注释是可以接受的,例如在成员本身上,它应该只在选定的方法中访问,并且那些方法也被标记——或特定的 checkstyle 注释 enabling/disabling 特定规则。
无法保护成员免受其自身 class 的侵害,但您可以保护成员免受其 children/subclasses 的侵害。例如:
class TestParent {
private String value;
public String getValue() {
return this.value == null || this.value.isEmpty() ? "default" : this.value;
}
public void setValue(String newValue) {
this.value = newValue;
}
}
class TestType extends TestParent {
public int getValueLength() {
return this.getValue().length();
}
}
这样 TestType 仍然可以使用 setValue()
和 getValue()
,但是尝试直接访问 this.value
将不起作用,因为 value
在 TestParent
中具有私有访问权限]
只是把这个答案写成表格:
class LoadableVar<T> {
private T val;
private Supplier<T> loader;
public LoadableVar(Supplier<? extends T> loader) {
this.loader = loader;
}
public T get() {
if (this.val == null) {
//see: volatile and double-locking if multithreading
this.val = this.loader.get();
}
return this.val;
}
public void set(T overwrite) { //WARN: ignores the loader!
this.val = overwrite;
}
}
然后,将其应用于您的 class:
class TestType {
private final LoadableVar<String> value;
public TestType() {
//can also be passed into the class, or done however you desire
this.value = new LoadableVar<>(() -> /* load string from i/o, etc */);
}
public String getValue() {
return this.value.get();
}
public void setValue(String newValue) {
this.value.set(newValue); //I don't think this should be settable, personally
}
public int getValueLength() {
return this.getValue().length();
}
}
现在,当您在 TestType 的范围内编写代码时:
String s;
s = this.value; //compile error!
s = this.getValue(); //OK
s = this.value.get(); //OK
如您所见,这样做也使 TestType#getValue
变得多余,您可以简单地允许 value
成为 protected
成员(在这种情况下,我会删除那些设置器并使其不可变)。
无法在代码中强制执行此操作。
但是,由于您在评论中提到“希望有某种方法可以通过某种工具实现它”,并且该问题带有 checkstyle 标记,我相信您可以创建自定义规则:
<module name="Regexp">
<property name="id" value="valueField"/>
<property name="format" value="\bvalue\b"/>
<property name="illegalPattern" value="true"/>
<property name="ignoreComments" value="true"/>
<property name="message" value="Do not use 'value' field directly; use getValue() instead."/>
</module>
然后您可能想要取消对您自己的“有效”行的检查:
<module name="SuppressWithNearbyCommentFilter">
<property name="idFormat" value="valueField"/>
</module>
并且在代码中:
private String value; // SUPPRESS CHECKSTYLE
public String getValue() {
return this.value == null || this.value.isEmpty() ? "default" : this.value; // SUPPRESS CHECKSTYLE
}
public void setValue(String newValue) {
this.value = newValue; // SUPPRESS CHECKSTYLE
}
(还有其他方法可以通过注释抑制 Checkstyle 的检查,您可能会发现这些方法在视觉上更令人愉悦。请参阅 filters 文档。)
实际上 运行 Checkstyle 时,您可能希望将其限制为那个源文件。例如,如果使用 Ant,您可以这样做:
<taskdef resource="com/puppycrawl/tools/checkstyle/ant/checkstyle-ant-task.properties"
classpath="tools/checkstyle-8.33-all.jar"/>
<checkstyle config="checkstyle.xml">
<fileset dir="src/main/java" includes="**/TestType.java"/>
</checkstyle>
挑战
给定一个具有重要 getter 的 class 和访问相同 private
成员字段的其他一些内部方法:
有效示例
class TestType {
private String value;
public String getValue() {
return this.value == null || this.value.isEmpty() ? "default" : this.value;
}
public void setValue(String newValue) {
this.value = newValue;
}
public int getValueLength() {
return this.getValue().length();
}
}
我想禁止绕过 getValueLength()
方法中的 getter 。
getValueLength()
的以下实现应该会产生某种错误(单元测试失败、checkstyle 错误或任何其他可以以通用方式自动执行的错误):
无效方法
public int getValueLength() {
// ERROR: possible NullPointerException
return this.value.length();
}
背景
在我的实际代码中,getter 内部进行了一些延迟加载,仅在首次访问时才加载实际值。首先调用getValueLength()
可能会直接导致NullPointerException
或者根据缺失值执行错误操作。
这样做的目的是为了确保未来的开发人员不会忘记只使用 getter 方法并且永远不要直接访问成员。由于这是一个孤立的问题(即只有在专门添加这种延迟加载的情况下),如果需要一些额外的注释是可以接受的,例如在成员本身上,它应该只在选定的方法中访问,并且那些方法也被标记——或特定的 checkstyle 注释 enabling/disabling 特定规则。
无法保护成员免受其自身 class 的侵害,但您可以保护成员免受其 children/subclasses 的侵害。例如:
class TestParent {
private String value;
public String getValue() {
return this.value == null || this.value.isEmpty() ? "default" : this.value;
}
public void setValue(String newValue) {
this.value = newValue;
}
}
class TestType extends TestParent {
public int getValueLength() {
return this.getValue().length();
}
}
这样 TestType 仍然可以使用 setValue()
和 getValue()
,但是尝试直接访问 this.value
将不起作用,因为 value
在 TestParent
中具有私有访问权限]
只是把这个答案写成表格:
class LoadableVar<T> {
private T val;
private Supplier<T> loader;
public LoadableVar(Supplier<? extends T> loader) {
this.loader = loader;
}
public T get() {
if (this.val == null) {
//see: volatile and double-locking if multithreading
this.val = this.loader.get();
}
return this.val;
}
public void set(T overwrite) { //WARN: ignores the loader!
this.val = overwrite;
}
}
然后,将其应用于您的 class:
class TestType {
private final LoadableVar<String> value;
public TestType() {
//can also be passed into the class, or done however you desire
this.value = new LoadableVar<>(() -> /* load string from i/o, etc */);
}
public String getValue() {
return this.value.get();
}
public void setValue(String newValue) {
this.value.set(newValue); //I don't think this should be settable, personally
}
public int getValueLength() {
return this.getValue().length();
}
}
现在,当您在 TestType 的范围内编写代码时:
String s;
s = this.value; //compile error!
s = this.getValue(); //OK
s = this.value.get(); //OK
如您所见,这样做也使 TestType#getValue
变得多余,您可以简单地允许 value
成为 protected
成员(在这种情况下,我会删除那些设置器并使其不可变)。
无法在代码中强制执行此操作。
但是,由于您在评论中提到“希望有某种方法可以通过某种工具实现它”,并且该问题带有 checkstyle 标记,我相信您可以创建自定义规则:
<module name="Regexp">
<property name="id" value="valueField"/>
<property name="format" value="\bvalue\b"/>
<property name="illegalPattern" value="true"/>
<property name="ignoreComments" value="true"/>
<property name="message" value="Do not use 'value' field directly; use getValue() instead."/>
</module>
然后您可能想要取消对您自己的“有效”行的检查:
<module name="SuppressWithNearbyCommentFilter">
<property name="idFormat" value="valueField"/>
</module>
并且在代码中:
private String value; // SUPPRESS CHECKSTYLE
public String getValue() {
return this.value == null || this.value.isEmpty() ? "default" : this.value; // SUPPRESS CHECKSTYLE
}
public void setValue(String newValue) {
this.value = newValue; // SUPPRESS CHECKSTYLE
}
(还有其他方法可以通过注释抑制 Checkstyle 的检查,您可能会发现这些方法在视觉上更令人愉悦。请参阅 filters 文档。)
实际上 运行 Checkstyle 时,您可能希望将其限制为那个源文件。例如,如果使用 Ant,您可以这样做:
<taskdef resource="com/puppycrawl/tools/checkstyle/ant/checkstyle-ant-task.properties"
classpath="tools/checkstyle-8.33-all.jar"/>
<checkstyle config="checkstyle.xml">
<fileset dir="src/main/java" includes="**/TestType.java"/>
</checkstyle>