零基础学Java第三节(基本输入输出)

本篇文章是《零基础学Java》专栏的第三篇文章,文章采用通俗易懂的文字、图示及代码实战,从零基础开始带大家走上高薪之路!

Java程序的命令行参数

我们可以利用Java程序执行时的命令行参数进行数据的输入。所谓命令行参数,是在执行Java类的命令中,跟在Java类后面的若干由空格分隔的字符序列。如后图。

程序代码

/*
 * HelloWorldArgs.java
 */

/**
 * HelloWorld 在标准输出设备中输出“Hello World!”
 */
public class HelloWorldArgs {

       public static void main(String[] args) {
           System.out.println("Hello World!" + args[0] + args[1]); // Display the string.
       }//end method main

}//end class HelloWorldArgs

释义

第一,在这段程序中,我们用到了所有三种注释形式。

第二,在用println方法输出数据的时候,我们用到了args[0]args[1],这里我们可以看到,这种形式同C语言中的数组的使用是不是很像?事实上,args就是数组变量,它是main的形式参数,args数组中元素的类型为String(Java中的字符串类型名,它是一个类,属于引用类型,不是基本类型)。

第三,println方法的实参为:"Hello World!"+args[0]+args[1] 这是一个表达式(关于表达式我们见附录1),其中的+不是算术上的加法,我们看到+左右都为字符串,那么这里+的作用就是连接它左右的字符串,构成新的字符串,它是一个字符串连接运算。

既然是表达式,那么就必须进行计算并产生结果,那么参与运算的args[0]、args[1]的值又是从哪里来的呢?

它们的值从命令行上来,如图所示执行该类:

该图例中,有三个命令行参数,所以args数组的大小为3。因为Java的下标从0开始,所以,args的最后一个元素的下标为2。与C语言相比较,C语言不对数组下标是否越界进行检查,而Java则要求下标不得越界。在本程序中args的最大下标为1,那么,如果我们在执行这个java程序的时候,命令行参数少于2个,则会出错,如图:

这里只有一个命令行参数,而我们在程序中需要2个命令行参数,所以会产生如图所示的异常:java.lang.ArrayIndexOutOfBoundsException,我们看lang后面的标识符,它是java.lang这个包(包是若干相关类的集合)中的一个类,这个类代表数组下标越界异常。

第三行的提示:at HelloWorldArgs.main(HelloWorldArgs.java:11),表示错误发生在HelloWorldArgs这个类的main方法中,在源文件HelloWorldArgs.java文件的第11行。

大家在以后学习Java的过程中,要学会看出错提示。

使用BufferedReader进行输入

BufferedReader类是一种Java标准类中的一个负责从字符输入流中读取文本的类。为了提高效率,对流中的字符进行缓冲,从而实现字符、数组和行的高效读取的一个类。这个类的全称java.io.BufferedReader,其中java.io是一个包(package)名,顾名思义,这个包是同java的输入输出相关的 类及子包的集合。

什么是包呢?这个问题,我们这里要略知一二。简单的讲,你可以把包想象为一个箱子,这个箱子中放着功能相关的东西(类),还可以在大箱子中放小箱子(子包)。所以,大家可以知道上面的java.iojava是大箱子,io为小箱子,BufferedReader这个类是io这个子包中的一个功能类。这些包以何种形式保存的呢?答案是,以文件夹的形式保存,如图:

在JDK的安装目录中,有个压缩文件:src.zip,所有的JDK的源代码均在这里,有志向研究java的同学,可以读读这些代码,看看大师和菜鸟之间的区别。我们用解压软件打开可以看到整个JDK的包结构,就本图,我们可以看到BufferedReader这个类的源码就在那里,而且io下再无子包。

大家可能说,源代码是无法执行的,那可以执行的.class文件在哪里呢?在JDK的 jre\lib文件夹下有个rt.jar文件,大家用解压软件打开它,就可以看到JDK中所有的.class文件均在这里。如图:

这里大家要注意包名的命名规则,包名中所有的字母均为小写字母,这是大家约定俗成的规则。

BufferedReader

原理

这个类的功能很强大,它的功能不仅仅局限在从键盘中输入数据。目前,我们先只学习利用该类如何从键盘输入数据。

