为什么在 Java 中可以使用 + 运算符添加 String,而 String 是一个类?在 String.java代码中,我没有找到这个操作符的任何实现。这个概念是否违反了面向对象的原则?
String.java
检查 +操作符的操作数是 Java 编译器的一个特性。它根据操作数生成字节码:
+
这就是 Java 规范所说的 :
运算符 + 和 -称为加法运算符。 表达式: 乘法表达式 加法表达 + 乘法表达 加性表达-乘性表达 相加运算符具有相同的优先级,并且在语法上是相同的 左结合(他们从左到右分组)。 < b > 如果 +操作符的操作数是 String,那么操作就是字符串串联。 否则,+运算符的每个操作数的类型必须是 将(5.1.8)转换为基元数值类型,或者发生编译时错误。 在每种情况下,二进制 -运算符的每个操作数的类型必须是 可转换(5.1.8)为基元数字类型的类型,或 发生编译时错误。
运算符 + 和 -称为加法运算符。 表达式: 乘法表达式 加法表达 + 乘法表达 加性表达-乘性表达
-
相加运算符具有相同的优先级,并且在语法上是相同的 左结合(他们从左到右分组)。 < b > 如果 +操作符的操作数是 String,那么操作就是字符串串联。
String
否则,+运算符的每个操作数的类型必须是 将(5.1.8)转换为基元数值类型,或者发生编译时错误。
在每种情况下,二进制 -运算符的每个操作数的类型必须是 可转换(5.1.8)为基元数字类型的类型,或 发生编译时错误。
+操作符通常在编译时由 StringBuilder替换。查看这个 回答了解关于这个问题的更多细节。
StringBuilder
Java 语言为字符串连接操作符(+)和将其他对象转换为字符串提供了特殊支持。字符串串联是通过 StringBuilder(或 StringBuffer)类及其 append方法实现的。
StringBuffer
append
首先(+)是重载的,而不是重写的
Java 语言为字符串提供了特殊的支持 连接操作符(+) ,它已经被 Java 字符串重载 物品。
如果左边的操作数是 String,它将作为串联操作。
如果左边的操作数是整数,它作为加法运算符
让我们看看下面 Java 中的简单表达式
int x=15; String temp="x = "+x;
编译器在内部将 "x = "+x;转换为 StringBuilder,并使用 .append(int)将整数“添加”到字符串中。
"x = "+x;
.append(int)
5.1.11字符串转换
任何类型都可以通过字符串转换转换为 String 类型。 首先将基元类型 T 的值 x 转换为引用值 就好像把它作为一个参数给一个合适的类实例 创造表达式(15.9) : 如果 T 是布尔型的,那么使用新的布尔型(x)。 如果 T 是 char,那么使用新字符(x)。 如果 T 是 byte、 short 或 int,则使用 new Integer (x)。 如果 T 是 Long,则使用 new Long (x)。 如果 T 是 Float,那么使用 new Float (x)。 如果 T 是 Double,那么使用 new Double (x)。 然后,此引用值按字符串转换为 String 类型 转变。 现在只需要考虑参考值: 如果引用为 null,则将其转换为字符串“ null”(4个 ASCII 字符 n、 u、 l、 l)。 否则,转换就像是通过调用被引用对象的 toString 方法执行的,不带任何参数; 但是 如果调用 toString 方法的结果为 null,则 字符串“ null”被替代。 ToString 方法由原始类 Object 定义 (4.3.2)。许多类覆盖它,特别是布尔,字符, 整数,长数,浮点数,双数和字符串。 有关字符串转换上下文的详细信息,请参阅5.4。
任何类型都可以通过字符串转换转换为 String 类型。
首先将基元类型 T 的值 x 转换为引用值 就好像把它作为一个参数给一个合适的类实例 创造表达式(15.9) :
然后,此引用值按字符串转换为 String 类型 转变。
现在只需要考虑参考值:
ToString 方法由原始类 Object 定义 (4.3.2)。许多类覆盖它,特别是布尔,字符, 整数,长数,浮点数,双数和字符串。
有关字符串转换上下文的详细信息,请参阅5.4。
15.18.1.
字符串串联的优化: 实现可以选择执行转换和连接 在一个步骤中避免创建然后丢弃中间体 提高重复字符串的性能 连接,Java 编译器可以使用 StringBuffer 类或 类似的技术来减少中间字符串对象的数量 通过对表达式求值创建的。 对于基元类型,实现还可以优化消除 通过直接从原语转换来创建包装器对象 键入字符串。
字符串串联的优化: 实现可以选择执行转换和连接 在一个步骤中避免创建然后丢弃中间体 提高重复字符串的性能 连接,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习惯用法的简写。
String 类如何覆盖 + 运算符?
严格来说,编译器 超载是 String 操作数的 + 运算符。
+操作符应用于 String时的含义是由语言定义的,因为每个人都已经写过了。既然你似乎不觉得这足以令人信服,那么考虑一下:
整数、浮点数和双精度数都有不同的二进制表示,因此在位操作方面,添加两个整数与添加两个浮点数是不同的操作: 对于整数,你可以一位一位地添加,带一位并检查溢出; 对于浮点数,你必须分别处理尾数和指数。
因此,原则上,“加法”取决于被“加法”对象的性质。Java 为 String 以及 int 和 float (longs、 double、 ...)定义了它