page6(update:2017/11/06)[例 題6a|例題6b|例題6c |課題5]


[ prev | next | index ]

6. 入力のあるプログラム

java言語ではキーボードやファイルからのデータ入力には入出力用のオブジェクトを使います。ところが、これらが持つメソッド(関数)には例外を投げるものが多いのです。前のページで例外処理 の説明をしました、必要なら前のページを復習してください。

目次

[目次]

※以下で説明する入出力には色々なクラスが出てきて複雑に思えるでしょう。しかし,これらは必要な仕事をうまく分割し,その組み合わせ次第で様々なことができるように工夫した結果です。文字列の入出力でさえ,多様な文字コードやデータの取りこぼしの防止など,実はかなり複雑な処理なのです。

6.1 データの読み込み

入力を読んで基本データ型の値を得るまでを2段階に分けて説明します

  1. 標準入力から文字列を読み取る方法
    (1)送信されてきたバイトを1バイトづつ受けとる。
    (2)受信したバイト列をjavaの標準文字コードであるUnicodeに変換してchar型のデータとして読む。
    (3)char型のデータ列として蓄える。必要に応じて文字列として読み出す。
    面倒な手順に見えますが、役割分担をして複雑にならないようにしたものです。

  2. 文字列から基本データ型の値への変換
    文字列と基本データ型との変換にはラッパークラスと呼ばれるオブジェクトを使います。

この2つの段階で何れも例外処理が必要です。しかし、まずは例外処理を省いた形で以下に説明します。

6.1.1 標準入力から文字列を読む

ストリーム

ストリーム(stream)は1列に並んだデータの流れです。

標準出力への 書き出しでは、System.outと言う出力ストリームを使ってきました。SyatemクラスのクラスフィールドであるoutはPrintStreamクラスのインスタンスで標準出力ストリームに繋がっています。したがって、printメソッドを利用すると文字列を標準出力へ書き出します。


System.out.print("123 ABC Hello あいうえお");

一方、入力ストリームとしては、System.inが用意されています。このinInputStreamのインスタンスで標準入力ストリームに繋がっています。

しかし、InputStreamはバイト列を読めるだけの単機能なストリームで、このままでは文字列や数値を読み込むことができません。以下にchar型の文字列を読み込むまでの手順を示したいと思います。

(1) java.io.InputStream

InputStreambyte型データ列を読み取るためのオブジェクトです。単純なストリームで、このままでは使いにくいのです。

例えば1文字読み込むとしても、javaで使われる文字のデータはUnicode2.0でコード化されていますが、これに対して標準入力から送られてくるバイト列はASCIIコードやSJISの文字コードが使われていることも多いのです。ここで、SJISの場合だとASCII文字は1バイト、漢字などは2バイトで1文字となります。これに対してユニコードではASCII文字は上位1バイトは0、下位の1バイトはASCIIコードと同じ2バイトになります。漢字の部分は同じ2バイトですが文字と対応するコードは異なります。このような文字コード変換を自分でプログラムするのは困難です。

そこで、文字コード変換のために次のようなオブジェクトが用意されています。

(2) java.io.InputStreamReader

InputStreamReaderはInputStreamからbyte型データを受け取りUnicodeの文字に変換して文字(char型)として読み取れるようにするオブジェクトです。

 byteデータをSystem.inから読み、JISの文字コードを自動判定し、Unicode文字に変換して読むインスタンスは次のように作ります。このインスタンスからreadメソッドを使ってchar型のデータを読み出せます。

new InputStreamReader(System.in, "JISAutoDetect");

※"JISAutoDetect"の文字列はJISの文字コードを自動判定するように指示するためのものです。
 ※Windowsで使われる文字コードはSJISとは少しだけ違う"Windows-31J"です。

しかし、1文字づつ読み出すのは面倒なので、通常は改行などで区切られた1行分の文字列を取り出したいことが多い。そこで更に、複数の文字をバッファする機能もを持つBufferedReaderを繋ぎます。

(3) java.io.BufferedReader

BufferedReaderのオブジェクトは、InputStreamReaderから文字を受け取って1行の文字列単位で読めるのが特徴です。 インスタンスを作るときは(1)と(2)をまとめて次のように書くことが多いようです。

