说起System类,对我们来说既熟悉又陌生,熟悉的是我们刚接触java时就会用到这个类,甚至每天写代码也会用到这个类(System.out.println());陌生的是我们对这个类一知半解,它是如何实现的,为什么这样写就能在屏幕上输出东西。今天就让我们通过阅读它的源码揭开它那神秘的面纱。
深拷贝与浅拷贝:
浅拷贝:只是简单的对对象的引用进行复制。 深拷贝:拷贝对象的值和对象的内容。
System类是被final修饰的,不能被继承。
看到这些成员变量,有些是公有的,有些是私有的。这些对象被赋值为null,而且有的被final修饰不能被重新赋值。找遍整个类也没看到在哪里初始化这些对象。但我们使用System.out.println();的时候并没有报错啊。能够使用,就说明对象在谋个时刻被创建了,那到底这些对象是什么时候被创建的呢。怀着无比好奇的心理,在类中查找,忽然我看到了如下代码。
static { registerNatives(); private static native void registerNatives();静态代码块,类被加载的时候执行,会不会这些对象是在registerNatives方法中创建的呢。再一看这个方法是本地方法。看下注释,也看不懂,但隐约能看到VM啊,initializer啊,initializeSystemClass method之类的,找了一下,发现类中有一个initializeSystemClass方法,大概能猜到,是VM会执行这个方法。
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)); setOut0(newPrintStream(fdOut, props.getProperty("sun.stdout.encoding"))); setErr0(newPrintStream(fdErr, props.getProperty("sun.stderr.encoding"))); loadLibrary("zip"); Terminator.setup(); sun.misc.VM.initializeOSEnvironment(); Thread current = Thread.currentThread(); current.getThreadGroup().add(current); setJavaLangAccess(); sun.misc.VM.booted(); }在这个方法中可以看到刚才那些对象都是在这里被创建的。 lineSeparator = props.getProperty("line.separator");从系统中获取换行符。 setIn0(new BufferedInputStream(fdIn));设置标准输入流,也就是创建in对象,追溯到源头也就是创建了FileDescriptor.in对象,这个对象用于操作标准输入流,再往深了就到了虚拟机内部实现细节了,这里就有展开了,有兴趣的同学可以自行研究。 其它的对象像out、err创建过程同in类似。到这里我们终于明白了为什么执行System.out.println()时会向控制台输出东西了。
可以看到System类就一个构造函数,而且被私有化了,不允许我们创建对象。如果要使用这个类,只能使用类中的一些静态成员。这也是java中单例模式的雏形,把构造私有化,然后对外提供一个内部创建的唯一的对象,就形成单例了。
setIn:
public static void setIn(InputStream in) { checkIO(); setIn0(in); } //对setIo进行安全检查 private static void checkIO() { SecurityManager sm = getSecurityManager(); if (sm != null) { sm.checkPermission(new RuntimePermission("setIO")); } } //给System.in重新赋值,内部实现。 private static native void setIn0(InputStream in);给System.in对象重新赋值。即当调用了些方法后,我们再使用System.in的时候接收的信息不再是从键盘录入了,而是我们指定的文件或设备。点开函数可以看到,第一个函数是进行安全检查的,第二个函数是调用的本地方法。有人疑惑System.in变量不是被final修饰了吗,为什么还可以被修改。额...这个...它调用的是本地方法,由虚拟机内部实现,他是老大能不能改变他说了算。
setOut:
public static void setOut(PrintStream out) { checkIO(); setOut0(out); }给System.out重新赋值,原理同setIn。比如我们想让System.out.println()把内容输出到文件,就可以调用这个方法,指定输出文件。
setErr:
public static void setErr(PrintStream err) { checkIO(); setErr0(err); }给System.err重新赋值,原理同setOut。
console:
public static Console console() { if (cons == null) { synchronized (System.class) { cons = sun.misc.SharedSecrets.getJavaIOAccess().console(); } } return cons; }返回唯一的Console对象,这个对象是跟Java虚拟机相关联的。可以看到这里使用了synchronized代码块,避免多线程调用的时候返回多个Console对象。
inheritedChannel:
public static Channel inheritedChannel() throws IOException { return SelectorProvider.provider().inheritedChannel(); }返回从创建此 java 虚拟机的实体中继承的通道。如果有则返回,没有返回null。
SecurityManager设置与获取:
public static void setSecurityManager(final SecurityManager s) { try { s.checkPackageAccess("java.lang"); } catch (Exception e) { // no-op } setSecurityManager0(s); } public static SecurityManager getSecurityManager() { return security; }设置和获取java的安全管理器,当运行未知的Java程序的时候,该程序可能有操作如删除系统文件、重启系统等,为了防止运行恶意代码对系统产生影响,需要对运行的代码的权限进行控制,这时候就要启用Java安全管理器。
currentTimeMillis:
public static native long currentTimeMillis();获取当前系统时间的毫秒数。1秒=1000毫秒(ms);
nanoTime:
public static native long nanoTime();获取当前系统时间的纳秒数。1秒=1,000,000,000 纳秒(ns);
arraycopy:
public static native void arraycopy( Object src,//源数组 int srcPos, //源数组要复制的起始位置 Object dest, //目标数组 int destPos, //目标数组的起始位置 int length//复制的长度 );复制数组,从一个明确的数组中复制数组元素到另一个数组中。System.arraycopy采用的是浅复制,使用的时候注意一下。
identityHashCode:
public static native int identityHashCode(Object x);获取对象的hashCode,一般可通过对象的hashCode()方法获取对象的hashCode,但有些对象重写了hashCode()方法,那么想要获取对象的hashCode时就要使用本方法了。
props:
private static Properties props; private static native Properties initProperties(Properties props); public static void setProperties(Properties props) { SecurityManager sm = getSecurityManager(); if (sm != null) { sm.checkPropertiesAccess(); } if (props == null) { props = new Properties(); initProperties(props); } System.props = props; } public static Properties getProperties() { SecurityManager sm = getSecurityManager(); if (sm != null) { sm.checkPropertiesAccess(); } return props; }props是用于承装java的系统变量的,initProperties是初始化props对象,本地方法实现。setProperties()重新设置系统的Properties对象。getProperties()方法用于获取Properties对象。不论是设置还是获取都先会进行安全检查,看是否对系统属性有安全限制。获取Properties对象后可重新设值取值。
public static String setProperty(String key, String value) { checkKey(key); SecurityManager sm = getSecurityManager(); if (sm != null) { sm.checkPermission(new PropertyPermission(key, SecurityConstants.PROPERTY_WRITE_ACTION)); } return (String) props.setProperty(key, value); }设置系统属性到Properties对象中,如果已经存在则覆盖原有属性,并返回原有属性。该对象是全局的,在程序中到处都可以调用。
getProperty:
public static String getProperty(String key) { checkKey(key); SecurityManager sm = getSecurityManager(); if (sm != null) { sm.checkPropertyAccess(key); } return props.getProperty(key); }从Properties中获取系统属性,如果有则返回,如果没有返回null。
getProperty(String key, String def):
public static String getProperty(String key, String def) { checkKey(key); SecurityManager sm = getSecurityManager(); if (sm != null) { sm.checkPropertyAccess(key); } return props.getProperty(key, def); }从Properties中获取系统属性,如果有则返回,如果没有返回默认值,第二个参数为默认值。
clearProperty:
public static String clearProperty(String key) { checkKey(key); SecurityManager sm = getSecurityManager(); if (sm != null) { sm.checkPermission(new PropertyPermission(key, "write")); } return (String) props.remove(key); }用于清除系统属性,并返回原属性值。
lineSeparator:
public static String lineSeparator() { return lineSeparator; }获取系统的换行符widows系统返回"\r\n",UNIX系统返回"\n"。
getenv:
public static java.util.Map<String,String> getenv() { SecurityManager sm = getSecurityManager(); if (sm != null) { sm.checkPermission(new RuntimePermission("getenv.*")); } return ProcessEnvironment.getenv(); }获取java程序环境信息,如LOCALAPPDATA,JAVA_HOME等。返回的是一个Map对象。
exit:
public static void exit(int status) { Runtime.getRuntime().exit(status); }结束当前正在运行中的java虚拟机。参数0代表正常退出,非0代表异常退出。
gc:
public static void gc() { Runtime.getRuntime().gc(); }该方法用于告知虚拟机进行垃圾回收。垃圾收集器将运行以回收未使用的内存空间。注意这一步只是通知虚拟机要进行垃圾回收操作,虚拟机并不一定立即进行回收操作。
runFinalization:
public static void runFinalization() { Runtime.getRuntime().runFinalization(); }我们知道Object对象不有一个finalize方法,当进行垃圾回收时,对象被回收前会执行finalize方法。调用runFinalization方法运行处于挂起终止状态的所有对象的终止方法。Java虚拟机已经尽最大努力去完成所有未执行的终止方法。
load:
public static void load(String filename) { Runtime.getRuntime().load0(Reflection.getCallerClass(), filename); }用于装载指定名称filename的系统库文件。filename必需是绝对路径。
loadLibrary:
public static void loadLibrary(String libname) { Runtime.getRuntime().loadLibrary0(Reflection.getCallerClass(), libname); }加载系统中的本地库,如果是Windows系统加载的是xxx.dll,如果是unix系统加载的是xxx.so,注意该方法不能包含文件的扩展名,但该文件必需在JVM属性java.library.path所指向的路径中。
mapLibraryName:
public static native String mapLibraryName(String libname);将一个库名称映射到特定于平台的、表示本机库的字符串中。有点抽象,比如你想映射mapLibraryName这个库名,映射到Windows平台就是mapLibraryName.dll映射到Unix平台就是mapLibraryName.so