Java中是否有一个只读(Readonly)字段(可在类本身的范围内设置)?

46

如何在类内部拥有一个可写变量,但只能在类外部"只读"?

例如,不必这样做:

Class C {
  private int width, height;

  int GetWidth(){
    return width;
  }

  int GetHeight(){
    return height;
  }

  // etc..

我想要做类似这样的事情:

Class C {
  public_readonly int width, height;

  // etc...

最佳解决方案是什么?

7个回答

50

创建一个包含 public final 字段的类。提供构造函数以初始化这些字段。这样你的类就是不可变的,但是在从外部访问值时不会有额外的开销。例如:

public class ShortCalendar
{
    public final int year, month, day;

    public ShortCalendar(Calendar calendar)
    {
        if (null == calendar)
            throw new IllegalArgumentException();

        year = calendar.get(Calendar.YEAR);
        month = calendar.get(Calendar.MONTH);
        day = calendar.get(Calendar.DATE);
    }
}

2
在Java中,Getter几乎没有开销。该语言是为这种编程风格而设计的,并且旨在处理大量的Getter。简单的Getter(大多数Getter都是如此)将由JVM进行优化,从而提供接近本机代码的访问速度。 - Thomas Uhrig
14
首先,我不反对在必要的情况下使用getter。然而,它们通常会使你的代码充斥着样板式的一行代码。Java是一种笨重的语言,幸运的是,在一些特定情况下,你可以像Objective C属性那样获得优雅的解决方案。 - Aleks N.
3
@AlaksiejN.,如果需要在对象构建后修改类中的值,则此解决方案无效。 - Pacerier
2
@Pacerier 是的,这是预期的行为。如果您需要比C结构更复杂的类,请使用其他人的建议。 - Aleks N.

25

在Java中没有这样的方法。

您有两个选择(其中一个是您提到的):使用公共getter并将字段设置为私有,或在类中进行透彻的文档说明。

获取方法的开销非常小(如果有的话)。如果您需要多次调用它,则可能要缓存获取的值而不是调用获取方法。

编辑:

一种方法是定义一个公共内部类(我们称之为innerC),并仅使其构造函数对您的C类可用,并使您的字段公共。这样,您无法在类外创建innerC实例,因此从外部更改字段是不可能的,但您可以从内部更改它们。但是,您可以从外部读取它们。


2
编辑完成了吗?应该将内部类中的字段指定为final以防止外部更改,从而使外部类成为唯一可以通过重新实例化内部类来更改值的类?即使我考虑到这一点,我也不确定是否有任何方法可以防止C类的用户存储对innerC的引用,然后在C重新实例化innerC以设置新值之后将innerC重置为旧引用。也许一些代码片段可以帮助澄清我的困惑? - yjw
你说得对,存储旧值会使这个解决方案过时。我没有想到。 - Luchian Grigore
1
内部字段 innerC 仍然可以从外部设置为 null。 - jamp
没错,我认为你可以在类外面添加这个readonly等等,以便让其他人知道。 - Cflowe Visit

7

据我所知,编译器不会优化这种类型的代码。

Hotspot JIT编译器绝对会进行优化。

我在想什么是最好的解决方案?

不要过早地进行优化。如果您在绘图例程中使用此代码,则我可以保证实际绘图所需的时间至少比调用微不足道的方法长一百倍,即使未进行内联。


@Pacerier:就是这个意思,我已经添加了一个链接以供确认。请注意,这是1999年一篇文章的一部分 - 我相信这个功能已经在Hotspot的第一个版本中实现了,该版本随Java 1.3一起发布。 - Michael Borgwardt
@MichaelBorgwardt Heys,我又在思考这个问题了,我想知道当他们谈论“避免getfield操作码”(https://dev59.com/pW445IYBdhLWcg3wpL1_)时,他们是不是试图减少这些东西的开销? - Pacerier
@Pacerier:我同意,问题中的每个人都同意。但是trim()代码可能非常古老,来自JIT编译器远不如现在先进的时代。 - Michael Borgwardt
答案中的链接似乎已过时。 - undefined
@庞,我已经用archive.org的链接替换了它。 - undefined
显示剩余3条评论

5

无法从外部将字段设置为“只读”。唯一且正确的方法是将字段设置为私有,并仅提供getter而不提供setter。


1
是的和不是。当然它是“只读”的,因为它是final的;) 但是它从“内部”和“外部”都是final的 - 我的意思是,它是final的,我还能说什么呢?对于最初的问题(你读过它吗?)的答案是否定的。当然,您可以设置一个final变量,但最初的问题要求一些“只读从外部”的关键字,这根本不存在。而且它也不是final的;) - Thomas Uhrig
作者询问一个只能在类本身范围内设置的字段。因此,我的解决方案满足了这个条件。从问题描述中我们不知道是否需要不断修改该字段。 - Aleks N.
2
他明确要求提供一种解决方案,使其仅能从外部变为只读。仅限于外部。这就是整个问题所涉及的内容。 - Thomas Uhrig
作者展示的类示例是不可变对象的一个好案例。因此,public final 是值得考虑的一个好选项。 - Aleks N.
1
嗯,你知道我不喜欢唠叨一个话题。我的意图是在这里提供选项,而不是说没有可能。 - Aleks N.
显示剩余2条评论

1
实际上,HotSpot编译器很可能会内联调用您的getter方法,因此不会涉及任何开销(除了调用这些方法的开销几乎无法测量之外)。
编辑
如果您真的需要每个CPU周期,请使用C或C++(或在其中编写性能关键部分并通过JNA调用它,尽管这不太可能值得花费时间)。

顺便问一下,我们如何验证HotSpot编译器是否会内联调用(或不会)? - Pacerier
我不是100%确定,但我认为没有一种方法可以验证这一点(因为JVM执行内联操作而不是编译器)。 - helpermethod

1
你的解决方案是: 私有字段, 私有设置器, 受保护或公共的获取器(注意:受保护的允许从同一包以及从子类中访问)

2
@jamp 显然你既没有研究过Java中的访问修饰符(例如:http://docs.oracle.com/javase/tutorial/java/javaOO/accesscontrol.html),也没有尝试过编写示例来查看其是否可以编译通过。如果你是Java初学者,请不要评论其他人的答案。 - RokL
@jamp,你把_protected_和_package_-protected/private(package-protected或package private)搞混了。 - Solomon Ucko
2
@jamp为什么不删除你的评论,这样人们就可以得到清晰的答案了。 - user1935724

-3

是的,我们可以使用final关键字使变量只读

例如:public final int id = 10;


1
在这行代码之后,它本身的类中是不可设置的。 - Sidias-Korrado

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