System
类
打开其源码,可以看到out
是它的一个静态变量,类型为PrintStream
:
public final static PrintStream out = null;
因此就可以通过System.out
的方式使用它。
但是这里的out
是个null
,如果直接使用,肯定是不行的,因为它是一个常量,一旦这个类被加载到虚拟机中,就不可再更改了。
于是我猜测可能是有了这个初始化类的方法,使得其在类的加载过程中被赋予了一个新的引用:
/**
* Initialize the system class. Called after thread initialization.
*/
private static void initializeSystemClass() {
props = new Properties();
initProperties(props); // initialized by the VM
sun.misc.VM.saveAndRemoveProperties(props);
lineSeparator = props.getProperty("line.separator");
sun.misc.Version.init();
FileInputStream fdIn = new FileInputStream(FileDescriptor.in);
//创建了一个文件输出流对象
FileOutputStream fdOut = new FileOutputStream(FileDescriptor.out);
FileOutputStream fdErr = new FileOutputStream(FileDescriptor.err);
setIn0(new BufferedInputStream(fdIn));
//主要是这里调用了设置out值的方法,这个方法是本地方法,无法查看源码
setOut0(newPrintStream(fdOut, props.getProperty("sun.stdout.encoding")));
setErr0(newPrintStream(fdErr, props.getProperty("sun.stderr.encoding")));
Terminator.setup();
sun.misc.VM.initializeOSEnvironment();
Thread current = Thread.currentThread();
current.getThreadGroup().add(current);
setJavaLangAccess();
sun.misc.VM.booted();
}
由上面的代码可以看到,在类的加载过程中初始化的时候,给out设置了一个新对象的引用。
具体的再来看newPrintStream(fdOut, props.getProperty("sun.stdout.encoding"))
:
/**
* Create PrintStream for stdout/err based on encoding.
*/
private static PrintStream newPrintStream(FileOutputStream fos, String enc) {
if (enc != null) {
try {
return new PrintStream(new BufferedOutputStream(fos, 128), true, enc);
} catch (UnsupportedEncodingException uee) {}
}
return new PrintStream(new BufferedOutputStream(fos, 128), true);
}
可以看到,使用文件输出流对象fos构造了一个缓冲区输出流对象,然后再构造了一个PrintStream对象,给了out
。这时out就有了新值,不再为空了。
我们这时再来看看这个文件输出流对象怎么来的:
FileOutputStream fdOut = new FileOutputStream(FileDescriptor.out);
可以看到是用了FileDescriptor.out
通过构造函数得到的,那就再看看它是什么:
/**
* A handle to the standard output stream. Usually, this file
* descriptor is not used directly, but rather via the output stream
* known as {@code System.out}.
* @see java.lang.System#out
*/
public static final FileDescriptor out = standardStream(1);
private static native long set(int d);
private static FileDescriptor standardStream(int fd) {
FileDescriptor desc = new FileDescriptor();
desc.handle = set(fd);
return desc;
}
到这里彻底终止于一个本地方法。但是我们通过这个方法名standardStream(int fd)
可以明白的知道这是一个标准输出流。
System.out
是怎么来的
- 正是通过这个标准输出流,我们得到了
FileDescriptor.out
, - 然后通过
new FileOutputStream(FileDescriptor.out)
构造出来了fos
对象, - 再然后通过
new BufferedOutputStream(fos, 128)
构造得到了bos
对象, - 又通过
new PrintStream(new BufferedOutputStream(fos, 128), true)
构造出一个打印流对象赋给了System.out
,因此它本质上就是一个PrintStream
对象。
我通过查看PrintStream
类的方法,发现了一系列被重载的println()方法,它会根据传进来参数的类型与个数调用不同的方法。
最后
最后我们使用刚才的一系列构造方法构造一个我们自己的PrintStream
类,并打印"hello world"
FileOutputStream fos = new FileOutputStream(FileDescriptor.out);
BufferedOutputStream bos = new BufferedOutputStream(fos, 128);
PrintStream ps = new PrintStream(bos, true);
ps.println("hello world");
因为FileDescriptor
类的standardStream()
方法是私有的,因此我们不能够调用以获取一个标准输出流,而只能通过FileDescriptor.out
获取。