A股上市公司传智教育(股票代码 003032)旗下技术交流社区北京昌平校区

 找回密码
 加入黑马

QQ登录

只需一步,快速开始

© HM李帅 中级黑马   /  2013-3-13 13:54  /  2092 人查看  /  8 人回复  /   0 人收藏 转载请遵从CC协议 禁止商业使用本文

本帖最后由 HM李帅 于 2013-3-13 16:13 编辑

在System中 对静态常量的out的定义为
public final static PrintStream out = null;
但是底下还有一个setOut的方法  为
   public static void setOut(PrintStream out) {
        checkIO();
        setOut0(out);
    }

final定义的常量不是不可以被修改吗  这里的操作为什么可以修改  因为若是不可以out不就一直是null了吗

8 个回复

倒序浏览
此out非彼out,函数里的out是个局部变量,生命周期只存在于setOut方法  建议多看看,全局变量和局部变量的生命周期。
回复 使用道具 举报
你怎么修改的?你就列出了一个方法,并没有改变out 变量所指向的对象!

final修饰的对象引用是不能改变,唯一可以修改的就是对象本身的内容!
回复 使用道具 举报
沉默de羔羊 发表于 2013-3-13 14:14
你怎么修改的?你就列出了一个方法,并没有改变out 变量所指向的对象!

final修饰的对象引用是不能改变, ...

很简单  jdk已经把final的out定义为null了
如果之后没做过修改
你程序里的 System.out   .print( )是怎么做到的???
回复 使用道具 举报
HM李帅 发表于 2013-3-13 14:35
很简单  jdk已经把final的out定义为null了
如果之后没做过修改
你程序里的 System.out   .print( )是怎么 ...

我怎么感觉你在偷换概念呢,他们两个out虽然都是printStream流创建出来的对象,但是归属的类不相同啊!

System.out的归属类是System.

而你定义的out只是自己定义的一个类里的一个属性!
回复 使用道具 举报
沉默de羔羊 发表于 2013-3-13 15:05
我怎么感觉你在偷换概念呢,他们两个out虽然都是printStream流创建出来的对象,但是归属的类不相同啊!

...

这不是我定义的 实在java源码里面粘出来的。。。
不信你可以自己去看jdk
回复 使用道具 举报
HM李帅 发表于 2013-3-13 15:11
这不是我定义的 实在java源码里面粘出来的。。。
不信你可以自己去看jdk

我刚才看了jdk 下面的System类源码!

其中out生成的过程:
    public final static PrintStream out = nullPrintStream();
    private static PrintStream nullPrintStream() throws NullPointerException {
        if (currentTimeMillis() > 0) {
            return null;
        }
        throw new NullPointerException();
    }

而public static native long currentTimeMillis();这里native 很奇怪!就找不到!

我想问的是,你直接修改了JDK源码?然后运行的?
回复 使用道具 举报
但是底下还有一个setOut的方法  为
   public static void setOut(PrintStream out) {
        checkIO();
        setOut0(out);
    }
这个out只是局部变量
回复 使用道具 举报
【声明】
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的方法为
  1.    public static void setOut(PrintStream out) {
  2.         checkIO();
  3.         setOut0(out);
  4.     }
复制代码
这就说明了输出流out可以被重定向!(这句话不错)
但是final类型的变量一旦初始化不是不能被修改吗?(这句话也没错)

那么错在哪里呢?【答案要从java.System的源代码中去找】
【答案】



首先:
我们先来看
public final static PrintStream out = null;
为什么out要定义为null呢,null是无法调用println()方法的;
因此我们再来找原因:

其实System类中刚开始的时候out确实是null,那么什么时候能把out指向标准输出的呢?
答案是在initializeSystemClass()这个函数。
  1. /**
  2. * Initialize the system class. Called after thread initialization.
  3. */
  4. private static void initializeSystemClass() {
  5. props = new Properties();
  6. initProperties(props);
  7. sun.misc.Version.init();
  8. FileOutputStream fdOut = new FileOutputStream(FileDescriptor.out);
  9. .................
  10. setOut0(new PrintStream(new BufferedOutputStream(fdOut, 128), true));
  11. ...............
  12. ...............
复制代码
接着找到FileDescriptor这个类中的静态成员 out:(用自身定义自身)
public static final FileDescriptor out = standardStream(1);

以及standardStream()方法:
  1. private static FileDescriptor standardStream(int fd) {
  2. FileDescriptor desc = new FileDescriptor();
  3. desc.handle = set(fd);
  4. return desc;
  5. }
复制代码
这时候返回了 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的流的重定位的】

评分

参与人数 1技术分 +1 收起 理由
冯海霞 + 1

查看全部评分

回复 使用道具 举报
您需要登录后才可以回帖 登录 | 加入黑马