【声明】
final可以修饰类、函数、变量。
特点如下:
(1)final标记的类不能被继承。
(2)final标记的方法不能被子类重写。
(3)final标记的变量(成员变量或局部变量)即成为常量,只能赋值一次。
(3.1)final 标记的成员变量必须在声明的同时赋值,
如果在声明的时候没有赋值,那么只有一次赋值的机会,而且只能在构造方法中显式赋值,然后才能使用。
(3.2)final标记的局部变量可以只声明不赋值,然后再进行一次性的赋值。
(4)当在描述事物时,一些数据的出现值是固定的,那么这是为了增强阅读性,都给这些值起个名字方便阅读。而这个值不需要改变,所以加上final修饰。
【补充】
1、作为常量,常量的书写规范所有字母都大写,如果有多个单词组成,但此间通过_连接。
2、内部类定义在内部的局部位置上时,只能访问该局部被final修饰的局部变量。
3、接口中的属性都是常量,修饰符都是固定的,必须是public static final。
【本题答案】
在System中 对静态常量的out的定义为:
public final static PrintStream out = null;
而且类中还有setOut的方法为- public static void setOut(PrintStream out) {
- checkIO();
- setOut0(out);
- }
复制代码 这就说明了输出流out可以被重定向!(这句话不错)
但是final类型的变量一旦初始化不是不能被修改吗?(这句话也没错)
那么错在哪里呢?【答案要从java.System的源代码中去找】
【答案】
首先:
我们先来看
public final static PrintStream out = null;
为什么out要定义为null呢,null是无法调用println()方法的;
因此我们再来找原因:
其实System类中刚开始的时候out确实是null,那么什么时候能把out指向标准输出的呢?
答案是在initializeSystemClass()这个函数。- /**
- * Initialize the system class. Called after thread initialization.
- */
- private static void initializeSystemClass() {
- props = new Properties();
- initProperties(props);
- sun.misc.Version.init();
- FileOutputStream fdOut = new FileOutputStream(FileDescriptor.out);
- .................
- setOut0(new PrintStream(new BufferedOutputStream(fdOut, 128), true));
- ...............
- ...............
复制代码 接着找到FileDescriptor这个类中的静态成员 out:(用自身定义自身)
public static final FileDescriptor out = standardStream(1);
以及standardStream()方法:- private static FileDescriptor standardStream(int fd) {
- FileDescriptor desc = new FileDescriptor();
- desc.handle = set(fd);
- return desc;
- }
复制代码 这时候返回了 handle为1的FileDescriptor;
在传统的unix的系统中,文件描述符为0,1,2分别表示为标准输入,标准输出和错误输出。
最后调用了setOut0()方法:
private static native void setOut0(PrintStream out);
setOut0()是native方法,所以再也追踪不到以后的细节了,而到这时候,还是没找到什么时候能把out指向标准输出。
但是:
setOut()调用了setOut0(),而setOut0()的方法全名显示为
private static native void setOut0(PrintStream out);
我们知道setOut方法是重置输出流的对象的,因此虽然我们看不到setOut0的细节,但是因为setOut调用了setOut0,我们可以大致猜到setOut0是通过调用底层的代码实现对out的流的重定位的,而initializeSystemClass()这个函数也调用了setout0将fd为1的文件封装成文件流,再封装成缓冲流,再封装成打印流,最后通过setout0将out与这个流绑定。
【所以到这里我们找到了最终答案】
【System中虽然我们不能对final FileDescriptor out重新赋值,但是System类中initializeSystemClass()这个方法对System类进行初始化的时候,其中的setOut0()方法通过调用底层的代码实现了对out的流的重定位的】 |