首先,如果我们希望BufferedReader这个类为我们服务,就必须创建这个类的对象(不是所有的类都必须创建对象才能用,比如我们常用的System类,就是直接用了,怎么区分,我们以后慢慢了解,这里我们记着我们学过的每个类的使用方式),如下代码:

BufferedReader br = new BufferedReader(new InputStreamReader(System.in));

这行代码中,我们通过 new BufferedReader(实参)创建了一个对象(什么?实参?难道BufferedReader(实参)是方法调用?不错,这里就是方法调用,只不过这里的这个方法是BufferedReader的构造方法。所谓构造方法,我们先简单理解为创建对象时会执行的方法,该方法不同于普通方法,它没有返回值类型,甚至连void都没有,构造方法只能由new使用,不能把构造方法当作普通方法调用),这个对象的引用放在了 br 这个变量中,注意不是这个对象放在br中,而是这个对象的引用放在了br中。

那么大家一定疑问了,什么是引用?如果对比C语言,这里引用相当于C中的指针,只不过Java中的引用值本身是不能像C中的指针一样进行数值的操作的,比如不能像C一样对指针进行值的加减等。如果以内存存储的角度来看,br变量是保存在栈中的,而对象是保存在堆中的,所以,br中只是保存了对象的地址,这里我们把它叫做引用。引用就代表那个对象。我们可以很自然的通过引用对对象进行操作。这就像电视机与遥控器的关系,电视机就是对象,遥控器就是引用,遥控器丢了就不能操纵电视机了,虽然电视机还在那里,所以引用应该保存起来,本例中新建对象的引用就是保存在br中。

大家可能还有疑问,“类”和“对象”又是什么关系?我们再做个类比,“类”就是盖楼的图纸,“对象”就是盖成的大楼。有了图纸,不代表这个“类”就可以用了,只有楼盖好了,我们才能使用楼(实体存在的楼)的功能。所以,我们在写Java程序的时候都是先设计类,再创建该类的对象(还是那句话,有些类不需要创建对象就可使用)。

那么,这行语句中的 new InputStreamReader(System.in)又是什么用呢?我们看到new就知道是创建对象,这里创建的是 InputStreamReader这个类的对象,创建完成的对象的引用,作为实参传递给创建BufferedReader类对象的构造方法,然后这个InputStreamReader对象就由br引用的BufferedReader对象使用了(那到底怎么用的呢?由BufferedReader封装起来了,那是个黑盒子,大家没必要知道)。

为什么必须有个InputStreamReader对象呢?这是因为在Java系统中,数据传递的方式是以“流”的形式进行的,流是在Java系统中看待数据传递的一种模式,就像水管中的水,只不过这根水管很细,在水管中流动的数据排队流过。在Java中有两种数据流,一种是“字节流”,一种是“字符流”,也就是说这根水管中流过的数据是字节还是字符。BufferedReader处理的数据必须是字符流,而代表标准输入设备-键盘的Systems.in,它处理的数据是字节流输入数据,怎么对接呢?这就需要InputStreamReader进行字节流向字符流的转换,到这里大家是不是明白了呢?如图:那至于InputStreamReader是怎么转换的,我们就不需要知道了,我们只要知道它能转换就行了。

InputStreamReader类的全称为java.io.InputStreamReader,前面的是包名不消说了。

接下来我们就可以使用BufferedReader的对象来对数据进行处理了。怎么做呢?就是通过对象来调用对象中的方法来完成相应的功能,这里我们先看看BufferedReader这个类中常用的方法。

返回值类型 方法名及参数 方法的功能 可能发生的异常
BufferedReader(Reader in) 它是构造方法,创建一个使用默认大小输入缓冲区的缓冲字符输入流对象。参数为字符流对象
BufferedReader(Reader in,int sz) 它是构造方法,创建一个由sz指定大小输入缓冲区的缓冲字符输入流对象。
void close() 关闭该流并释放与之关联的所有资源。 IOException
int read() 读取单个字符,读取到的字符编码以int值返回,因为字符的编码为两个字节,所以,返回值的范围为0~65535(0x0000 ~ 0xffff),如果返回值为-1,则表示已到达流的末尾,流中再没有数据。 IOException
int read(char[] cbuf, int off, int len) 将字符依次读入到 cbuf 数组的 off 下标(数组的下标从0开始)开始处,期望都len个字符,但实际因为流中可能没有那么多字符,实际读取的字符个数以方法的返回值返回。返回值如果是-1,表示已到达流的末尾。 IOException
String readLine() 在流中读取一个文本行,行是以换行 ('\n')、回车 ('\r') 或回车后直接跟着换行终止的。这个文本行以字符串的形式返回,注意字符串中不含有行结束符。返回值为null表示到达流的末尾 IOException
boolean ready() 判断此流是否已准备好被读取。 IOException
void mark(int readAheadLimit) 标记流中的当前读取位置。readAheadLimit表示在做过该标记以后,可读取字符的数量,如果标记以后再读取字符的数量超出了改数值以后,执行reset方法可能会失败。readAheadLimit的值不应大于输入缓冲区的大小,如果大于输入缓冲区的大小了,系统会重新再分配一个新的缓冲区。所以应该小心使用readAheadLimit这个实参值,不要太大。 如果发生I/O错误,会抛出IOException;如果readAheadLimit<0,会抛出IllegalArgumentException,该异常不需处理。
boolean markSupported() 判断此流是否支持 mark() 操作(它一定支持)。
void reset() 将流的输入位置重置到最后一次做的标记处。 IOException
long skip(long n) 从当前读取位置跳过n个字符,n为期望值,实际跳过的字符数以返回值的形式返回。n的值应该为非负数。 如果发生I/O错误,会抛出IOException;如果n为负数,则抛出IllegalArgumentException,该异常不需处理。

程序示例

功能需求1:

从键盘上输入一串字符,以回车作为结束,然后把输入的字符再回显到屏幕上

  • 需求分析:从需求,我们知道,我们只需要输入一行内容,而且输入数据为字符,我们利用前面提到的BufferedReader来进行实现。

  • 程序编码:

    public class TestBufferedReader1{
        public static void main(String[] args) {
            //创建一个BufferedReader对象,准备进行字符串的输入
            BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
            //读入输入行,结果保存在str中
            String str = br.readLine();
            //将str中的内容输出到屏幕
            System.out.println(str);
        }
    }

    大家把上面的代码用Sublime保存为TestBufferedReader.java,在解决好字符编码问题以后,进行编译,会怎么样?出错了?!出错信息是: 怎么办?

    我们要仔细看出错信息显示的是什么,找不到符号...,找不到哪些呢?图中的那些数字表示出错的行号,^指定的为出错的具***置,分别是BufferedReader、InputStreamReader

    OMG!怎么办?

    大家还记得我们用的这两个类的全称是什么?回忆一下。好了,我们在用这两个类的时候,如果不加上包名,java编译器就不认识,那我们加上试试,代码如下:

    public class TestBufferedReader2{
        public static void main(String[] args) {
            //创建一个BufferedReader对象,准备进行字符串的输入
            java.io.BufferedReader br = new java.io.BufferedReader(new java.io.InputStreamReader(System.in));
            //读入输入行,结果保存在str中
            String str = br.readLine();
            //将str中的内容输出到屏幕
            System.out.println(str);
        }
    }

    编译结果如图:原来的找不到符号的错误没有了,但是怎么又多出一个“未报告的异常错误”问题呢?什么时候才能没有问题啊?!

    大家稍安勿躁,遇到问题,我们不怕,对于初学者来讲,遇到越多的问题,我们应该越高兴,因为,每修正一个错误,我们就学到了一点东西。好吧,我们来看看错误提示,在代码的第六行有个“ 未报告的异常错误IOException; 必须对其进行捕获或声明以便抛出”错误,问题给你指出来了,而且还有解决方案,挺好。但是怎么“捕获”或者“声明”呢?

    1. 我们先看看怎么声明:我们在main方法的后面加上throws IOException(这里throws是加字母s的哦),试试看,代码如下:

      public class TestBufferedReader3{
         public static void main(String[] args) throws IOException{
             //创建一个BufferedReader对象,准备进行字符串的输入
             java.io.BufferedReader br = new java.io.BufferedReader(new java.io.InputStreamReader(System.in));
             //读入输入行,结果保存在str中
             String str = br.readLine();
             //将str中的内容输出到屏幕
             System.out.println(str);
         }
      }

      编译后,如图:

      怎么又成找不到符号了,这次是找不到IOException,结合前面的经验,我们知道,这里还是需要用全名:java.io.IOException,好了,我们再改改:

      public class TestBufferedReader4{
         public static void main(String[] args) throws java.io.IOException{
             //创建一个BufferedReader对象,准备进行字符串的输入
             java.io.BufferedReader br = new java.io.BufferedReader(new java.io.InputStreamReader(System.in));
             //读入输入行,结果保存在str中
             String str = br.readLine();
             //将str中的内容输出到屏幕
             System.out.println(str);
         }
      }

      编译后,如图:

      我们经常说:“没有消息就是好消息”,这里没有任何提示信息,就表明,我们的源代码编译通过了。我们可以测试看看:

      可以运行了。

    2. 我们再来看看怎么捕获异常:这个麻烦一点,我们需要使用异常捕获语句,它的一般写法如下:

      try{
         /*可能会发生异常的语句,一旦发生异常,就不管发生异常语句的后面还有多少语句没有执行而中断try中的语句的执行,系统会生成能够描述相关异常的异常类对象,这个异常类对象由后面的匹配的catch捕获,从而进入相关catch进行异常处理
         */
      } catch(异常类名1 e) {
         //一旦发生异常,由catch捕获,在这里写出对异常的处理代码
      } catch(异常类名2 e){
         //依然是处理当和异常类名2相匹配的异常发生时,需要做的工作
      } //如果try块中可能发生n种异常,这里就需要写n个catch语句来进行捕获
      finally{
         /*finally这部分子句是可以没有的,视情况而定。finally子句中的代码不管try中是否发生异常,最终都会执行finally子句中的语句。*/
      } 

      我们从编译提示信息,我们知道 br.readLine(); 可能会出现IOException异常,而这个异常,我们没有捕获。那我们就用上面的异常处理语句来做一下,代码改成:

      public class TestBufferedReader5{
         public static void main(String[] args){
             //创建一个BufferedReader对象,准备进行字符串的输入
             java.io.BufferedReader br = new java.io.BufferedReader(new java.io.InputStreamReader(System.in));
             try{
                 //读入输入行,结果保存在str中
                 String str = br.readLine();
                 //将str中的内容输出到屏幕
                 System.out.println(str);
             } catch(java.io.IOException e){//这里我们记得用IOException的全名
                 //打印异常栈信息
                 e.printStackTrace();
             } 
         }
      }

      编译执行,没有问题。

    3. 那这两种方式有什么区别呢?我们从异常处理语句,我们可以看到,一旦发生异常,我们可以主动由catch来进行捕获处理。所以,这两种处理方式,第一种是不主动的方式,就是说,写这段代码的程序员准备把发生的异常提交给调用这个方法的代码来进行处理,在方法内部不处理;第二种方式是比较主动的,一旦发生异常,这个异常在本方法内部就进行了处理,不再提交给调用它的代码来处理了。这就好比一个工厂中的一个车间,在车间的生成过程中,如果发生一些生产事故,有些事故车间主任就可以处理,有些事故必须上报到厂长那里,有些可能厂长也无法处理,就需要进一步上报。那么,自己处理就用catch,上报的话,就在方法的后面加上 throws 若干异常类名throws后面可以有若干异常类名,中间用,隔开),自己处理的异常就不要上报了。

    等等,现在是不是大功告成了呢?还有收尾工作没有做完:对于流的操作,我们在不使用流的时候,一定记得把流关闭了,这就像用水,不用水的时候,要记得把水龙头拧住。怎么做呢?在操作流的最后一步,执行close方法,我们一次性把代码补齐,代码如下(我们用异常捕获来做):

    public class TestBufferedReader6{
        public static void main(String[] args){
            //创建一个BufferedReader对象,准备进行字符串的输入
            java.io.BufferedReader br = new java.io.BufferedReader(new java.io.InputStreamReader(System.in));
            try{
                //读入输入行,结果保存在str中
                String str = br.readLine();
                //将str中的内容输出到屏幕
                System.out.println(str);
            } catch(java.io.IOException e){
                //打印异常栈信息
                e.printStackTrace();
            } finally {
                try{
                    br.close();
                }catch(java.io.IOException e){
                    //打印异常栈信息
                    e.printStackTrace();
                }
            }
        }
    }

    大家是不是有点疑问,为什么把close放在finally子句中,而且close本身又套了一层try...catch语句?首先,为了保证不管readLine有没有发生异常,我们的流都要关闭,所以,close必须放在finally子句中,以保证close必须执行;其次,close方法也可能发生IOException异常,所以close外面也要再套一层try...catch语句。

    到这里程序的编译到运行已经没有问题了,但是,我们有没有觉得总是在类名前加包名是不是太累赘?有没有办法呢?办法总是比问题多。我们可以在源文件的最开始加上若干 import 包名.类名;或者 import 包名.*;,比如上面的代码,我们可以这么做:

    import java.io.BufferedReader;
      import java.io.InputStreamReader;
      import java.io.IOException;
    
    //下面所使用类名就不必使用全名了,是不是清爽了许多?
    public class TestBufferedReader6{
        public static void main(String[] args){
            //创建一个BufferedReader对象,准备进行字符串的输入
            BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
            try{
                //读入输入行,结果保存在str中
                String str = br.readLine();
                //将str中的内容输出到屏幕
                System.out.println(str);
            } catch(IOException e){
                //打印异常栈信息
                e.printStackTrace();
            } finally {
                try{
                    br.close();
                }catch(IOException e){
                    //打印异常栈信息
                    e.printStackTrace();
                }
            }
        }
    }

    我们还可以这么做:

    import java.io.*;
    
    //下面所使用类名就不必使用全名了,是不是清爽了许多?
    public class TestBufferedReader6{
        public static void main(String[] args){
            //创建一个BufferedReader对象,准备进行字符串的输入
            BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
            try{
                //读入输入行,结果保存在str中
                String str = br.readLine();
                //将str中的内容输出到屏幕
                System.out.println(str);
            } catch(IOException e){
                //打印异常栈信息
                e.printStackTrace();
            } finally {
                try{
                    br.close();
                }catch(IOException e){
                    //打印异常栈信息
                    e.printStackTrace();
                }
            }
        }
    }

    这两种形式上的区别在于,后者使用了通配符*,这*代表相应子包中的所有类,注意只是这个子包中的所有类,是不包含子包中的子包的。比如上面的 import java.io.*; 是不能写作 import java.*; 的。因为后者的 * 只是代码java这个包中的所有类,而不包含它的子包 java.io

    大家是不是觉得用 * 挺不错的,省事。但是我们建议大家使用第一种方案,这是因为使用 * 会一股脑地把相应子包中的所有类都引入了,而不管你的程序用不用那些类;而不用 * 的方式,我们只是用什么才引入什么,我们可以很好的限制我们代码出错的几率。

    细心的同学会发现一个问题,那就是System也是类,怎么就没有用全名呢?System的全名为:java.lang.System ,从全名我们知道System类在java.lang这个包中,JDK对这个包总是默认引入的,也就是说对于所有的java源代码,相当于在源代码的最开始都有个import java.lang.*;,这条语句是省略的。

功能需求2:

前面的代码只能输入一行文字,如果输入多行呢?每输入一行就回显一行。

  • 需求分析:从需求,我们知道,我们需要输入多行内容,每输入一行都是以回车结束来这行的。我们想到可以用循环来实现。循环是需要结束的,我们怎么来结束输入呢?总不能让这个程序永远运行下去吧。对于键盘的输入,如果要结束这个输入流,我们在最后输入ctrl+z,再输入回车就可以了。而readLine这个方法在遇到流的末尾的时候,它的返回值为null

  • 程序编码:

    import java.io.BufferedReader;
      import java.io.InputStreamReader;
      import java.io.IOException;
    
    public class TestBufferedReader7{
        public static void main(String[] args){
            //创建一个BufferedReader对象,准备进行字符串的输入
            BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
            try{
                //读入输入行,结果保存在str中
                String str; 
                /*我们注意while的循环条件的写法,str = br.readLine()是一个赋值表达式,凡表达式都是有计算加结果的,它的计算结果就是赋值到str中的值
                */
                while((str = br.readLine()) != null){
                    //将str中的内容输出到屏幕
                    System.out.println(str);
                };                
            } catch(IOException e){
                //打印异常栈信息
                e.printStackTrace();
            } finally {
                try{
                    br.close();
                }catch(IOException e){
                    //打印异常栈信息
                    e.printStackTrace();
                }
            }
        }
    }

这里只要我们编译成功以后再运行,都没有发生异常的情况,我们怎么才能看到异常呢?我们如果希望java的标准输入系统发生异常,这种概率实在太小了。有一种办法,我们自己生成异常,来验证一下异常的处理,代码如下:

  import java.io.BufferedReader;
    import java.io.InputStreamReader;
    import java.io.IOException;

  public class TestIOException{
      public static void main(String[] args){

          try{
              throw new IOException(); //生成一个IOException异常对象并抛出,这个throw不带s哟
              System.out.println("能显示这一行吗?");        
          } catch(IOException e){
              //打印异常栈信息
              e.printStackTrace();
          } finally {
              System.out.println("不管怎么样,都会显示这一行");
          }
      }
  }

编译的时候,会出现如图所示的错误:

也就是说在编译的时候,java系统就已经探知到throw后面的语句是无论如何都不会执行到的。

那我们把它去掉再试试。如图:

框中的信息是 e.printStackTrace(); 的执行结果。

从这个示例,我们知道,异常的产生也可能是程序在运行过程中出现问题,由系统被动产生用于代表相关异常的异常对象,也可以由代码主动产生异常。

如果我们将上面的代码再修改一下,把catch的参数由IOException改为Exception,会怎么样呢?大家就会发现,没有任何问题,依然可以捕获。这是什么道理呢?catch后的参数不是同发生的异常精确匹配吗?我们知道Java语言是面向对象的语言,面向对象的概念中,类之间是可以有继承关系的,在同一个继承链中,处于继承关系底层的类可以看作是一个处于高层的类类型,就本例来讲,我们可以说IOException is Exception。其实,这很好理解,就如同:不管松树、柳树、杨树,它们都是树 是一个道理的。不在一个继承链中的类不能这么讲,比如,我们不能说:石头是树 一样。在JDK中,所有的类都有一个根类:Object,所有的类都继承自该类。

功能需求3:

完成如图所示的功能:

  • 需求分析:从需求,我们知道,我们需要提示信息和输入混合在一起,提示信息的输出,我们可以使用System.out.print()来显示。这里关键有一点,我们要计算第一个整数和第二个整数的和,而br.readLine()读取的数据为字符串而非整数,这里就需要把读到的字符串数据转换为整数。要做到这一点,我们需要使用int的封装类Integer,该类中有一个方法:

      public static int parseInt(String s) throws NumberFormatException

    通过这个方法的首部,我们可以得到什么信息呢?该方法的返回值类型为int,参数类型为String,它的修饰符中有一个static,它可能抛出 NumberFormatException 异常。static修饰符的作用,就是告诉我们这个方法,可以使用类名直接使用,不需要创建类的对象,再通过对象使用它。

  • 程序编码:

    import java.io.BufferedReader;
    import java.io.InputStreamReader;
    import java.io.IOException;
    
    public class TestBufferedReader8 {
        public static void main(String[] args) {
    
            BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
            int first; //用于存储输入的第一个整数
            int second; //用于存储输入的第二个整数
            int result; //用于存储计算结果
            String str;
    
            try{
                System.out.print("请输入第一个整数:"); //我们没有用println方法
                str = br.readLine(); //可能会发生IOException异常
                first = Integer.parseInt(str); //将字符串转换为整数,如果str中的字符串格式不能转换为整数,会发生NumberFormatException
    
                System.out.print("请输入第二个整数:");
                str = br.readLine();
                second = Integer.parseInt(str);
    
                result = first + second;
                System.out.println("结果为:" + result);
            } catch(IOException e) {//发生IOException异常时,执行下面的代码
                e.printStackTrace();
                System.out.println("发生了IO异常");
            } catch(NumberFormatException e) {//发生NumberFormatException异常时,执行下面的代码
                e.printStackTrace();
                System.out.println("发生了数字格式异常");
            } finally {
                try{
                    br.close();
                }catch(IOException e){
                    //打印异常栈信息
                    e.printStackTrace();
                }
            }
    
        }    
    }

    我们对上面的编码进行编译,分别执行两次,第一次,输入的数字为合格的整数形式;第二次,输入的数字格式不合格,观察现象,如图:

    从图中,我们看到第二次会发生格式转换错误,并输出提示信息。

    如果我们把上面的代码再改变一下,如下:

    import java.io.BufferedReader;
    import java.io.InputStreamReader;
    import java.io.IOException;
    
    public class TestBufferedReader8 {
        public static void main(String[] args) {
    
            BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
            int first; //用于存储输入的第一个整数
            int second; //用于存储输入的第二个整数
            int result; //用于存储计算结果
            String str;
    
            try{
                System.out.print("请输入第一个整数:"); //我们没有用println方法
                str = br.readLine(); //可能会发生IOException异常
                first = Integer.parseInt(str); //将字符串转换为整数,如果str中的字符串格式不能转换为整数,会发生NumberFormatException
    
                System.out.print("请输入第二个整数:");
                str = br.readLine();
                second = Integer.parseInt(str);
    
                result = first + second;
                System.out.println("结果为:" + result);
            } catch(Exception e) {//发生Exception异常时,执行下面的代码
                e.printStackTrace();
                System.out.println("发生了其他异常");
            } catch(IOException e) {//发生IOException异常时,执行下面的代码
                e.printStackTrace();
                System.out.println("发生了IO异常");
            } catch(NumberFormatException e) {//发生NumberFormatException异常时,执行下面的代码
                e.printStackTrace();
                System.out.println("发生了数字格式异常");
            } finally {
                try{
                    br.close();
                } catch(IOException e){
                    //打印异常栈信息
                    e.printStackTrace();
                }
            }
    
        }    
    }

    我们编译一下,看看:

    怎么会出现这些信息呢?这是因为Exception这个类是所有异常类的根类,因此,如把catch(Exception e)放在所有的catch前面,就会导致所有其它的catch不会被执行,java编译器在编译这段代码的时候能发现这类问题,于是就报错了。如果我们把catch(Exception e)放在最后,就不会出现问题了,它的意义在于,如果发生的异常不能被catch(Exception e)前的若干catch子句捕获的话,必会被最后的catch(Exception e)捕获,换句话说,就是不管发生什么样的异常,我们的代码都能捕获了,是不是更好些呢?代码如下:

    import java.io.BufferedReader;
    import java.io.InputStreamReader;
    import java.io.IOException;
    
    public class TestBufferedReader8 {
        public static void main(String[] args) {
    
            BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
            int first; //用于存储输入的第一个整数
            int second; //用于存储输入的第二个整数
            int result; //用于存储计算结果
            String str;
    
            try{
                System.out.print("请输入第一个整数:"); //我们没有用println方法
                str = br.readLine(); //可能会发生IOException异常
                first = Integer.parseInt(str); //将字符串转换为整数,如果str中的字符串格式不能转换为整数,会发生NumberFormatException
    
                System.out.print("请输入第二个整数:");
                str = br.readLine();
                second = Integer.parseInt(str);
    
                result = first + second;
                System.out.println("结果为:" + result);
            } catch(IOException e) {//发生IOException异常时,执行下面的代码
                e.printStackTrace();
                System.out.println("发生了IO异常");
            } catch(NumberFormatException e) {//发生NumberFormatException异常时,执行下面的代码
                e.printStackTrace();
                System.out.println("发生了数字格式异常");
            } catch(Exception e) {//发生Exception异常时,执行下面的代码
                e.printStackTrace();
                System.out.println("发生了其他异常");
            } finally {
                try{
                    br.close();
                } catch(IOException e){
                    //打印异常栈信息
                    e.printStackTrace();
                }
            }
    
        }    
    }

    本例演示的是字符串向整数的转换,如果需要输入其他类型数据,大家可以参考JDK说明书对相应基本类型的封装类的说明。

使用Scanner进行输入

Scanner类是一个可以使用正则表达式来解析基本类型和字符串的简单文本扫描器。它的输入流不局限于键盘输入。该类的功能很强大,我们这里只涉及我们要用到的东西,完整的说明大家参考JDK说明书。

介绍

Scanner 使用分隔符将其输入分解为各个不同的部分(称为token),默认情况下使用空白作为分隔符。我们可以使用该类中的useDelimiter方法来设置不同的分隔符。

