【问题标题】:Does operator overloading exist in Java?Java中是否存在运算符重载?
【发布时间】:2011-11-03 23:27:39
【问题描述】:

我只想了解 Java 中的一件事。让我们考虑以下 Java 代码段。

int x=10;
String temp="x = "+x;
System.out.println(temp);

在 Java 中完全有效,并生成输出 x = 10 作为字符串。


虽然变量 x 是 int 类型,但它会自动转换为 String 类型(包装器类型)。 Java 是如何做到这一点的? Java 中是否存在某种或某处的运算符重载,就像上面的 C++ 和 C# 一样?尽管它已从 Java 中删除。 这里使用了哪个特定的概念来将 x 转换为字符串。我唯一想知道的事情


还有一个问题,Java 中的布尔数据类型(不是布尔,一个包装类)不能转换为任何其他类型(据我所知)。为什么会这样,在一些非常具体的情况下,将其转换为字符串或其他一些类型可能很有用。

【问题讨论】:

  • 请只问一个问题。
  • 要回答你的附带问题,唯一真正有用的是以另一种形式的布尔值输出给用户或其他一些人类可读的形式,它会转换为“真”或附加到字符串或使用 StringBuilder 时为“假”。其他任何事情,都应该由开发人员单独处理,而不是演员。
  • 虽然 JDBC 驱动程序将布尔值转换为 PL/SQL 数字 0 和 1 会很好,所以我不必编写一堆糟糕的帮助程序。 ;-)
  • @ScottAnderson - 您真的希望 JDBC 能够满足无法进行自身类型转换的损坏扩展语言吗?
  • 添加到@glowcoder 的评论中,Java 没有 boolean 和 int 之间的自动转换这一事实是好事,因为它实际上消除了if (i = 1) ... 错误综合症。 IIRC,这是一个有意识的设计决定。

标签: java operator-overloading


【解决方案1】:

编译器在内部将该短语 ("x = "+x) 转换为 StringBuilder,并使用 .append(int) 将整数“添加”到字符串中。

为了超越实际的“Java 如何做到这一点”,我将听取 Stephen 的建议并给出理论。从概念上讲,连接中的每个值首先转换为字符串,然后连接。 Null 被连接为单词“null”。

来自Java Language Specification

15.18.1.1 字符串转换

任何类型都可以通过字符串转换转换为String类型。一个值 原始类型 T 的 x 首先被转换为参考值,就像通过 将其作为适当的类实例创建的参数 表达式:

如果 T 是布尔值,则使用 new Boolean(x)。如果 T 是 char,则使用 new 字符(x)。如果 T 是 byte、short 或 int,则使用 new Integer(x)。如果 T 很长,然后使用 new Long(x)。如果 T 是浮点数,则使用 new Float(x)。 如果 T 为 double,则使用 new Double(x)。然后这个参考值 通过字符串转换转换为String类型。现在仅供参考 值需要考虑。如果引用为空,则为 转换为字符串“null”(四个 ASCII 字符 n、u、l、l)。 否则,转换就像通过调用 不带参数的被引用对象的 toString 方法;但如果 调用 toString 方法的结果为 null,则字符串“null” 改为使用。

toString 方法由原始类 Object 定义;许多 类覆盖它,特别是 Boolean、Character、Integer、Long、Float、 双精度和字符串。

15.18.1.2 字符串拼接的优化

实现可以选择执行转换和连接 一步避免创建然后丢弃中间体 字符串对象。提高重复字符串的性能 连接,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 习语的简写。

【讨论】:

  • 它还将接受字符串上 + 运算符的正确操作数的任何类型。对于类实例,如果没有被继承的子类之一覆盖,它将使用 Object.toString() 方法。对于基本类型,它会做类似的事情。 Object.toString(),如果没有被覆盖并且数组实例的输出将不是很有用。使用例如1 + "" 将不起作用,因为整数实例不允许创建 StringBuilder,因此尝试进行整数加法 instea("" + 1 可以作为解决方法)。
  • 我应该指出,阅读字节码是回答这些问题的懒惰方式。如果你用(比如)C 或 C++ 尝试这种方法,你最终会得到各种不正确的想法......以及在你更改编译器开关等时破坏的脆弱程序等。了解 Java 的正确方法是阅读 JLS 或一本好的教科书……而不是字节码。
  • 我知道要查找字节码示例,因为我实际上已经阅读了 JLS,非常感谢。 ;-) 有时一个实际的例子会以更直接的方式传达要点,并且 OP 确实特别询问了它是如何完成的。
【解决方案2】:

Java 确实支持有限的运算符重载...对于具有某些(原始)操作数类型组合的内置运算符。主要案例有:

  • 算术运算符具有针对不同数字基本类型的重载。 '+' 运算符为字符串连接额外重载。

  • '&'、'|'、'^' 和 '!'运算符被重载用于(非短路)逻辑和按位积分运算。

但是,Java 支持任何形式的程序员定义的运算符或运算符重载。


说重载是“编译器魔法”或“编译器完成它”的答案没有抓住重点。 Java 语言规范说它是语言的一部分,它意味着什么,并且(几乎)规定了它应该如何实现。


正如另一个答案指出的那样,自动装箱/拆箱和其他东西(严格来说)是转换而不是重载。比如:

int j = ...
Integer i = ...
j = j + i;

这使用了 '+' 运算符的 (int, int) 重载,并使用自动拆箱将第二个操作数转换为 int。

byte b = ...
j = j + b;

这使用了 '+' 运算符的 (int, int) 重载,并使用强制转换将第二个操作数转换为 int。

请注意,JLS 指定了根据操作数的(初始)类型确定使用哪个运算符重载的规则。因此,在第二个示例中,JLS 要求使用“+”的 (int, int) 重载,而不是 (byte, byte) 重载。

【讨论】:

  • 我没有说编译器在做运算符重载。我说它在内部将短语转换为 StringBuilder.append() 调用。根本不是一回事。
  • @ScottAnderson - 但你仍然错过了真正的意义。 OP 需要知道这不仅仅是一些聪明的编译器。这是 JLS 的东西,他应该参考 JLS(或一本好的 Java 教科书)来真正理解这里发生了什么。 [阅读字节码是为懒惰的人准备的,他们懒得去阅读 JLS ;-) ]
  • 要点,见上文,但许多(大多数?)人从实际示例中学习得最好,而不是理论。从概念上讲,JLS 说会发生一件事,但接着说,“好吧,实际上你可以这样做以进行优化”。
【解决方案3】:

这都是编译器的魔法。你不能在 Java 中进行运算符重载。

交叉手指,希望这不是大错特错,让我投了 20 票

【讨论】:

  • 这没有错,但它会让你因为不确定而投反对票。
  • @owlstead 我只是有点笑。很确定我在这里所说的话,但我不会自大到认为即使是我最坚定的信念也不能建立在摇摇欲坠的基础之上。
  • 投票决不应该基于某人对他们答案的信心;相反,它们应该仅基于答案的有用性。在我看来,如果有人对他们的答案很有信心但仍然不确定,我想知道!这对我更有用。话虽如此,当您不确定时,您必须问自己需要做什么才能完全确定。在这种情况下,它将咨询 JLS。如果您想成为最有用的,您会希望在发布之前或之后立即执行。
猜你喜欢
  • 2012-02-11
  • 2012-01-12
  • 2015-01-01
  • 2012-09-04
  • 2010-10-20
  • 2010-10-21
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多