【问题标题】:How does the String class override the + operator?String 类如何覆盖 + 运算符?
【发布时间】:2012-07-09 15:23:06
【问题描述】:

为什么在 Java 中,当 String 是一个类时,您可以使用 + 运算符添加字符串?在String.java 代码中,我没有找到该运算符的任何实现。这个概念是否违反了面向对象?

【问题讨论】:

  • Plus (+) 运算符是 Java 的语言特性。
  • 这都是编译器的魔法。你不能在 Java 中进行运算符重载。
  • 我觉得奇怪的是 String 是作为一个库实现的/i> 语言。这是不对的。
  • @13ren 你错了。 String 是 java.lang 类的一部分,它代表语言 - java 语言!!!这个包中的所有类都是特殊的。请注意,您无需导入 java.lang 即可使用它们。

标签: java string object


【解决方案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 字符串重载 对象。

    1. 如果左侧操作数是字符串,则它作为连接工作。

    2. 如果左侧操作数是整数,则它作为加法运算符工作

    【讨论】:

    • (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、...)定义了它

      【讨论】: