最近一直在思考几个问题。我一直搞不清楚Java输入流机制下一系列流类的调用关系。直到昨天晚上我才有了一个比较清晰的头绪。
最开始我一直想不通,作为输入流的顶层类InputStream以及它的子类都具有read()方法,为什么在读取键盘输入的时候总是要以
BufferedReader br=new BufferedReader(new InputStreamReader(System.in))
这样的方式引入输入流,进而使用br变量的read()方法读取键盘输入。
经过几天的仔细研究,我基本搞清楚了BufferedReader、InputStreamReader以及System.in之间的关系。System.in默认为键盘输入,并且已经打开,而由于System.in是字节流并非字符流,所以如果想以字符流读取键盘输入的话,就需要使用BufferedReader这个流,但是这个流中没有参数为字节流对象的构造函数,于是起到传递工作要依靠一个中间类对象来完成,而InputStream的子类InputStreamReader类的对象恰好可以起到这个作用。因此,以上引入键盘输入的方式只是将输入流做了一个字节到字符的转化。事实上,通过实验,我发现直接调用System.in的read()方法同样可以读取键盘输入,代码如下:
public class TryReader {
public static void main(String[] args) throws IOException{
char c;
System.out.println("Enter characters,'q' to quit.");
do{
c=(char) System.in.read();
System.out.println(c);
}while(c!='q');
}
}
但是此时,问题又出现。System.in是java.lang.*包中System类预定义的字段,是InputStream的一个实例变量。当我在查看System.in这个字段的属性的时候,同时查看了InputStream类,发现在InputStream类里read()方法是一个抽象方法,也就是说,InputStream的read()方法没有方法体,所以显然正常的理解是System.in直接调用其read()方法是不合法的。而上述代码却可以正确运行。这矛盾的两个现实实在是让我很困扰。尽管其实这样的细枝末节可以不必考虑,依照一般的规范书写代码完全可以不理会这些Java源包里的无关紧要的定义,但是,我觉得这关乎对Java语言运行机制的深入理解,因此有必要好好考虑这个问题。
当这个问题困扰了我整整一天的时候我几乎要放弃求索答案的努力了,忽然提醒了我。Java的多态性有两种实现方式,其中之一就是用超类的实例变量引用子类的对象。这时候在该实例变量调用一个方法的时候,实际上调用的是此方法被所引用的子类对象所属的类重写后的版本。因此我推测,事实上这里的System.in只是一个引用了InputStream子类对象的实例变量,而真正表示键盘输入的是另外一个已经打开的流对象。
也许这个推测并不准确,但我想,根据Java的语法规则,这是我目前能找到的最好解释了。