为什么在 Java 中,当 String 是一个类时,您可以使用 + 运算符添加字符串?在String.java
代码中,我没有找到该运算符的任何实现。这个概念是否违反了面向对象?
7 回答
让我们看一下Java中的以下简单表达式
int x=15;
String temp="x = "+x;
编译器在内部转换"x = "+x;
为 a并用于将整数“添加”到字符串中。StringBuilder
.append(int)
任何类型都可以通过字符串转换转换为String类型。
原始类型 T 的值 x 首先被转换为引用值,就好像通过将其作为参数提供给适当的类实例创建表达式(第 15.9 节):
- 如果 T 是布尔值,则使用 new Boolean(x)。
- 如果 T 是 char,则使用 new Character(x)。
- 如果 T 是 byte、short 或 int,则使用 new Integer(x)。
- 如果 T 很长,则使用 new Long(x)。
- 如果 T 是浮点数,则使用 new Float(x)。
- 如果 T 为 double,则使用 new Double(x)。
然后通过字符串转换将此引用值转换为 String 类型。
现在只需要考虑参考值:
- 如果引用为 null,则将其转换为字符串“null”(四个 ASCII 字符 n、u、l、l)。
- 否则,转换就像调用被引用对象的 toString 方法一样执行,不带参数;但如果调用 toString 方法的结果为 null,则使用字符串“null”。
toString 方法由原始类 Object(第 4.3.2 节)定义。许多类会覆盖它,特别是 Boolean、Character、Integer、Long、Float、Double 和 String。
有关字符串转换上下文的详细信息,请参见第 5.4 节。
字符串连接的优化: 实现可以选择在一个步骤中执行转换和连接,以避免创建然后丢弃中间字符串对象。为了提高重复字符串连接的性能,Java 编译器可以使用 StringBuffer 类或类似技术来减少通过计算表达式创建的中间 String 对象的数量。
对于原始类型,实现还可以通过直接从原始类型转换为字符串来优化包装对象的创建。
优化后的版本实际上不会首先进行完整包装的字符串转换。
这是编译器使用的优化版本的一个很好的说明,尽管没有转换原语,您可以在其中看到编译器在后台将内容更改为 StringBuilder:
http://caprazzi.net/posts/java-bytecode-string-concatenation-and-stringbuilder/
这个java代码:
public static void main(String[] args) {
String cip = "cip";
String ciop = "ciop";
String plus = cip + ciop;
String build = new StringBuilder(cip).append(ciop).toString();
}
生成这个 - 看看这两种连接样式如何导致相同的字节码:
L0
LINENUMBER 23 L0
LDC "cip"
ASTORE 1
L1
LINENUMBER 24 L1
LDC "ciop"
ASTORE 2
// cip + ciop
L2
LINENUMBER 25 L2
NEW java/lang/StringBuilder
DUP
ALOAD 1
INVOKESTATIC java/lang/String.valueOf(Ljava/lang/Object;)Ljava/lang/String;
INVOKESPECIAL java/lang/StringBuilder.<init>(Ljava/lang/String;)V
ALOAD 2
INVOKEVIRTUAL java/lang/StringBuilder.append(Ljava/lang/String;)Ljava/lang/StringBuilder;
INVOKEVIRTUAL java/lang/StringBuilder.toString()Ljava/lang/String;
ASTORE 3
// new StringBuilder(cip).append(ciop).toString()
L3
LINENUMBER 26 L3
NEW java/lang/StringBuilder
DUP
ALOAD 1
INVOKESPECIAL java/lang/StringBuilder.<init>(Ljava/lang/String;)V
ALOAD 2
INVOKEVIRTUAL java/lang/StringBuilder.append(Ljava/lang/String;)Ljava/lang/StringBuilder;
INVOKEVIRTUAL java/lang/StringBuilder.toString()Ljava/lang/String;
ASTORE 4
L4
LINENUMBER 27 L4
RETURN
查看上面的示例以及如何根据给定示例中的源代码生成字节码,您将能够注意到编译器在内部对以下语句进行了转换
cip+ciop;
进入
new StringBuilder(cip).append(ciop).toString();
换句话说,+
字符串连接中的运算符实际上是更冗长的StringBuilder
习语的简写。
它是检查运算+
符的操作数的 Java 编译器功能。并根据操作数生成字节码:
- 对于 String,它会生成代码来连接字符串
- 对于 Numbers,它会生成添加数字的代码。
这就是 Java 规范所说的:
运算符 + 和
-
称为加法运算符。AdditiveExpression:MultiplicativeExpression AdditiveExpression + MultiplicativeExpression AdditiveExpression - MultiplicativeExpression加法运算符具有相同的优先级并且在语法上是左结合的(它们从左到右分组)。如果运算符的任一操作数的类型
+
是String
,则该操作是字符串连接。否则,运算符的每个操作数的
+
类型必须是可转换(第 5.1.8 节)为原始数字类型的类型,否则会发生编译时错误。在每种情况下,二元
-
运算符的每个操作数的类型都必须是可转换(第 5.1.8 节)为原始数字类型的类型,否则会发生编译时错误。
String 类如何覆盖 + 运算符?
它没有。编译器会这样做。严格来说,编译器为字符串操作数重载了 + 运算符。
首先 (+) 被重载而不是被覆盖
Java 语言为字符串连接运算符 (+) 提供了特殊支持,该运算符已为 Java 字符串对象重载。
如果左侧操作数是字符串,它作为连接工作。
如果左侧操作数是整数,则它用作加法运算符
正如每个人都已经写过的那样,+
运算符在应用时的含义由语言定义。String
由于您似乎认为这不够令人信服,请考虑以下几点:
整数、浮点数和双精度数都有不同的二进制表示,因此添加两个整数与添加两个浮点数在位操作方面是不同的操作:对于整数,您可以逐位添加,携带位并检查溢出;对于浮点数,您必须分别处理尾数和指数。
因此,原则上,“加法”取决于被“加法”对象的性质。Java 为字符串以及整数和浮点数(longs、doubles、...)定义了它
Java 语言为字符串连接运算符 (+) 和将其他对象转换为字符串提供了特殊支持。字符串连接是通过StringBuilder
(or StringBuffer
) 类及其append
方法实现的。
+
运算符通常在编译时替换为a StringBuilder
。检查此答案以获取有关此问题的更多详细信息。