分析一下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)

posted @ 2022-02-26 16:02  dog_IT  阅读(162)  评论(0编辑  收藏  举报