new BufferedReader(new InputStreamReader(System.in,"JISAutoDetect"));

のように1つの式にすると、InputStreamReaderの参照変数を定義しないで済みます。

BufferedReaderのメソッドreadLine()を使えば一行分の文字列をString型インスタンスとして受け取れます。

 

1行の文字列を読み込むプログラムは例えば次のようになります。

//このままでは使えません 例外処理が必要です
BufferedReader reader=
    new BufferedReader(new InputStreamReader(System.in,"JISAutoDetect"));
String s=reader.readLine();
バッファ:Buffer

通信などで、送られてくる文字列の処理が間に合わなくなると、データを受け取り損ねたりしかねません。そこで、通常は受け取ったデータを貯めておくバッファを用意します。送られてきたデータをとりあえずバッファに貯めておけば取りこぼしを防げます。このバッファからデータを取り出して処理することにすれば、取りこぼしは起きません。

(考えよう)外部との効率的な入出力を行うためにはバッファが必要です。バッファを使わない場合のデータ受け取りはどんな手順になるか? 考えてみましょう。

6.1.2 文字列の基本データへの変換

C言語のscanfを使うときは書式指定子%dや%lfを指定するだけで基本データ型に変換してくれて簡便でしたが、scanfはバッファーオバーランを起こしやすく危険な関数としても知られています。javaではもっと安全で厳密な処理を行う為に、基本データ型ごとに対応するラッパークラスを用意しています。ラッパークラスには文字列から基本データ型への変換メソッドが用意されています。

※javaのライブラリーにはscanfのような関数は在りません

※C言語のscanfなどで書式に"%d"を指定して、渡される文字列を整数型へ変換して入力するような場合、渡された文字列が”ABC"のような変換できないものでは、scanfは0を戻して変換できないことを示します。同じ状況でjava言語の場合はNumberFormatExceptionの例外を投げます。

ラッパークラス

boolean, char, byte, short, int, long, float, doubleの基本データ型はオブジェクトではありません。 しかし、これらの基本データ型の値をオブジェクトとして扱いたいことがあります。この為に用意されたのがラッパークラスです。 基本データ型の値を包んで(ラップして)オブジェクトにするので、ラッパークラスと呼ばれます。

基本データ型 ラッパークラス
wrapper class
boolean Boolean
char Character
byte Byte
short Short
int Integer
long Long
float Float
double Double

このラッパークラスには文字列から基本データ型の値に変換するメソッドも用意されています。以下に文字列から基本データ型の値を得る2つの方法を説明します。

(1)文字列から一度ラッパークラスのインスタンスを作る方法

インスタンスを作るときに値や文字列を渡して、Boolean b2=new Boolean("true") のように基本データ型の値を保持したインスタンスを作れます。

/*インスタンス生成と初期化を使ってインスタンスを作る場合*/
Boolean b1=new Boolean(true);/
Boolean b2=new Boolean("true");//文字列がTRUE,trueでtrue、それ以外ではfalseの値のオブジェクトができる
Double d1=new Double(3.1415);
Double d2=new Double("3.1415");
/*コンストラクタではなくてクラスのメソッドを呼んでインスタンスを作ることも可能です*/
Boolean b3=Boolean.valueOf(true);
Boolean b4=Boolean.valueOf("true");
Double d3=Double.valueOf(3.1415);
Double d4=Double.valueOf("3.1415");

次にインスタンスを参照する変数b2からインスタンスのメソッド(関数)を使って b2.booleanValue() で値を読み取ります。

boolean b=b2.booleanValue() ;
double d=d2.doubleValue();

この方法はラッパークラスのインスタンスを作ってしまうので、沢山のデータを読み込むだけの場合は、インスタンスを使い捨てにするので効率的で はありません。 値が欲しいだけの場合はインスタンスを作らなくてもクラスのメソッド(関数)を呼んで変換することができます。

※復習
インスタンスのメソッドを呼ぶときは 参照変数.メソッド名( );
クラスののメソッドを呼ぶときは クラス名.メソッド名( );

(2)直接に基本データ型へ変換する方法

