如何创建一个抽象的、不可变的类?

22

以下是简短版。首先:我希望我的类是不可变的。我知道一个类不能同时是抽象和最终的。我的问题是:是否有一种方式只允许内部类扩展和实现外部抽象类?这可能不是达到我的不可变目标的最佳方法,所以如果有人有更好的设计,我很乐意听取建议。

我正在编写用于向量操作的类,例如在物理和工程中使用,而不是编程中的向量操作。(我也知道JScience有一个包 可以处理这种东西。我想自己写,并保持简单。)

我喜欢Java几何包中使用的模式,例如可以使用两种精度级别之一(float或double)创建Line2D

public abstract class Line2D {
    public static class Float extends Line2D { /* Implementation */ }
    public static class Double extends Line2D { /* Implementation */ }
}

这是我非常希望在我的课程中加入并扩展的一个特性,因此我创建了以下内容:

public abstract class Vector2D {

    public final static class Integer extends Vector2D {

        // Component length projected along x/y axis, respectively
        final private int i, j;

        public Integer( int i, int j ) {
            this.i = i;
            this.j = j;
        }

        public Object doStuff() { /* vector operations */ }
    }

    public final static class Float extends Vector2D {
        // Identical to Vector2D.Integer, except with floats
    }

    public final static class Double extends Vector2D { /* Same Idea */ }

    // Outer class methods and whatnot
}

很明显,Vector2D.IntegerVector2D.FloatVector2D.Double都是final类型。有没有办法使Vector2D对这些内部类以外的所有内容都是final的呢?


顺便问一下,你不能用泛型来完成吗?大多数操作都可以使用它们,并且在Java中编写Vector<Integer>比Vector.Integer更符合语言习惯...那么就不需要进行所有这些强制转换了... - Falco
1
@Falco 根据性能要求,装箱/拆箱转换可能(仍然)过于昂贵(最后但并非最不重要的原因是由数百万个“Double”实例所暗示的潜在垃圾)。例如,在Java 8中,Stream的特殊化为DoubleStream也有其原因... - Marco13
2个回答

25

有没有一种方法只允许内部类继承和实现外部抽象类?

是的,将外部类的构造函数设为私有。

例如:

abstract class MyAbstractClass {
    int i; // some property
    private MyAbstractClass(int i) {
        this.i = i;
    }

    public static class InnerConcrete extends MyAbstractClass {
        int j; // some other property
        public InnerConcrete(int i, int j) {
            super(i);
            this.j = j;
        }
    }
}

我认为我从未遇到过这种方法。工厂模式可能更灵活,可以将原本可能很大的类拆分成几个文件。抽象类的包访问级别可能也足够了。


我忘记了这种方法。但是这不会导致奇怪的设计吗? - Luiggi Mendoza
@LuiggiMendoza,你说的“odd”是什么意思? - aioobe
2
让我确认我是否正确理解了这个。由于外部类仍然是公共的非最终类,另一个类在理论上可以扩展它。然而,由于无法从外部世界调用超级构造函数,因此它实际上是最终类? - drmuelr
2
@LuiggiMendoza,我猜OP的意图是在设计抽象类时避免考虑到未预料到的继承。《Effective Java》中的第15条建议是为继承而设计和文档化否则禁止它 - aioobe
@aioobe 我从没见过这种方法。 它被用于实现标签联合/代数数据类型,目的是创建一个有限的备选项集,您可以使用接受lambda或visitor的调度函数进行切换。我听说Scala的case classes就是这样实现的。 - Doval
显示剩余2条评论

10
有没有一种方法只允许内部类扩展和实现外部的抽象类?
我建议采用另一个选择:将您的抽象类设置为非公共类,并仅使公共的final实现类可见。这就是AbstractStringBuilder的情况,它属于java.lang包,是abstract且非public,并由StringBuilderStringBuffer类实现,它们是public final
以下是这些类的相关源代码:
abstract class AbstractStringBuilder implements Appendable, CharSequence {
    //implementation details...
}

public final class StringBuilder
    extends AbstractStringBuilder
    implements java.io.Serializable, CharSequence {
    //implementation details...
}

public final class StringBuffer
    extends AbstractStringBuilder
    implements java.io.Serializable, CharSequence {
    //implementation details...
}

如果我做的事情略有不同,这可能很好。然而,我只选择了内部类,因为它们实际上都是相同的,并且做着相同的事情,只是精度不同。将它们拆分成单独的类会增加更多的复杂性,而这并不值得,在这种情况下。除此之外,这是一个很好的解决方案。 - drmuelr

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接