String 类如何覆盖 + 运算符?
Posted
技术标签:
【中文标题】String 类如何覆盖 + 运算符?【英文标题】:How does the String class override the + operator? 【发布时间】:2012-07-09 15:23:06 【问题描述】:为什么在 Java 中,当 String 是一个类时,您可以使用 + 运算符添加字符串?在String.java
代码中,我没有找到该运算符的任何实现。这个概念是否违反了面向对象?
【问题讨论】:
Plus (+
) 运算符是 Java 的语言特性。
这都是编译器的魔法。你不能在 Java 中进行运算符重载。
我觉得奇怪的是 String 是作为一个库实现的/i> 语言。这是不对的。
How does + operator behaves differently with numbers and strings in Java?的可能重复
@13ren 你错了。 String 是 java.lang 类的一部分,它代表语言 - java 语言!!!这个包中的所有类都是特殊的。请注意,您无需导入 java.lang 即可使用它们。
【参考方案1】:
这是检查+
运算符的操作数的Java 编译器功能。并根据操作数生成字节码:
Java 规范是这么说的:
运算符 + 和
-
称为加法运算符。 加法表达式: 乘法表达式 加法表达式 + 乘法表达式 AdditiveExpression - 乘法表达式加法运算符具有相同的优先级并且在语法上是 左联想(他们从左到右分组)。 如果任一类型
+
运算符的操作数是String
,则该操作是字符串连接。否则,
+
运算符的每个操作数的类型必须是 可转换(第 5.1.8 节)为原始数值类型,否则会发生编译时错误。在任何情况下,二进制
-
运算符的每个操作数的类型必须是 可转换(第 5.1.8 节)为原始数字类型的类型,或 发生编译时错误。
【讨论】:
规范中的引用与这个问题完全无关。 这是摘录“如果+运算符的任一操作数的类型是String,则该操作是字符串连接。否则,+运算符的每个操作数的类型必须是类型可转换(第 5.1.8 节)为原始数字类型,否则会发生编译时错误”。你能告诉我为什么它不相关吗? 它没有说明它是如何实现的,这是个问题。我认为发布者已经明白该功能的存在。【参考方案2】:+
运算符通常在编译时被 StringBuilder
替换。查看此answer 了解有关该问题的更多详细信息。
【讨论】:
如果是这种情况,StringBuilder 是否存在任何理由供公众使用?有没有+
操作符不被StringBuilder
替换的情况?
您要问的问题是“为什么 + 运算符存在于所有公共用途?”,因为这就是可憎之处。至于你的另一个问题,我也不是很清楚,但我猜应该没有这种情况。
如果只有两个元素,编译器可能会使用 concat() 代替。当程序员在长嵌套/循环代码中构建字符串时,编译器也无法用 StringBuilder 替换 concat()(或使用多个 StringBuilder 并将它们附加在一起) - 使用单个显式 StringBuilder 会更好地提高性能。【参考方案3】:
Java 语言为字符串连接运算符 (+) 和将其他对象转换为字符串提供了特殊支持。字符串连接是通过StringBuilder
(或StringBuffer
)类及其append
方法实现的。
【讨论】:
【参考方案4】:首先(+)是重载而不是覆盖
Java 语言为字符串提供了特殊的支持 连接运算符 (+),已为 Java 字符串重载 对象。
如果左侧操作数是字符串,则它作为连接工作。
如果左侧操作数是整数,则它作为加法运算符工作
【讨论】:
(2) 如果左操作数是一个整数,它会自动拆箱到int
,然后应用Java的正常规则。
引用下面给出的两条规则是错误的:我认为它们应该是:两个原语(或不可装箱的类)=加法;至少一个字符串 = 连接【参考方案5】:
让我们看一下Java中的以下简单表达式
int x=15;
String temp="x = "+x;
编译器在内部将"x = "+x;
转换为StringBuilder
,并使用.append(int)
将整数“添加”到字符串中。
5.1.11. String Conversion
任何类型都可以通过字符串转换转换为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、 整数、长整数、浮点数、双精度和字符串。
有关字符串转换上下文的详细信息,请参阅第 5.4 节。
15.18.1.
字符串拼接的优化: 实现可以选择执行转换和连接 一步避免创建然后丢弃中间体 字符串对象。提高重复字符串的性能 连接,Java 编译器可以使用 StringBuffer 类或 减少中间字符串对象数量的类似技术 通过评估表达式创建的。
对于原始类型,实现也可以优化掉 通过直接从原语转换来创建包装器对象 输入一个字符串。
优化后的版本实际上不会先进行完整的字符串转换。
这是编译器使用的优化版本的一个很好的说明,尽管没有转换原语,您可以在其中看到编译器在后台将内容更改为 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
成语的简写。
【讨论】:
非常感谢,我不熟悉 jvm 字节码,但生成的代码为 String plus = cip + ciop;和 String build = new StringBuilder(cip).append(ciop).toString();是一样的。我的问题是这个操作是否违反了面向对象? 不,它没有。运算符重载(如在 C++ 和某些语言中)有一些缺点,Java 设计人员认为它有点令人困惑,因此在 Java 中将其省略了。对我而言,面向对象的语言必须具备 Java 所具有的继承、多态性和封装等主要概念。 是的,但我认为这个运算符已经为 String 类重载了 是的,Java 中使用 运算符重载 来连接 String 类型,但是,您不能定义自己的运算符(如在 C++、C# 和其他一些语言中)。 @Pooya:实际上“int / int”与“int / float”是已经运算符重载,所以即使C也有。然而,C(和 Java)没有 有的是用户定义的运算符重载:唯一定义运算符可以使用的不同方式(在 C 和 Java 中)是语言定义(并且区别是在编译器中实现的)。 C++ 的不同之处在于它允许用户定义的运算符重载(通常简称为“运算符重载”)。【参考方案6】:String 类如何覆盖 + 运算符?
它没有。编译器会这样做。严格来说,编译器重载字符串操作数的+运算符。
【讨论】:
【参考方案7】:+
运算符在应用于String
时的含义由语言定义,正如每个人都已经写过的那样。由于您似乎认为这不够令人信服,请考虑以下几点:
整数、浮点数和双精度数都有不同的二进制表示形式,因此,就位操作而言,添加两个整数与添加两个浮点数是不同的操作:对于整数,您可以逐位添加,携带位并检查溢出;对于浮点数,您必须分别处理尾数和指数。
因此,原则上,“添加”取决于被“添加”对象的性质。 Java 为字符串以及整数和浮点数(longs、doubles、...)定义了它
【讨论】:
以上是关于String 类如何覆盖 + 运算符?的主要内容,如果未能解决你的问题,请参考以下文章