分析一下System.out.println(xxx)的几个重载方法

本文通过分析System.out.println()在不同参数类型下的行为,揭示其内部字符化和输出的过程。针对char[]和其他类型数组,解释了转换和输出的原理。同时强调,当参数为char[]时,应确保非null以避免错误。此外,讨论了基本数据类型如何通过包装类的类方法toString(int i)进行字符化。

问题一:下面调用什么方法?打印结果是什么?

	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)

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值