为什么Java有“String”类型而不是“string”?

19

包装类是很好的,它们的目的也很清楚。但是为什么我们要省略基本类型呢?


我已经编辑了我的答案并添加了更多信息。 - Robert Fraser
6个回答

44

“primitive” 的含义取决于你的理解

在 Java 中,“primitive” 通常指 “value type”。然而,C# 有一个 string 关键字,它的作用与 Java 的 String 完全相同,只是编辑器中着色方式不同。它们都是别名,指向 System.Stringjava.lang.String 类。String 在这两种语言中都不是值类型,因此在这个意义上它不是原始数据类型。

如果你认为“primitive” 意味着内置于语言中,那么 String 就是原始数据类型。只是首字母大写。字符串字面量(那些用引号括起来的东西)会自动转换为 System.String,使用加号进行连接。所以按照这种标准,它们(和数组)就和 int、long 等一样原始。

那么什么是 String?

String 不是一个包装器。String 是一个引用类型,而基本类型是值类型。这意味着如果你有:

int x = 5;
int y = x;

x和y的内存都包含“5”。但是使用:

String x = "a";
String y = x;

x 和 y 的内存中都包含一个指向字符 "a" 的 指针(以及长度、偏移量、ClassInfo 指针和监视器)。字符串因为是不可变的,所以它们像一个基本类型。通常情况下这不是问题,但如果你使用反射来改变字符串的内容(不要这样做!),那么 x 和 y 都将看到更改。实际上,如果你有:

char[] x = "a".toCharArray();
char[] y = x;
x[0] = 'b';
System.out.println(y[0] == 'b'); // prints "true"
所以不要只使用char[](除非这是你想要的行为,或者你确实尝试减少内存使用)。
每个Object都是一个引用类型——这意味着您编写的所有类、框架中的每个类甚至数组都是如此。唯一的值类型是简单的数值类型(int、long、short、byte、float、double、char、bool等)。
为什么String不像char[]那样可变?
原因有几个,但主要归结于心理学和实现细节:
- 想象一下,如果您将一个字符串传递到另一个函数中,并且该函数以某种方式更改了它,那么会发生什么混乱。或者,如果它将其保存在某个地方并在将来更改了它怎么办?对于大多数引用类型,您接受这是类型的一部分,但Java开发人员决定,至少对于字符串,他们不希望用户担心这一点。 - 字符串不能被原子化处理,这意味着多线程/同步将成为一个问题。 - 字符串文字(放在引号中的东西)可能在计算机级别上是不可变的1(出于安全原因)。这可以通过在程序启动时将它们全部复制到另一个内存部分中或使用写时复制来解决,但这很慢。
为什么我们没有字符串的值类型版本?
基本上是因为性能和实现细节,以及拥有两种不同的字符串类型的复杂性。其他值类型具有固定的内存占用。int始终为32位,long始终为64位,bool始终为1位等2。除其他外,这意味着它们可以存储在堆栈上,以使所有函数参数都存在于一个地方。此外,大量复制字符串会严重影响性能。
另请参见:在C#中,为什么String是一种行为类似于值类型的引用类型?。这适用于.NET,但在Java中也同样适用。 1 - 在C/C++和其他本机编译语言中,这是真的,因为它们被放置在进程的代码段中,而操作系统通常会阻止您进行编辑。在Java中,这通常是不正确的,因为JVM将类文件加载到堆上,因此您可以在那里编辑字符串。但是,Java程序无法原生编译(有些工具可以做到这一点),并且某些架构(特别是某些ARM版本)直接执行Java字节码。 2 - 实际上,这些类型中的一些在机器级别上具有不同的大小。例如,布尔值在堆栈上存储为WORD大小(x86上为32位,x64上为64位)。在类/数组中,它们可能会被视为不同。这是由JVM决定的所有实现细节——规范说布尔值要么为真,要么为假,机器可以弄清楚如何做。