Scanner类中有很多不同的next为前缀的方法,我们可以使用不同的 next 方法将分解得到的token转换为不同类型的值。比如,我们可以使用nextInt()将下一个要被处理的token转换为int数据。

如何判断我们要处理的token是不是还有呢?Scanner类中也提供了一系列以hasNext为前缀的方法来判断是否还有要处理的token,如果还有,hasNext方法的返回结果为true,否则就是false

Scanner在使用的时候,也是需要创建该类的对象,然后利用该对象来完成工作的。

使用该类的一般流程如下:

  • 创建Scanner对象,同时设置输入源
  • 设置分隔符(如果使用默认的空白符的话,这步是可以省略的)
  • 对输入的数据进行操作
  • 关闭扫描器

示例1

我们设置一个字符串,它的内容为: mew, I want to fish 13 fishes.。我们把这个字符串用Scanner进行扫描来看看结果如何。代码如下:

//Scanner在java.util包中,需要引入
import java.util.Scanner;

public class TestScanner1{
    public static void main(String[] args) {
        String inStr = "mew, I want to fish 13 fishes."; //建立输入源数据
        Scanner sc = new Scanner(inStr);//创建扫描器
        //sc.useDelimiter();

        //对数据源进行扫描处理
        while (sc.hasNext()) {
            //next()方法的返回值类型为String
            //所以本例是把所有的token都看作字符串来进行输出
            System.out.println(sc.next());
        }

        sc.close();//关闭扫描器

    }
}

运行结果如图:

从运行结果,我们可以看到原始的字符串,通过扫描器,按默认的空白符为分隔符,把它分成了7个token,每个tokennext()方法以字符串进行处理。

示例2

我们还按上面的数据源,这次,我们改变一下分隔符,改为,,看看效果。代码如下:

//Scanner在java.util包中,需要引入
import java.util.Scanner;

public class TestScanner2{
    public static void main(String[] args) {
        String inStr = "mew, I want to fish 13 fishes."; //建立输入源数据
        Scanner sc = new Scanner(inStr);//创建扫描器
        sc.useDelimiter(",");//设置分隔符为: “,”

        //对数据源进行扫描处理
        while (sc.hasNext()) {
            //next()方法的返回值类型为String
            //所以本例是把所有的token都看作字符串来进行输出
            System.out.println(sc.next());
        }

        sc.close();//关闭扫描器

    }
}

运行结果如图:

以“,”为分隔符,将原始数据源分隔成了两个token。那么能不能设置多种分隔符呢?答案当然是可以的,我们只需要在设置useDelimiter方法参数字符串的时候,把分隔符用|分隔开就行了,比如上例:我们把空格和逗号都作为分隔符,我们把上面的设置分隔符语句改为 sc.useDelimiter(" |,");即可(注意:| 前面有个空格)。大家自己测试一下吧。

示例3

还按上面的数据源,这次,我们把数据源中所有的整数提取出来,我们把代码修改如下:

//Scanner在java.util包中,需要引入
import java.util.Scanner;

public class TestScanner3{
    public static void main(String[] args) {
        String inStr = "mew, I want to fish 13 fishes."; //建立输入源数据
        Scanner sc = new Scanner(inStr);//创建扫描器

        //我们用循环对token序列依次进行扫描
        //依然由hasNext()作为是否有下一个token的判断条件
        while (sc.hasNext()) {
            //判断下个token是否为可转换为int的数据
            if(sc.hasNextInt()){//如果可以转换为int数据,把它输出出来
                System.out.println(sc.nextInt());
            }else {//如果不是,就用next方法把该token略过,继续到下个循环判断下个token的情况        
                sc.next();
            }
        }

        sc.close();//关闭扫描器

    }
}

问题

  1. 如果使用Scanner对键盘进行输入,我们只需要在创建扫描器的时候,把System.in作为参数传递给Scanner的构造方法就可以了,其余的操作是相同的,既是Scanner sc = new Scanner(System.in); 。本题的要求就是设计一个使用Scanner实现键盘的数据输入的程序,输入内容要回显到屏幕上。
  2. 扫描 "123 3.45 true a56 ok!"这个字符串,并输出如图的结果:
#Java学习##java工程师#
全部评论
java太复杂了
点赞 回复
分享
发布于 2022-06-26 10:12

相关推荐

1 1 评论
分享
牛客网
牛客企业服务