25

我使用 Java 已经很长时间了,我想知道这个函数是如何实现的System.out.print()工作的。

这是我的疑问:

作为一个函数,它在io包的某处有一个声明。但是 Java 开发人员是如何做到这一点的,因为这个函数可以接受任意数量的参数和任何参数类型,无论它们如何排列?例如:

System.out.print("Hello World");
System.out.print("My name is" + foo);
System.out.print("Sum of " + a + "and " + b + "is " + c);
System.out.print("Total USD is " + usd);

无论变量的数据类型是什么a, b, c, usd, foo或它们如何传递,都System.out.print()不会抛出错误。

对我来说,我从来没有参与过任何有这种要求的项目。提供,如果我得到这样的要求,我真的不知道如何解决它。

谁能向我解释它是如何完成的?

4

9 回答 9

28

System.out只是 的一个实例PrintStream。您可以查看它的JavaDoc。它的可变性基于方法重载(具有相同名称但具有不同参数的多个方法)。

此打印流将其输出发送到所谓的标准输出


在您的问题中,您提到了一种称为可变参数函数(或varargs)的技术。不幸的是,这不受 支持PrintStream#print,因此您一定将其误认为是其他内容。然而,在 Java 中实现这些非常容易。只需检查文档。


如果你好奇 Java 是如何知道如何连接非字符串变量的"foo" + 1 + true + myObj,那主要是 Java 编译器的责任。

当连接中不涉及变量时,编译器只是连接字符串。当涉及到变量时,将串联转换为StringBuilder#append链。结果字节码中没有连接指令;即+运算符(在谈论字符串连接时)在编译期间被解析。

Java中的所有类型都可以转换为字符串(int通过类中的方法Integerboolean通过类中的方法Boolean,通过它们自己的对象#toString,...)。有兴趣可以查看 StringBuilder 的源代码。


更新:我很好奇自己并检查了(使用javap)我的示例System.out.println("foo" + 1 + true + myObj)编译成什么。结果:

System.out.println(new StringBuilder("foo1true").append(myObj).toString());
于 2013-06-15T07:42:16.960 回答
3

即使它看起来好像System.put.print...()采用可变数量的参数,但它没有。如果你仔细观察,字符串只是简单地连接起来,你可以对任何字符串做同样的事情。唯一发生的事情是,您传入的对象通过调用该toString()方法的 java 隐式转换为字符串。

如果您尝试这样做,它将失败:

int i = 0;
String s = i;
System.out.println(s);

原因是,因为这里没有进行隐式转换。

但是,如果您将其更改为

int i = 0;
String s = "" + i;
System.out.println(s);

它有效,这也是使用时发生的情况System.put.print...()

如果你想在java中实现可变数量的参数来模仿C之类的东西,printf你可以像这样声明它:

public void t(String s, String ... args)
{
    String val = args[1];
}

这里发生的是传入一个字符串数组,其中包含所提供参数的长度。在这里,Java 可以为您进行类型检查。

如果你真的想要一个 printf 那么你必须这样做:

public void t(String s, Object ... args)
{
    String val = args[1].toString();
}

那么您是否必须相应地转换或解释论点。

于 2013-06-15T07:58:30.603 回答
3

了解 System.out.print 的工作原理是一个非常敏感的点。如果第一个元素是字符串,则 plus(+) 运算符用作字符串连接运算符。如果第一个元素是整数 plus(+) 运算符用作数学运算符。

public static void main(String args[]) {
    System.out.println("String" + 8 + 8); //String88
    System.out.println(8 + 8+ "String"); //16String
}
于 2017-04-04T07:27:28.390 回答
1

显然,编译器的制作方式令人困惑,尽管编译器开发人员认为他们增加了一些智能性。他们真正应该添加的真正聪明之处是查看整个参数并一致地解释 + 运算符。例如,System.out.println(1+2+"hello"+3+4);应该输出3hello7而不是3hello34

于 2017-09-07T12:43:44.593 回答
0

都是关于方法重载的。

println() 方法中的每种数据类型都有单独的方法

如果你传递对象:

打印一个对象,然后终止该行。此方法首先调用 String.valueOf(x) 以获取打印对象的字符串值,然后表现得好像它调用 print(String) 然后调用 println()。

如果您通过原始类型:

对应的原始类型方法调用

如果你通过 String :

对应的 println(String x) 方法调用

于 2013-06-15T07:49:23.243 回答
0

我认为您对该printf(String format, Object... args)方法感到困惑。第一个参数是格式字符串,它是强制性的,其余可以传递任意数量的Objects。

print()println()方法都没有这样的重载。

于 2013-06-15T07:49:25.430 回答
0

只要您选择要打印的内容,您就可以将任何内容转换为字符串。要求非常简单,因为Objet.toString()可以返回默认的哑字符串:package.classname + @ + object number.

如果您的打印方法应该返回 XML 或 JSON 序列化,则 toString() 的基本结果将不可接受。即使方法成功。

这是一个简单的例子来说明 Java 可以是愚蠢的

public class MockTest{

String field1;

String field2;

public MockTest(String field1,String field2){
this.field1=field1;
this.field2=field2;
}

}

System.out.println(new MockTest("a","b");

会打印一些东西package.Mocktest@3254487!即使您只有两个 String 成员,也可以实现打印

Mocktest@3254487{"field1":"a","field2":"b"}

(或者几乎它在调试器中的显示方式)

于 2013-06-15T09:36:04.250 回答
0

您提到的场景不是重载,您只是将不同的变量与字符串连接起来。

System.out.print("Hello World");

System.out.print("My name is" + foo);

System.out.print("Sum of " + a + "and " + b + "is " + c); 

System.out.print("Total USD is " + usd);

在所有这些情况下,您只调用 print(String s) 因为当某些内容与字符串连接时,它会通过调用该对象的 toString() 转换为字符串,并且直接连接原语。但是,如果您想知道不同的签名,那么 print() 会为各种参数重载。

于 2018-10-16T07:40:47.890 回答
0

@ikis,首先正如@Devolus 所说,这些不是传递给print(). 事实上,所有这些传递的参数都被连接起来形成一个字符串。所以print()不会涉及多个争论(又名 var-args)。现在还有待讨论的概念是如何print()打印传递给它的任何类型的论点。

要解释这一点 -toString()是秘密:

System是一个具有静态字段的类,out类型为PrintStream。所以你正在调用println(Object x)a 的方法 PrintStream

它是这样实现的:

 public void println(Object x) {
   String s = String.valueOf(x);
   synchronized (this) {
       print(s);
       newLine();
   }
}

如我们所见,它正在调用 String.valueOf(Object) 方法。这是按如下方式实现的:

 public static String valueOf(Object obj) {
   return (obj == null) ? "null" : obj.toString();
}

你看,这toString()就是所谓的。

所以无论从toString()那个类的方法返回什么,都会被打印出来。

正如我们所知,toString()它在Object类中,因此从 Object 继承了默认实现。

例如:请记住,当我们有一个toString()我们覆盖的类然后我们将该 ref 变量传递给print时,您会看到打印什么?- 这是我们从toString().

于 2018-10-16T07:10:42.860 回答