你创建和使用的每个类都不是原始类型之一。 - thecoop
2
@Ravi - 所有对象都是引用类型(也就是说所有类)。Java中唯一的值类型是具有关键字的类型(如int,char,double等)。 - Robert Fraser
1
不确定这怎么回答问题,或者是我误解了问题?我认为问题是为什么Java没有字符串的原始类型。 - Aadith Ramia
它解答了我对于将包装类与字符串混淆的疑惑。但是,另一个部分“为什么Java中没有字符串”仍然未解决。 - Ravi Gupta
如果string是编译器识别的类型,它可能会有一个基于字符序列相等性的==运算符(但是,如果将两个字符串强制转换为Object,则==运算符将测试引用相等性)。因此,这种设计也使得受益者受益匪浅。 - supercat
显示剩余2条评论

10

String 的基本类型是 char[]

这适用于许多语言(如 C、Java、C#、C++ 等)。


据我所知,char[]不是原始类型。 - Ahmed Kotb
1
char 是最基本的数据类型之一,而数组构造是语言的一部分。字符串实际上就是 char 数组。 - Oded
是的,String不仅仅是char[]的包装器。 - Robert Fraser
这意味着int[]、char[]和所有原始类型的数组都被视为原始类型吗? - Ravi Gupta
@Ravi - 不对!char[]实际上是一个具有一些特殊语法的数组类。数组是按引用传递的,并且只在语言中具有“原始”含义,因为它们具有特殊的支持(但字符串也是如此)。 - Robert Fraser
4
字符串不是字符数组。String类可能使用字符数组作为内部存储(大多数情况下确实如此),但这并不是语言或API规范所要求的。 - jarnbjo

3

字符串可以是任意长度的。Java的创始人不希望有一种原始类型,无法指定具体的内存大小。这是String在Java中不是原始类型的主要原因之一。


嗯...但这听起来更像是实现问题,而不是规范问题。 - Ravi Gupta
嗯...这取决于你如何看待它...我不会将其归类为完全的实现问题...这只是做出决定时考虑的其中一个因素。 - Aadith Ramia

0

字符串是一种特殊情况。所有真正的原始类型(int、long等)都是按值传递,并直接在JVM中实现。字符串是引用类型,因此像任何其他类一样处理(大写字母,按引用传递...),除了编译器有特殊的钩子来处理它,就像内置类型一样(例如字符串连接符+)。

由于它已经是引用类型,所以不需要像Integer那样的包装类来将其作为类使用(例如在集合中)。


4
引用类型不是按引用传递,而是按值传递引用。 - Jon Skeet
我猜Java中没有任何类型是按引用传递的。 - Ravi Gupta

0

原始类型?

在Java中,字符串没有原始类型。原始类型包括int、float、double、boolean等和char。

因此,为了使用字符串,它们使用了一个对象。您实例化它,它存在于堆中,您有一个对它的引用等。

他们是如何实现的呢?将其表示值保存在char数组中。

不可变性

但他们确保了不可变性。当您有一个对String对象的引用时,您知道可以自由地将其传递给其他对象,因为该引用指向的值不会更改。所有修改字符串的方法都返回字符串的其他实例,因此不会更改其他对String的引用所表示的值。

是否可以像.NET一样采用其他方式?

可以。他们可以定义一个保留字“string”,然后编译器进行转换。

但他们没有这样做...


-1
一个 String 就是一个 char 的数组。由于它是一个数组,它不能是一个原始类型!:-)

不,它不是一个原始类型 - 它是一个数组,加上一个偏移量,再加上一个长度。我同意它不仅仅是一个基本类型,但它比一个char[]更复杂。 - Jon Skeet
@Jon:还有一个监视器和ClassInfo。别忘了那个老旧的synchronized()语句 :-). - Robert Fraser
正如SO的Anderson先生指出的那样,char[]在许多方面都与String不同。 - Ravi Gupta
@Robert:不确定你所说的ClassInfo是什么意思,但char[]也有一个Monitor(可用于同步块)和一个Class,它的超类是Object:char[].class.getSuperclass() == Object.class - user85421

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