为什么StringBuffer是final的?

6

String类是不可变的,其中一个原因是该类被声明为final,尽管还有其他原因。 但是为什么StringBuffer或StringBuilder是final却是可变的呢? 那么有哪些其他因素决定了String是不可变的呢?


4
我认为你可能混淆了final即不可扩展性和不可变性的概念。 - jdphenix
相关讨论:https://dev59.com/FVvUa4cB1Zd3GeqPslqH - Dmitry Sadakov
1
@jdphenix 为了使一个类真正不可变,它应该是final的,以避免扩展并添加可变性。 - Steve Kuo
@jdphenix 我知道 final 修饰符对类的作用,但我不明白 String 类为什么是不可变的,因为到处都说它是 final 的。但 StringBuffer 也是 final 的,那还有哪些因素呢? - Abhilash28
4
不可变类是指其实例状态在构造后无法更改的类。最终类是指不能被扩展的类。这两个概念几乎是正交的。它们之间唯一的联系是不可变类是最终类,这样就无法创建子类来添加可变状态到基类中。 - JB Nizet
显示剩余5条评论
7个回答

6

StringBufferStringBuilder主要用于单个方法内的字符串连接操作,使用它们的代码通常是由编译器生成的。因此,扩展它们不是典型的用例。

另一方面,使用final可以在JVM内进行更好的优化,至少在过去是这样;然而,今天的HotSpot JVM并不需要它,但从这些类中更改final声明的原因从来没有改变过。

请注意,扩展StringBuilder并覆盖方法以实现多态行为有点无意义,因为整个JRE中没有任何接受或返回StringBuilder实例的public方法(除了StringBuilder本身)。填补这一空白的是AppendableCharSequence,它们提供了更大的灵活性。

可变性不可变性的概念与final类的概念完全不同。它们仅取决于类提供的方法或操作的类型。String没有允许修改其内容的方法,而StringBufferStringBuilder有这样的方法。声明一个不可变类为final只是帮助禁止子类引入支持突变的方法,但这不是硬性要求。


1
我想要为字符串构建器添加简单的功能,比如appendln,它基本上会执行sb.append(str).append("\n"),而不是使用一个接受stringbuilder的静态方法,我认为通过扩展stringbuilder并添加几个appendln实现会更加清晰。所以对于这个小用例来说,扩展它会很有用。在这种情况下,有没有更好的解决方案?因为我不确定是否可以使用Appendable或CharSequence,而扩展则更容易些。 - AgentM
2
@AgentM 所有 StringBuilderappend 方法都已经声明为返回 StringBuilder 而不是你的假想子类,因此当使用继承来的 append 方法时,yourBuilder.append(x).appendLn(y) 不起作用,因为在第一个调用(继承的)append 方法之后,编译器会抱怨 StringBuilder 没有你的新方法。除非你覆盖所有这些方法以返回更特定的类型,即你的子类。但是当你必须覆盖所有方法时,扩展并不比委托更容易。 - Holger
@Holger非常好的观点!对我来说,那就是“正确”的答案! - marcolopes

2
为什么String是final和不可变的其实是两个部分独立的问题。首先,String是java.lang包的一部分,名字就表明这个包中的类型与实现Java语言相关。这是它是final的原因之一,如果我将String带到任何地方,我可以依赖它的行为符合Java语言定义的String的行为(因为您无法创建具有不同行为的自己的子类)。其次,String的不可变性只是“仅仅”是一个设计选择,但它在这样一个方面有有利的影响,即当你传递一个String(例如文件名)时,被调用的API可以确定你不能更改字符串并直接存储对它的引用(而不是如果它是可变的则进行复制)。它还显著简化了在多线程环境下使用String。那么为什么StringBuilder/Buffer是final呢?这又是“仅仅”设计选择,但是没有像String那样有强有力且明显的理由支持它们。他们只是以这种方式完成了它。

1
我认为您对于“final”关键字在类中使用时存在误解。当变量被声明为final时,它们的值一旦初始化就不能改变。
例如:
final int i=5;
      i++// Will give an error.

但是:

对于类: final关键字的作用是使类不可继承,即该类无法被子类化! 例如:

final class question
{}
class answer extends question//Will give an error!!!
{}

回答你的问题:

现在,你是对的,字符串是不可变的,因为一旦字符串对象被初始化,就不能被改变,而Stringbuilder和Stringbuffer是可变的(反之亦然),这与这些类是final无关!你没有正确地子类化它们!

我希望我有所帮助!

编辑:

我想我应该完全解释一下Stringbuilder为什么是可变的,而String不是:

我将逐行解释此代码:

Stringbuilder a = new Stringbuilder("a");//Initial String!!
a.append("b"); //Now the String is ab!!

因为同一对象即 a 的值已经改变,所以该对象被称为可变的(在初始化后能够改变其值)。
现在是字符串:
String a = "a";
a = a + "b"; //Explained below.

尽管在您看来,字符串对象a正在改变其值,因此是可变的,但实际上并非如此,因为在幕后,Stringbuilder正在工作。 即编译器在上述代码中实际执行的操作是:

a = new StringBuilder(a.append("b")); //The actual code...

所以,现在变量a存储的值为"ab"。 我知道这很令人困惑,但如果您仔细阅读我的代码,就会发现对象a从未改变过它的值,只是被赋予了一个新值! 这就是所谓的不可变性,即一个对象在初始化后无法更改! 希望对您有所帮助。

那么它是如何变成不可变的呢? - Abhilash28
@abhilash:不暴露任何变异方法,禁止子类化,并保证方法的结果始终相同。 - Louis Wasserman
请注意,虽然我在上面指定变量a存储值“ab”,但实际上在Java中变量不存储任何值!它们只是存储实际存储在内存中(使用“new”关键字创建)的对象的内存引用。我只是为了简化解释才这样说的! - Kaushal Jain
如果你想知道字符串类如何是不可变的,答案非常简单!在 java.lang.String 类内部的数据成员都是 final 的!!! - Kaushal Jain

1

那么决定String不可变的其他因素是什么?

答案很简单,因为String类的所有数据成员都是final的,这使它成为不可变的。

如果您想使自己的类不可变,请将该类的所有数据成员都设置为final。并在构造函数中初始化所有最终数据成员,以便一旦它们被初始化,就永远不会改变。

如果您查看不可变性的String类实现,您会发现:

private final char[] value;

值的初始化将在构造函数部分进行:

public String(String toCopy){

     value = toCopy.value;

}

如果你查看 StringBuffer 类的实现,你会发现:

private char[] value;

public StringBuffer(String toCopy){

     value = toCopy.value;

}

在上面的代码中,你可以看到,String类的value数据成员将被初始化一次并且永远不会改变,但是StringBuffer类的value数据成员可以被初始化和多次更改。 因此,这使得String类是不可变的,而不是使类变为final。

1

String不是不可变的,因为它是final的;final和不可变性或多或少是无关的。将一个类设为final是使其不可变的方法之一,但是无论你想要什么样的行为,将一个类设为final也是良好编程实践的一部分。

一个不可变类不能被外部用户扩展。它可以在自己的包中有几个实现,例如Guava的ImmutableMap,在这种情况下,该类不需要是final的,但是您也可以通过将构造函数设置为包私有来禁止外部用户扩展该类。

final类是任何不能被扩展的类。在Effective Java中,这是许多情况下的推荐做法--也就是说,每当您没有显式的理由使类可子类化时,将其设为final通常是一个好的实践。


0

如果您关注的是扩展StringBuilder或StringBuffer的行为,您仍然可以这样做 - 只是不能通过继承。您可以使用组合或委托。String、StringBuffer和StringBuilder类都实现了CharacterSequence。因此,创建实现CharacterSequence的类,并将所有方法委托给组成的StringBuffer或StringBuilder。在GoF中,组合优于继承是黄金法则 - 请参见讨论

使一个类变为final是一种更强大的封装层次 - 可能代码的作者认为任何人不应该通过扩展来修改行为,因为他可能会觉得问题类的子类在转换方面并不真正代表它自己。


0

StringBuffer 是可变字符串的概念,是线程安全的,也就是说,对它执行的所有操作都是同步的。

StringBuilder 推荐用来替代 StringBuffer。当涉及到字符串时,很少使用同步,因此引入了它。

String 就是字符串。为什么它是不可变的?可能原因是线程安全和成本问题。如果您想存储全局的只读字符串,如果它是可变的,那么所有读取都必须进行同步 - 这是不必要的额外开销。

这就是为什么提供了这三个类 - 您可以选择要支付什么。

正如您所注意到的,所有这些类 - StringStringBuilderStringBuffer 都是 final 的。原因是您不能(也不应该)修改这些类的行为。

最好实现另一个类,其中包含(例如)StringBuilder 并提供类似的功能,而不是扩展它。

如果您愿意,可以通过实现CharSequence来提供类似的功能 - 所有这些类都实现了该接口。


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