ラッパークラスには文字列を基本データ型に直接変換するクラスのメソッドが有ります。例を示します。

boolean b=Boolean.parseBoolean("true");
double d=Double.parseDouble("3.1415");//DoubleをIntegerやLong等に変えれば他の型も同様

※沢山のデータを読むときはこちらを使います。オブジェクトを沢山生成して使い捨てると、プログラムが非常に遅くなる原因となります。オブジェクトの生成およびガベージコレクションは時間のかかる処理なのです。

6.2 基本データ型の読み取り[例題6a] 

入力ストリームから文字列を読み出し、基本データ型のラッパークラスのメソッドをつかって文字列を基本データ型へ変換します。ここでいろん な例外が発生する可能性があるのが煩わしい所です。下記に例題を作ってみましたので実際に試してください。

例題6a

import java.io.*;//java.ioのパッケージ名を省略する為のimport宣言
/*
ReadTest.java */ public class ReadTest{ /** main関数 文字列を読み込んで各基本データに変換する */ static public void main(String arg[]){ String s; int i; double d; boolean b; BufferedReader in=null;//nullは何も参照していない場合の参照型の値。C言語のNULLと同じです try{ //一行入力が可能なストリーム BufferedReaderを作る。IOExceptionを投げる可能性有り in= new BufferedReader(new InputStreamReader(System.in,"JISAutoDetect")); }catch(IOException e){ System.out.println("BufferedReaderが作れません。プログラムを終了します"); System.exit(1);//実行終了 } try{ System.out.print("整数を入力してください>>"); s=in.readLine();//一行の文字列を読む //入力できないときsがnullになるので、その場合はIOExceptionを投げる if(s==null)throw new IOException(); //文字列をintデータに変換する関数、例外NumberFormatExceptionを投げる可能性有り i=Integer.parseInt(s); System.out.println("入力された値を2倍にすると"+(2*i)+"\n"); System.out.print("実数を入力してください>>"); s=in.readLine(); if(s==null)throw new IOException(); //文字列を実数データに変換する関数、例外NumberFormatExceptionを投げる可能性有り d=Double.parseDouble(s); System.out.println("入力された値を2倍にすると"+(2*d)+"\n"); }catch(NumberFormatException e){//数値に変換できない文字列 System.out.println("入力を数値に変換できません"); }catch(IOException e){//入出力で問題発生 System.out.println("\n入力が受け取れませんでした。プログラムを終了します"); } } } 

文字列の+演算

System.out.println("入力された値を2倍にすると"+(2*i)+"\n"); の動作が分りますか?。文字列の+演算は文字列の結合を行います。ここで、文字列以外のものが出てきた場合、文字列に変換できるものは自動的に文字列に変換して結合します。この例ではiの値を10と仮定すれば、(2*i)の値20が計算されその後で文字列に変換して結合されるので

System.out.println("入力された値を2倍にすると20\n");

と同じ結果になります。

書式付の書き出し

PrintStreamに書式付の出力メソッドが追加されました。
printf format です。C言語のprintf関数に相当し以下のように使えます。


System.out.printf("入力された値を2倍すると%d\n",2*i);

※でも、scanfに相当する書式付入力はありません。

メモ: import文

ストリーム入出力のクラス名でjava.ioの部分はパッケージ 名です。

java.io.InputStreamReader
java.io.BufferedReader

※ java言語に用意された沢山のクラスはパッケージにまとめることで整理されています。パッケージ名は中身のクラスの役割を代表するように命名されています。

javaはjava言語の標準ライブラリであることを示すパッケージ名、ioは入出力input-outputを示すパッケージ名です。つまりjava.ioはjava標準の入出力パッケージの意味 です

いちいちクラス名にパッケージ名まで付けるのは長くなるので、プログラムの先頭にimport文

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

と書くと、そのプログラム中ではパッケージ名の部分を省略できます。

import は一見、C言語の#includeマクロと似ていますが、意味がちがいます。

java.ioパッケージ内の全クラスについて同様の扱いをしたければ

import java.io.*; 

と書くことで可能です。実はjava.langパッケージはデフォルトでimportされているのでSystemとかStringのようなクラスはクラス名だけで使えるようになっています。

※この様な*を使ってクラス名をし手間を省く事ができます。しかし、逆にプログラムの中で使っているクラスがどのパッケージのクラスなのかを知りたいときには不便です。面倒でもクラス名まで書いたインポート文を使うほうが良いと思います。

スタティク インポート(java5からの機能)

sin、cos の様な初等関数をjavaではMathクラスのクラスメソッドとして用意しています。

Math.sin( Maht.PI/2 )やMath.cos(3.14)のようにクラス名を付けて使うのですが、これでは煩雑です。そこでスタテックインポートを使ってクラス名の部分まで省略する書き方が用意されています。

※下記のようにスタテックインポートではstaticを付けて区別することが必要です。

import static java.lang.Math.*;
public class A
{
	public static void main(String arg[])
	{
		System.out.printf("%f",sin(PI/2)*cos(3.14));//クラス名を省略して記述可能
	}
}

[目次]

6.3 ファイルの読み書き[例題6b]

 ここではファイルからの読み込みと書き出しの例を以下に示す。この例はjava のソースファイルを読み、HTMLのタグに使われる文字「<」と「>」を代替え文字に置き換え、HTMLのファイルを書き出す。

ファイルからの読み込み

1)javaのソースファイルに対応したjava.io.Fileのインスタンスを作り参照を変数javaFileに代入。

※パッケージ名を以下では省略します。

File javaFile=new File("FileIO.java");

2)ファイルを読むためのストリームFileInputStreamを作り。このストリームからInputStreamReaderを作って文字コード変換などを行い、さらに文字列単位で読み込めるBufferedReaderのインスタンスを作る

