Java 使类不可变的另一种方法
我在读有效的java第三版。主题是 项目17:最大限度地减少相互性 在本主题中,将讨论不可变对象,以及不使类成为最终类但仍然是不可变的替代方法 以下是主题中的内容: 回想一下,为了保证不变性,类不能允许自己被子类化。这可以通过期末考试来完成,但还有另一个更灵活的选择。private或package private并添加公共静态工厂来代替公共构造函数 我可以同意将构造函数设为私有,这样会使类无法被子类化。但是带有包私有构造函数的类可以在包中使用子类。那么,这个类是否仍然可以使用包私有构造函数保持不变 编辑1: 具有包私有构造函数的类对于包外部的类仍然是不可变的。但这种方法有用吗Java 使类不可变的另一种方法,java,immutability,Java,Immutability,我在读有效的java第三版。主题是 项目17:最大限度地减少相互性 在本主题中,将讨论不可变对象,以及不使类成为最终类但仍然是不可变的替代方法 以下是主题中的内容: 回想一下,为了保证不变性,类不能允许自己被子类化。这可以通过期末考试来完成,但还有另一个更灵活的选择。private或package private并添加公共静态工厂来代替公共构造函数 我可以同意将构造函数设为私有,这样会使类无法被子类化。但是带有包私有构造函数的类可以在包中使用子类。那么,这个类是否仍然可以使用包私有构造函数保持不
public class BaseClass {
private final String arg1;
private final String arg2;
public BaseClass(final String arg1, final String arg2) {
this.arg1 = arg1;
this.arg2 = arg2;
}
public String getArg1() { return arg1; }
public String getArg2() { return arg2; }
}
可以对此类进行子类化,但必须始终提供arg1
和arg2
的值,并且不能更改。显然,通过对它进行子类化,您可以覆盖两个getter
方法
为了避免我和同事可能犯的错误,我的方法是对接口进行编程
在整个代码库中使用此接口将确保减少错误数量,因为内部状态可能仅在创建点更改。一旦对象脱离了创建点,就不能再对其进行更改(当然可以,但您必须手动向下转换它,这很容易发现)
另一种避免子类化(以及可能的易变性)的策略是使用注释处理器。这将在编译时强制执行您的规则。拥有包专用构造函数并不能保证100%的不变性,正如注释中已经提到的:
可以在同一个包中创建可变子类
开发jar
库您可能会在团队和软件包维护人员之间达成一些协议,但是couse没有以编程方式实施这一点,但仍然在产品开发人员的控制之下
其他人呢?可以在库之外创建类,但可以在同一个包中以及类路径中的多个位置创建类
如果你有这样的情况,并且关心这样的问题,就开始发挥作用
JAR文件中的包可以选择密封,这意味着
该包中定义的所有类都必须归档在同一个JAR中
文件例如,您可能希望密封一个包,以确保
软件中类之间的一致性
如果要保证包中的所有类都来自
相同的源代码,使用JAR密封。密封的JAR指定所有
该JAR定义的包是密封的,除非在
以每包为基础
假设您的jar
libarary有一个类com.example.Factory
和protected/package private
成员,包com.example
是密封的。创建试图访问这些成员的类com.example.Accessor
的尝试应失败
这就是如何确保有限(且可能受信任)的成员组访问包私有
成员的方法
回到主要问题:
那么,这个类是否仍然可以使用包私有构造函数保持不变
由于维护人员之间达成了强有力的协议,仔细的代码审查和包密封,它可能会变得不可更改。不一定会这样,不可以。可以在同一个包中创建一个可变子类。如果包维护者主要关心包+1的用户不进行父级化,那么他们可能会认为这是一个足够好的不变性实施。另一方面:immutable for me在某种程度上与将某物放入容器并向用户发出检索这些东西:“停止,除非你确切知道你在做什么,否则不要改变这个东西。”我认为你忽略了这种情况,只解释子类化。“你可以维护一个类不可变,同时允许子类化。”如果你想保留大多数用户认为最有用的不可变特性,就不能这样做。如果我读到一个类是不可变的,我通常希望能够假设它的状态没有任何方面会改变。我可以缓存它,只要我喜欢,它会很好。如果创建了一个可变的子类,状态可以在我的代码脚下更改-即使它是我从未使用过的类的一个方面,如果我传递对对象的引用,希望它是不可变的,这可能会导致问题。@JonSkeet你是对的。我指定了子类化时可以更改的行为(但不是数据)。这就是为什么我还编写了“程序到接口”,以便随着时间的推移减少错误的数量。如果代码不是您的(可能来自另一个库),那么注释处理器的想法也不错,可以强制执行特定的规则。如果您甚至没有最终确定getArg1()
方法,那么对于这些方法,它甚至不是不可变的。这不是我认为不可变的,而且它肯定没有保留不可变的许多好处。编程到接口是很好的,但这是另一种情况,在这种情况下,你真的不能保证不变性——而这通常是一件非常有用的事情。问题是关于不变性的——你建议的选项不能保证不变性。@JonSkeet当然!那么,我将不得不从答案中删除“不可变”一词,因为我可能误用了它。
public interface BaseInterface {
String arg1();
String arg2();
}