System.out.print()如何工作?

26

我已经使用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()都不会抛出错误。
对我来说,我从未参与过任何要求像这样的项目。如果我得到这样的需求,我真的不知道该怎么解决。
有人能向我解释一下吗?

7
有一个错误,因为System.out.printPrintWriter)肯定不支持你的第二至第四行代码,它没有这样的方法。 - SJuan76
11
System.out.print() 从不会抛出错误?真的吗?在你的例子中,它会抛出错误,除了第一行之外,其他每行都会抛出错误。因为逗号在 System.out.print() 中不被接受,逗号用于分隔不同的参数,而 System.out.print() 只接受一个参数。字符串之间的连接是通过使用 + 运算符来完成的。 - BackSlash
1
“,”是打字错误吗?应该用“+”吗? - pinkpanther
1
Java文档肯定能为您解答这个问题,而谷歌大叔也可能知道。 - Sello
9个回答

28

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

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


在您的问题中,您提到了一种称为可变参数函数(或varargs)的技术。不幸的是,PrintStream#print不支持它,因此您可能将其与其他内容混淆了。但是,在Java中很容易实现这些。 请查看文档。


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

当在连接中没有变量时,编译器只是简单地将字符串连接起来。当涉及到变量时,连接被转换为StringBuilder#append链。在生成的字节码中没有连接指令;即在编译期间解决了+运算符(在谈论字符串连接时)。

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


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

System.out.println(new StringBuilder("foo1true").append(myObj).toString());

完整的说,虽然允许参数重载,但不允许返回值重载。+1。 - fge
我不明白为什么这个答案会被点赞,因为它是错误的。system.out.print并没有使用可变数量的参数,它只是接收一个字符串。 - Devolus
4
@Devolus可能是因为我在说它不支持它?请在点踩之前先阅读。 - Pavel Horal
所以这与System是一个框架,out是一个类,以及println是一个方法无关,对吧? :) - mfaani

3
尽管看起来 System.put.print...() 接受可变数量的参数,但实际上不是这样的。如果您仔细观察,字符串只是简单地连接起来,您可以对任何字符串执行相同的操作。唯一发生的事情是,您传递的对象会被 Java 隐式转换为字符串,通过调用 toString() 方法。
如果您尝试这样做,它将失败:
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();
}

那么你需要相应地转换或解释参数。

3

理解 System.out.print 如何工作是一个非常敏感的问题。如果第一个元素是字符串,则加号(+)运算符作为字符串连接运算符使用。如果第一个元素是整数,则加号(+)运算符作为数学运算符使用。

public static void main(String args[]) {
    System.out.println("String" + 8 + 8); //String88
    System.out.println(8 + 8+ "String"); //16String
}

1
明显地,编译器虽然开发者们认为添加了一些机智,但是它的制作方式相当令人困惑。真正应该添加的机智是要查看整个参数并始终一致地解释 + 运算符。例如,System.out.println(1+2+"hello"+3+4); 应该输出 3hello7 而不是 3hello34

你为什么说3hello34不一致?加法是从左到右结合的,每个操作具有相等的优先级。你认为3+4应该比前面的加法优先级更高,这将是不一致的。这就像说1 + 2 / 0 + 3应该等于1而不是除以零的错误。(当然,字符串连接可能比加法低优先级,但它没有。而且,老实说,对于同一运算符,根据操作数的类型具有不同的优先级规则是混乱的。) - FeRD

0

@ikis,首先像@Devolus所说的那样,这些不是传递给print()的多个参数。实际上,所有这些传递的参数都被连接起来形成一个单独的字符串。因此,print()不接受多个参数(也称为可变参数)。现在要讨论的概念是print()如何打印传递给它的任何类型的参数。

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

System是一个类,具有静态字段out,类型为PrintStream。因此,您正在调用PrintStreamprintln(Object x)方法。

它的实现方式如下:

 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()方法,然后将该引用变量传递给print时,您会看到什么被打印出来?- 它是我们从toString()方法中返回的内容。


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"}

(或者基本上像在调试器中显示的那样)

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()重载了各种参数。

0

这都与方法重载有关。

println()方法中为每种数据类型提供了单独的方法。

如果传递对象:

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

如果传递原始类型:

对应的原始类型方法调用

如果传递字符串:

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


0

我认为你对printf(String format, Object... args)方法有些困惑。第一个参数是格式字符串,这是必须的,其余的你可以传递任意数量的Objects。

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


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接