BufferedReader input=
    new BufferedReader(new InputStreamReader(new FileInputStream(javaFile),"JISAutoDetect"));

つまり標準入力からの場合と比べればSystem.inがFileInputStreamのインスタンスに変わるだけで後は同じ

※この図では、
プログラム例との対応を示す意味でインスタンスを

参照変数:クラス名
の形で表記しました。参照変数を明示的に作らなかったものは
:クラス名
としています。

ファイルへの書き出し

書き出すほうも同様に、ファイルhtmlFileからFileOutputStreamを生成し OutputStreamWriterを介してPrintWriterのインスタンスoutputを生成する。outputに書き出すと文字コード変換等も行ってhtmlFileに書き込まれることになる。

 

下記に全体のサンプルプログラムを示す。ここで、注意してほしいのは例外処理が沢山いること。

/*FileIO.java*/
import java.io.*;

public class FileIO{
//
public static void main(String args[]){
BufferedReader input=null;
PrintWriter output=null;
File javaFile=null;
File htmlFile=null;
int c;//文字の読み込み用
try{
javaFile=new File("FileIO.java");//自分のソースファイル
//読み込み用ストリームの作成 JIS,EUC,SJISをUnicodeをに変換して読み込む
input=new BufferedReader( new InputStreamReader(new FileInputStream(javaFile),"JISAutoDetect") );
//
htmlFile=new File("FileIO.html");//書き出し用ファイルの作成
//書き出し用ストリームの作成 UnicodeをSJISに変換して書き出す output=new PrintWriter( new OutputStreamWriter(new FileOutputStream(htmlFile),"SJIS") );

output.println("<HTML><HEAD></HEAD><BODY><PRE>");
//1文字づつ呼んでhtmlのタグに使う文字などを置き換えて書き出す
while(true){
c=input.read();//1文字読み込みint型変数に格納
if(c==-1)break;//入力の終端では-1が返される
switch(c){//下記の3種類の文字を置き換える
case '<': output.print("&lt;");break;
case '>': output.print("&gt;");break;
case '&': output.print("&amp;");break;
default: output.write((char)c);
}
}
output.println("</PRE></BODY></HTML>");
}catch(Exception ex){ ex.printStackTrace(); }finally{
try{
//ファイルのストリームは必ず閉じること
if(input!=null)input.close();
if(output!=null)output.close();
}catch(Exception ex2){}/*例外が起きても何もしない*/
}
}
}

[目次]

