Java中与C++的const成员函数等效的是什么?

33
在C++中,我可以定义一个访问器成员函数来返回私有数据成员的值(或引用),以便调用者无法以任何方式修改该私有数据成员。
那么在Java中有没有办法做到这一点呢?
如果有,怎么做?
我知道有关final关键字,但据我所知,当应用于方法时,它:
1. 防止在子类中覆盖/多态化该方法。 2. 使该方法可内联。(请参阅下面@Joachim Sauer的评论)
但它不会限制该方法从返回对数据成员的引用,以便调用方不能修改它。
我是否忽视了一些显而易见的东西?

1
你的第二点是错误的:Java 没有指定内联,这种优化完全由 JVM 决定,并且 final 对于 JVM 内联方法并不是必需的。 - Joachim Sauer
@Joachim Sauer 感谢您指出这一点。我感到困惑。以下网址http://www.roseindia.net/javatutorials/final_methods.shtml则说明了相反的情况。他错了吗? - ef2011
2
是的,这是错误的。Roseindia.net 是一个资源,其质量通常很差。 它的网站从稍微有误导性到完全错误不等。 static final 字段如果使用常量表达式进行初始化(称为“编译时常量”),则可以内联,但 Java 中没有指定任何方法内联。 它是 JVM 在运行时的可能运行时优化,但不能影响观察到的行为(这对于 final 方法更容易实现,但也适用于非 final 方法)。 - Joachim Sauer
@Joachim Sauer 谢谢。+2,我会编辑我的原始帖子以反映您的更正。 - ef2011
1
由于我在网上找到了一些令人困惑的信息,所以我必须部分更正自己的话:至少有一个编译器实际上可以内联方法。通常我的说法仍然正确:普通的Java编译器不会内联方法,而是将这个决定留给运行时。 - Joachim Sauer
9个回答

24

Java中没有类似于C语言const的"类型修饰符"(可悲)。

你唯一能做的就是返回一个不可变对象或一个不可变包装器来包装可变对象。

不过,在Java中,不可变性不是一种语言特性,你必须依赖库来实现它。

一些不可变对象的例子包括:

  • 基本类型的包装类如 Integer, Character, ..
  • String
  • File
  • URL

常用的不可变包装器(即对可变类型进行包装以防止其改变)是由Collections.unmodifiable*()方法返回的。


4
我希望Java有两个功能:一个与“const”等效的功能,另一个是声明对象为不可变的方法,以便编译器可以检测潜在的违规操作。 注释 可能至少有助于实现后者。 - Ben
注意,这与“const方法”不同。我仍然可以在获取不可变数据的同时修改对象状态。 - JustAnotherCoder

8

这在Java中不存在。finalconst有不同的语义,除非应用于原始类型的变量。Java的解决方案通常涉及创建不可变类 - 在构造中初始化对象并不提供访问器以允许更改。这样的类的示例是例如StringInteger


5

您可以返回一个不可变对象,或者返回私有实例变量的副本。这样,对象的内部状态就可以被“保护”不受修改的影响。

private MyMutableObject mutable = ...

public MyMutableObject getMutableObject() {
   return new MyMutableObject(this.mutable);
}
`

这里有很多好的答案,但你提供了一个“定义不可变对象的策略”的链接,这对我来说似乎是唯一明智的出路(我不想使用外部库)。所以你得到了采纳。 :-) - ef2011

4

你没有忽略任何东西。在纯Java中没有方法可以做到这一点。可能有一些库使用注释提供了部分功能,但我目前不知道有哪些。

将引用传回不可变数据的方法是使你传回的类不可变,简单明了。有几个库函数可以帮助你在一些非常有限但常见的情况下生成不可变的 视图 数据。以下是一个示例:

private List<String> internalData;

public List<String> getSomeList() {
    return Collections.unmodifiableList(internalData);
}

2
你可以返回成员的副本,这样更改就不会反映在私有引用所指向的对象中。当然,对于基本类型,这个问题是不存在的。
但是要注意内存使用情况!这可能并不是所有情况下的正确解决方案。在这种情况下,另一个答案建议的不可变对象可能是正确的选择。

1

我认为在Java中对于非原始对象没有这样的方法(你总是传递这些对象的引用)。你能做到的最接近的方法是返回对象的副本(使用clone或类似的东西); 但那不是很符合Java的惯例。

如果您只想让成员对象的“可见”部分可访问,您可以创建一个具有可见部分的接口,并返回此接口。例如:

public interface Bar {
     public int getBing();
}

public class BarImpl implements Bar {
     private int bing;
     public int getBing() {
        return bing;
     }
     public void setBing(int bing) {
        this.bing = bing;
     }
}

public class Foo {
     private BarImpl bar;

     public Bar getNonModifiableBar() {
         return bar; // Caller won't be able to change the bing value, only read it.
     }
}

为什么返回一个副本不是Java的惯用方式?请参见http://www.informit.com/articles/article.aspx?p=31551&seqNum=2 - helpermethod
抱歉,我的意思是对于访问器(即臭名昭著的get/set方法),这并不是最常见的做法。我不会期望一个名为getFoo的函数返回字段的副本而不是字段本身的引用。在Boch的情况下,接口不是getStartDate() / setEndDate(),因此可能会有较少的潜在误解。 - phtrivier

0

防止修改由返回的对象负责。Java除了类型缺乏mutator之外,没有提供声明性/编译时检查不可修改对象的支持。

JDK中有一些支持:像Collection.unmodifiableCollection这样的方法将创建一个对象,如果客户端调用集合mutator方法,则会抛出运行时异常。

如果你真的有动力,可以通过定义只公开/实现访问器方法的只读接口(或类)来获得编译时检查。请记住,仅声明方法返回只读接口将无法防止运行时修改,如果客户端使用内省并且对象提供mutator(不抛出UnsupportedOperationException),则会导致修改。


1
顺便说一下,不可修改的...方法不会复制主题集合。新对象是一个代理,只将访问器调用委托给原始对象,并在任何变异器调用上抛出异常。 - John

0
避免这个问题的一种方法是不暴露数据结构(另一种是返回副本或不可变的包装器)。
例如,可以使用以下方式:
public List<String> getSomeList();

public String getSomeElement(int index);

0

当你做类似这样的事情:

Object2 obj2 = obj1.getObj2();
obj2 = new Object2();

原始的私有成员(obj1.obj2)保持不变(只是为了确保您掌握了这个概念)。您可以省略对obj2的setter,以使内部字段无法更改。

如果您希望Object2字段是不可变的,则需要应用相同的模式(私有字段,没有setter)。

这回答了您的问题吗?


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