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 类如何覆盖 + 运算符?的主要内容,如果未能解决你的问题,请参考以下文章

如何覆盖 C++ 类中的 bool 运算符?

枚举类运算符覆盖

在 C++ 中覆盖强制转换运算符

覆盖 == 运算符。如何与空值进行比较? [复制]

覆盖 JavaScript 中比较运算符的默认行为

在 Scala/Chisel 中使用类型参数覆盖/重载运算符