6.4 GUIからの入力

前回の演習にあったボタンは押されるとイベントの一種ActionEventオブジェクトを生成し配信します。配信はイベントオブジェクトを引数として配信先オブジェクトの決められたメソッドを呼び出すことで行われる。

従って、配信を受ける為には次のようなことが必要です
1)配信を予約する
  予約したオブジェクトにのみに配信されます。
2)配信を受けるオブジェクトが配信を受けとるメソッド(関数)を持っている
  配信を受ける側で決められた受け取りメソッドを定義していることの保障が求められます。 この為にインターフェースと呼ばれる仕組みを利用するので、以下に説明します。

6.4.1 インターフェイス

インターフェイス

java言語にはクラスに類似したinterfaceインターフェイス)と呼ばれる、オブジェクトの持つ公開メソッドの 頭部のみをまとめて宣言したものが在ります。メソッドの頭部は戻り値、名前、引数並びでこれらを合わせてsignatureシグニチャあるいはシグネチャと言い ます。

interface インターフェイス名
{
    public static final データ型 定数名=初期値;//定数をメンバに持つインターフェイスは例外的です。
    .....
    public 戻り値型 メソッド名1(引数並び);
    public 戻り値型 メソッド名2(引数並び);
    public 戻り値型 メソッド名3(引数並び);
    .....
}

クラスの書き方と似ています。ただし、メンバは全てpublicで、メンバはフィールドは定数のみ、メソッドはシグニチャのみです。

インターフェイスは何に使うのか?

インターフェイスはインスタンスをメソッドの共通性で分類するために使います。ただし、クラスを定義する段階でどの様なインターフェースを実装しているか、予め示すことが必要です。1つのクラスに複数のインターフェイスを実装できるのでクラスに縛られない多様な分類が可能になります。

※Java言語はクラスに記述された設計図からインスタンスを作るので、全てのインスタンスは元になったクラスに分類されるオブジェクトです。同様にインスタンスをインターフェイスで分類することが可能です。インターフェイスは多重実装ができるのでクラスに縛られない、多様な分類を可能にします。この辺の話は継承のところで詳しく説明する予定です。

 インターフェースの実装

classinterfaceを実装する宣言は次のように記述します。

class クラス名 implements インターフェイス名
{
    ......
    クラス内にインターフェイスで宣言した全てのメソッドを実装しないとコンパイルエラーになる
    ......
}

インターフェースに宣言されたメソッドを クラス内に全て実装しないとコンパイルエラーになります。

これによりインターフェースに宣言されたメソッドの存在が保証されます

複数のインターフェースを実装することも可能

 一つのクラスに複数のインターフェイスを実装する場合 インターフェース名をカンマで区切って並べて書きます

class クラス名 implements インターフェイス名1, インターフェイス名2, インターフェイス名3 
{
    ......
    クラス内にインターフェイスで宣言した全てのメソッドを実装しないとコンパイルエラーになる
    ......
}

インターフェイスも参照変数の型にできます

javaではクラスだけでなくインターフェイスも参照変数の型になります

1)インターフェイスを実装したクラスのインスタンスは、インターフェース型変数から参照できます

クラスが違っても同じインターフェイスを実装していれば、同じインターフェイス型変数から参照できます。

2)インターフェース型変数から参照できるメンバはインターフェースで宣言されたメンバのみです。

参照データ型 変数から参照できるメンバ
クラス型 クラスで定義された全てのメンバ
インターフェイス型 インターフェイスで宣言されたメンバのみ
(通常はメソッド)

6.4.2 配信の予約

配信受け取りメソッドの存在を保証するインターフェイス

ActionEventの配信を受け取るためのメソッドは インターフェイスActionListenerで宣言されている。

宣言されたメソッドは1つだけ。メソッド名はactionPerformedで引数はActionEvent型です。

ActionListener
public interface ActionListener
{
    public void actionPerformed(ActionEvent e);//シグニチャのみで中身は定義されていません
}

ActionEvent配信予約の受付

Buttonクラスはボタンが押されたときに、TextFieldクラスはEnterキーが押されたときに、ActionEventを生成して配布します。これらのクラスには配布先を登録する次のメソッドがあります。

