分析一下System.out.println(xxx)的几个重载方法
问题一:下面调用什么方法?打印结果是什么?
int[] arr = new int[]{1,2};
System.out.println(arr);
打印结果:[I@a57993
调用PrintStream的println(Object x)重载方法,除了参数为char[]的其他数组参数都会调用这个方法
public void println(Object x) {
String s = String.valueOf(x);
synchronized (this) {
print(s);
newLine();
}
}
再看String.valueOf()也有很多的重载方法
public static String valueOf(Object obj) {
return (obj == null) ? "null" : obj.toString();
}
obj为int[]数组,而所有类型的数组又没有重写toString()方法,默认调用Object的toString()方法,返回的是内存地址值。
public String toString() {
return getClass().getName() + "@" + Integer.toHexString(hashCode());
}
public void print(String s) {
if (s == null) {
s = "null";
}
write(s);
}
private void write(String s) {
try {
synchronized (this) {
ensureOpen();
textOut.write(s);
textOut.flushBuffer();
charOut.flushBuffer();
if (autoFlush && (s.indexOf('\n') >= 0))
out.flush();
}
}
catch (InterruptedIOException x) {
Thread.currentThread().interrupt();
}
catch (IOException x) {
trouble = true;
}
}
字符输出流,将内存地址这个字符串输出。
所以打印结果为:[I@a57993
System.out.println(arr.length);//数组是length属性,字符串是length()方法,集合是size()方法。
问题二:下面调用什么方法?打印结果是什么?
char[] arr2 = new char[]{'a','b'};
System.out.println(arr2);
打印结果:ab
调用PrintStream的println(char x[])重载方法
public void println(char x[]) {
synchronized (this) {
print(x);
newLine();
}
}
public void print(char s[]) {
write(s);
}
private void write(char buf[]) {
try {
synchronized (this) {
ensureOpen();
textOut.write(buf);
textOut.flushBuffer();
charOut.flushBuffer();
if (autoFlush) {
for (int i = 0; i < buf.length; i++)
if (buf[i] == '\n')
out.flush();
}
}
}
catch (InterruptedIOException x) {
Thread.currentThread().interrupt();
}
catch (IOException x) {
trouble = true;
}
}
字符输出流,将字符数组的所有字符输出。
所以打印结果为: ab
从上面两个问题的分析可以得出结论
println()这个方法,内部都是先将参数进行字符化,然后再通过xxxWrite字符输出流将参数字符化后的内容输出。
当参数是char[],这就不需要字符化了,因为String的底层数据存储就是char[],所以直接调用write(char[] s)输出即可。
当参数是非char[]的其他类型数组,就必须先进行字符化,调用String.valueOf(Object obj),返回字符串,再调用write(String s)进行输出。
问题三:下面调用什么方法?打印结果是什么?
char[] arr2 = new char[]{'a','b'};
System.out.println(arr2);
System.out.println("arr2:" + arr2);
第一个打印:ab
第二个打印:arr2:[C@1b84c92
第一个打印上面讲过,调用的是println(char x[]),输出全部的字符。
第二个打印,因为字符串拼接,所以参数是String,调用的是PrintStream重载方法println(String x)
public void println(String x) {
synchronized (this) {
print(x);
newLine();
}
}
public void print(String s) {
if (s == null) {
s = "null";
}
write(s);
}
private void write(String s) {
try {
synchronized (this) {
ensureOpen();
textOut.write(s);
textOut.flushBuffer();
charOut.flushBuffer();
if (autoFlush && (s.indexOf('\n') >= 0))
out.flush();
}
}
catch (InterruptedIOException x) {
Thread.currentThread().interrupt();
}
catch (IOException x) {
trouble = true;
}
}
问题四:下面调用什么方法?打印结果是什么?
char[] arr3 = null;
System.out.println("arr3:" + arr3);
System.out.println(arr3);
第一个打印:arr3:null
第二个:报错:Exception in thread "main" java.lang.NullPointerException
第一个打印,调用的是PrintStream重载方法println(String x)
第二个打印,调用的是println(char x[]),但是去看它的方法实现,发现并没有去考虑char[]参数为空的情况。
private void write(char buf[]) {
try {
synchronized (this) {
ensureOpen();
textOut.write(buf);
textOut.flushBuffer();
charOut.flushBuffer();
if (autoFlush) {
for (int i = 0; i < buf.length; i++)
if (buf[i] == '\n')
out.flush();
}
}
...
}
仔细查找报错的位置,发现是 textOut.write(buf);,具体方法是
public void write(char cbuf[]) throws IOException {
write(cbuf, 0, cbuf.length);
}
再具体就是 cbuf.length,我们传入的char[]数组是null,访问length属性就会报空指针异常!
所以,鄙人陋见,这可能是当初大佬们的一个小忽视吧,如果提前在println()或者println()方法内考虑到传入的数组是null,代码也会更健壮一些,你觉得呢?
所以避坑的要点就是:当参数为char[]时,一定要先判断它是非null的。
问题五:下面调用什么方法?打印结果是什么?
System.out.println(123);
很显然,这个真是太简单了,打印就是123嘛。调用的是PrintStream的println(int i)
那么底层是如何实现的呢?
传入的参数是基本类型,这里我们就分析println(int i)
public void println(int x) {
synchronized (this) {
print(x);
newLine();
}
}
public void print(int i) {
write(String.valueOf(i));
}
public static String valueOf(int i) {
return Integer.toString(i);
}
看到这里,可以印证上面的结论: println()这个方法,内部都是先将参数进行字符化,然后再通过xxxWrite字符输出流将参数字符化后的内容输出。
就算参数是基本数据类型, 也是需要先将其字符化,底层是通过它对应的包装类调用类方法toString(int i),注意这个特别指出是类方法,也就是包装类自己定义的static方法,而不是重写Object的toString()方法。不过有意思的是,包装类确实重写了Object的toString()方法,内部实现就是调用了自己的类方法toString(int i)
重写Object的toString()方法
public String toString() {
return toString(value);
}
Integer定义的类方法toString(int i)
public static String toString(int i) {
if (i == Integer.MIN_VALUE)
return "-2147483648";
int size = (i < 0) ? stringSize(-i) + 1 : stringSize(i);
char[] buf = new char[size];
getChars(i, size, buf);
return new String(buf, true);
}
关于包装类的类方法toString(int i)分析,可以看我的下篇--分析一下String.valueOf()有很多的重载方法,分析String.valueOf(int i)