void addActionListener(ActionListener al)

登録を取り消す場合は

void removeActionListener(ActionListener al) 

を呼んで予約をキャンセルもできます。

この予約メソッドの引数がActionListener型である点に注意してください。引数にできるのはActionListenerを実装したインスタンスの参照のみです。

※ActionListenerを実装したインスタンスの参照でなければActionListener型参照変数に代入できないので、間違いを防ぐことができます。

例題6c

Streamに比べると,GUIのテキストフィールドを用いた文字列の読み書きは簡単です。ただイベントの送受信の仕組みが解かり難いかもしれません。ここではボタンを押すとテ キストを読んで値を2倍にして上書きする例題を作りました。実行すると下の図の様なWindowが開きます。

ソースコードは下記の通りです。リンクからdownloadして試してください。
/*GUITest.java*/
import java.awt.*;
import java.awt.event.*;

public class GUITest implements ActionListener //ActionListenerの実装の宣言
{                     
    //クラスのメンバ
    public static void main(String args[]){
        GUITest test=new GUITest();//自分のインスタンスを作るだけ
    }

    //インスタンスのメンバ
    private TextField field=null;//
    private Button button=null;
    
    private GUITest()
    {
        Frame window=new Frame("GUIからの入力");
		
        button=new Button("2倍にします");
        button.addActionListener(this);//イベントを自分に配信予約
        window.add("North",button); 
		 
        field=new TextField("1");
        window.add("Center",field); 
		
        window.setSize(200,100);
        window.setVisible(true);
    }

    public void actionPerformed(ActionEvent e)//実装した中身のあるメソッド
    {
        String d=null,s=null;
        s=field.getText();//テキストフィールドから文字列を読む
        try{
            int i=Integer.parseInt(s);//整数に変換する
            d=Integer.toString(2*i);//文字列に変換する
        }catch(NumberFormatException nfe){
            d="入力はint型に変換できません";
        }
        field.setText(d);//テキストフィールドに文字列を書く
    }
}
 

実行の時間的な流れを示すシーケンス図

クラス間の関係を示すクラス図

[目次]

6.5 課題5

課題5の初期ファイルP5.java*****の部分を記述し、試験の点数(0〜100の整数値)を読み込んで平均を出すプログラムを作りなさい。点数の入力の終わりは文字列”END”の入力で示されます。

余力があれば、さらに書き足して入力が0-100の範囲外の処理を追加してください

import java.io.*;
public class P5
{
    public static void main(String args[])
    {
        int data;//点数
        int count=0;//データ数
        float total=0;//合計点
        //
        BufferedReader in=null;//入力用のReader
        String s;//Readerから読み込んだ一行の文字列を参照する変数
        //
        //BufferedReaderを作る try-catch構文が必要

       *******************************************
        while(true){//"END"でbreakする無限ループで点数を読み データ数と合計点を更新
            System.out.print("試験の成績を入力してください>>");
            System.out.flush();
            try{
                //一行分を文字列sに読み込む
                s=in.readLine();
                //文字列が空のときはIOExceptionを投げる
                if(s==null)throw new IOException();
                //文字列が"END"ならループを脱出
                if(s.equals("END"))break;
                //文字列を整数に変換して
                data=*******************//NumberFormatExceptionを投げる可能性有り
    
                //データ数を1つ増やし
                //点数を合計に加算
                count++;
                total+=data;
            }catch(NumberFormatException e){
                System.out.println("\n入力文字列を整数に変換できません");
            }catch(IOException e){
                System.out.println("\n入力に問題が有り続行不能です");
                break;
            }
        }
        //平均点の書き出し
        if(count==0){
            System.out.println("\nデータが無いので平均点は計算できません");
        }else{
            System.out.println("\nデータ数"+count+" 平均点"+total/count);
        }
    }
}

(注)文字列の比較でequalsメソッドを使っています。もしs=="END"の様に記述するとオブジェクトsとオブジェクト”END"が同じオブジェクトの場合のみtrueとなります。異なるオブジェクトでも中身の文字列が同じ並びであることを比較するにはequalsを使います。


[ prev | next | index ]