11/18 復習


11/4の課題について


質問と回答

Q
例外は多分理解できています。でも、なぜ例外になるのかはよくわかりません。 プログラムを添付しましたので見てください。お願いします。
import java.io.*;
class Kadai1104{
    public static void main(String[] args) throws IOException{
	double num1,num2;	
	while(true){
	    BufferedReader d1=new BufferedReader(new InputStreamReader(System.in));
	    try{
		num1=Double.parseDouble(d1.readLine());
	    }
	    catch(java.lang.NumberFormatException e){
		System.out.println("次は数字を入力してください.");
		continue;
	    }
	    break;
	}
	BufferedReader d2=new BufferedReader(new InputStreamReader(System.in));
	String s=d2.readLine();
        (略)
    }
}
A
TAの小田原です。

確かに、手入力では動くプログラムになっています。
しかし、入力手段としてファイルのリダイレクトを使うと、うまく動かなくな
るようです。

たとえば添付したtest.inというファイルjava/に保存して、

% java Kadai1104 < test.in

としてみてください。恐らくうまく動かないのではないでしょうか。先生のレ
ポート提出用プログラムでは、上のようにしてプログラムが正常に動作するか
どうかを確認しているため、うまく動かなかったわけです。

じゃあなぜリダイレクトではきちんと動かないのか、というと僕にはよくわか
らないのですが、あなたのプログラムでBufferedReader のオブジェクトを
「複数」生成していることが、正常に動かなくなることに関係しているように
思われます。
(田中注)
キーボードからの入力
複数行を入力する場合は1行ごとにBufferedReader型のオブジェクトを作成す る必要はなく,一度作ったBufferedReader型のオブジェクト d に対して,
d.readLine();
を繰り返し実行すれば良い.同じストリームに対してBufferedReader型のオブ ジェクトを複数作成すると,キーボード入力の場合は動くように見えても標準 入力をファイルにリダイレクトすると動かなくなることがある.
とあります。
Q.
コンパイルを通ったあとに
Exception in thread "main" java.lang.NoClassDefFoundError
というのが出ます。リンクとして挙げられているページを見てみると構造上の問題があるようなことが書いてあり、前半を例外を捕らえたあとに前に戻るようにするのかなと思うのですがどう表現すればいいのでしょうか。その他にも問題があるのでしょうか。

以下が書いてみたものです。

import java.io.*;
class Kadai{
    public static void main(String[] args) throws IOException{
      (略) 
    }
}
A.
TAの矢野です。

> コンパイルを通ったあとに
> 
> Exception in thread "main" java.lang.NoClassDefFoundError
> 
> というのが出ます。

NoClassDefFoundError というのは
javaの記述内容に関するエラーではありません。
java実行時に指定した名前のクラスが見つからなかった
(No Class Definition was Found)ので、実行そのものができていないのですね。

javaをコンパイルすると「クラス名.class」という
クラスファイル(実行のためのファイル)が生成されますが、
このときクラスファイルの名前は
javaのファイル名ではなく、記述したクラスの名称と等しくなります。
ですので、今回XXさんが書かれたコードをコンパイルすると、
「Kadai.class」というクラスファイルが生成されることになります。

ところで、XXさんが書かれているjavaのファイル名は
指定どおり「Kadai1104.java」となっていないでしょうか?
そうなっている場合、Kadai1104.javaでコンパイルが通ったので
> java Kadai1104
と打って実行しようとすると、
マシンは「Kadai1104.class」というクラスファイルを探しに行く、
しかしそのようなクラスファイルは存在しないために
NoClassDefFoundErrorが発生することになります。

この場合、
> java Kadai
でKadai.classを実行させることもできますが、
いちいちファイル名とクラス名が別になっていると混乱の原因になりますので
ファイル名とクラス名は同じにするのが原則です。

最後にjavaのコードの内容についてですが、
if文をもう一度見直すとよいかもしれません。
判定する条件式が一度に複数ある場合には、

if(条件1){
	条件1が真ならここのみ実行;
} 
else if (条件2) {
	条件2が真ならここのみ実行;
}
://条件がたくさんあればelse ifで続ける
else {
	全ての条件に当てはまらなかった場合ここを実行;
}

という書き方をします。
ほかにもちょこちょこありますが、
ひととおり動いているようなのでチューンナップしてみてください。

Q.
数字の時のInteger.parseIntのように、入力された文字列を演算子の型に変換 する、コンストラクタ?はありますか?または調べ方を教えてください。
A.
TAの矢野です。

「演算子型」という型はjavaには存在しませんので、端的に言うとありません。
ですので、今回の課題の場合は
「どのような文字(列)が入力されたかを判定する」という
操作をすることになります。

文字列の比較は、Stringクラスの動的メソッドequalsで行えます。
仕様の詳細は下記APIページにあります。
http://java.sun.com/j2se/1.4/ja/docs/ja/api/java/lang/String.html#equals(java.lang.Object)

これを使って、たとえばsという名前のString型オブジェクトが
"foo"と等しいか?ということを調べたい場合、

String s;
: //sに値を代入しておく
if (s.equals("foo")){    //sに代入された文字列が"foo"だった場合、真(true)
	sに代入された文字列が"foo"だった場合ここを実行;
}

といった書き方をすることができます。
今回は、入力された演算子(これも文字列ですね)が
たし算か、掛け算か、あるいは・・ということを判別したいわけですから、
例の"foo"の部分に何を持ってくればいいでしょうか・・?
以上のことをふまえ、条件分岐を考えてみてください。

ちなみに、Integer.parseIntは
Integer型のクラスに定義されている静的メソッドと呼ばれます。

Q.

式を略そうとして、メソッドを利用しようとしたのですが、メソッドから値を
移してくる方法がわかりません。(mainの方で初期化した値になってしまう)
また、条件判断がうまくいきません。教えてください。

import java.io.*;
class Ka1104{ 
    //IOExceptionで入力にエラーがあることを示す
    public static void main(String[] args) throws IOException{
     (略)
   }
//使用頻度の多い、文字入力の例外処理をメソッド化する
 static void Exception() throws IOException{
	BufferedReader d=new BufferedReader(new InputStreamReader(System.in));
        double num=0;
	String u;
	//正しく数字が入力されるまでの繰り返し
	while(true){
	    u=d.readLine();
	    try{
		num=Double.parseDouble(u);
	    }
	    catch(java.lang.NumberFormatException e){
		System.out.println("次は数字を入力してください。");
		continue;
	    }
	    break;
	}
 }
}
A.
TAの小田原です。

一般的な答え方をします。具体的な応用はgXXXXXXXさんがしてみてください(質
問の時には自分の名前を書くようにしてください)。

例えば
class C{
  void main(){
      int i = 0;
      method();
      System.out.println(i);
  }
  void method(){
      int i = 5;
  }
}

みたいなことをすると、これではメソッドで変数i の値を変えても、表示され
るのは 0 のままです。

基本的に変数のスコープ(有効範囲)はブロックの中に限られます。
したがって上の用な表現でiを二度指定しても、別々の変数として扱われてしまうのです。

では、メソッドの内容を反映させるにはどうするかというと、

1. クラス変数を用いる
2. メソッドの引数に渡す
3. メソッドの戻り値を用いる

などの方法があります。

1.の方法はクラスで定義した変数をmain()とmethod()の内部から、それぞれ参照します。

class C{
    static int i;  // クラスの中、mainの外で宣言する。
    public static void main(){
        i = 0;     // クラス変数を参照できるので宣言は不要。
        method();
        System.out.println(i);
    }
    static void method(){
        i = 5; // こちらも同様
    }
}

ただ、この方法だとちょっと面倒くさいことがあったりもするんですよね。
というのは、普通にmainから変数を参照しようとするには、その変数を 
static として宣言しなければならないのです。

2.の方法は、下のようにメソッドを定義し、

void method(int i){
     i = 5;
}

mainの中で method(i); として呼び出すというものです。こうすればiの値は
変更されます。

3.の方法は、method()にint型の変数を返させるということです。

int method(){
    int j = 5;
    return j;
}

と、戻り値をint型にして、メソッド内にreturn文をおいて 変数jの値を返す
ことにします。このメソッドを実行したら、メソッドがint型の5の値を返すこ
とになります。mainではその値を利用すればよいのです。iに代入したければ、

i = method();

としてやればよいですね。これはわかりやすくてお勧めかも。


> また、条件判断がうまくいきません。教えてください。

22行目からはじまるif 文のことでしょうか?
if文は

if(条件1){
  文a;
} else if(条件1-1){
  文b;
} else if(条件1-2){
  文c;
…
} else {
  文d;
}

と書きます。実行文が一文なら{}はつけなくても大丈夫です。
あなたのプログラムでは、 else ifの条件がないので単純な文法ミスでエラー
がでています。

下のように直すとよいでしょう。

if(t.equals("=")) break;
else if(t.equals("+")){
    Exception();
    ans=ans+num;
} else if(t.equals("-")){
    Exception();
    ans=ans-num;
} else if(t.equals("*")){
    …
} else {
    System.out.println("次は演算子を入力してください。");
	continue;
}
(田中注) プログラム注で使っている Exceptionというメソッド名は、標準クラ スライブラリに含まれる java.lang.Exception と名前が重なっているので、実 害はないかもしれませんが、避けた方がよいでしょう。似た例としてはMathや Randomという名前のクラスを作ってしまうと、同じディレクトリで java.lang.Math, java.lang.Random を使いたいプログラムがコンパイルできな い(走らない)という実害のあることもあります。

あと、このメールの後で訂正メールがでたのですが、「2.の方法」は基本データ型 の場合には適用できません。

class I{
    int i;
    I(int ii){ i=ii;}
}
class C{
  public static void main(String[] args){
      I iObj=new I(0);
      method(iObj);
      System.out.println(iObj.i);
  }
  static void method(I iObj){
      iObj.i = 5;
  }
}
のようにオブジェクト型にすればできますが、「その3」のように 値は returnで返すのが本筋ですね.
Q.
 11/11の課題をやっていると

Kadai1111.java:32: main(java.lang.String[]) は Kadai1111 で定義されています。
    public void main(String args[]) throws IOException{
                ^
というエラーが出たのですがどうすればいいのでしょうか?
A.
TAの小田原です。質問の際には名前を名乗る習慣をつけましょう。

>  11/11の課題をやっていると
> 
> Kadai1111.java:32: main(java.lang.String[]) は Kadai1111 で定義されています。
>     public void main(String args[]) throws IOException{
>                 ^
> 
> というエラーが出たのですがどうすればいいのでしょうか?

> 〜 は ○○で定義されています。

というエラーがでた場合には、"〜"という変数orメソッドが"○○"において多
重に定義されていることを示しています。これはルール違反になります。例え
ば

 int i = 0;
 int i = 0;

と、変数iを二重に宣言してしまったときなどにこのエラーがでます。この場
合はメソッドですが、同じクラス内で、同名でかつ全く同じ引数の型を取るメ
ソッドが二つ定義されてしまった時にこのエラーがでます。

ちょっとコードをみてみると、main(String[] args)がふたつあるのに気がつ
きます。これはうまく一つにまとめてやるか、メソッドの名前を変えるかする
必要があります。

では頑張って下さい。

Q.
課題の「プログラムの説明」というのは、ホームページに乗っている説明を書けばいいのでしょうか?それとも、自分がどんな風にプログラムを書いたのかの全体像をコメントの外に付け加えよ、という意味なのでしょうか?

24行目からの条件判断文で、System.out.println(ans)が重複しているので、条件判断文(条件判断文()条件判断文().....System.out.println(ans))のようにしたいのですが、どうしたらいいのでしょう?

24行目のif(t.equals("+")をif(t == "+")としたら、うまくいきませんでした。どうしてでしょう?

(プログラムは略)	
A.
TAの小田原です。

> 課題の「プログラムの説明」というのは、ホームページに乗っている説明を
> 書けばいいのでしょうか?それとも、自分がどんな風にプログラムを書いた
> のかの全体像をコメントの外に付け加えよ、という意味なのでしょうか?

このプログラムについて全く知らない人に使ってもらう時に、その人に対して
説明するように書けばよいのだと思います。

> 24行目からの条件判断文で、System.out.println(ans)が重複しているの
  で、条件判断文(条件判断文()条件判断文().....
  System.out.println(ans))のようにしたいのですが、どうしたらいいのでしょ
  う?

if-else文を抜けると、if-else文の次にあるコードを実行するので、if-else
文の後ろに

	System.out.println(ans);

とつけたせばよいです。
ただ、ここでは continue; の行によって強制的にループの先頭まで戻ってし
まうので、continue; の前に書かなければなりません。

そして、単純にcontinueの前におくと、「次は演算子を入力してください」の
後にもansが表示されてしまうことになるので、最後のelseで

	System.out.println("次は演算子を入力してください。");

としたあとで、continue;させるようにするとよいかもしれません。そのため
には else 文を{}で囲い、その中に表示とcontinueをおきます。

> 24行目のif(t.equals("+")をif(t == "+")としたら、うまくいきませんでした。どうしてでしょう?

== というのは両辺の「値」を比較するもので、「オブジェクトの内容」を比
較するものではないからです。"+" はString型のオブジェクトなので、 tが
String型のオブジェクトの変数であったからといっても == では内容を比較す
ることはできません。

ただオブジェクト型の変数に関しては==を使えないかというとそういうわけで
はありません。if (s == null) …; という文(sはString型とする)をみかける
ことがあります。オブジェクトがnull(空)であるかどうかについては、==を用
いて判定することができるのです。

正確にいうと、オブジェクトの変数というのはその中にオブジェクトへの「参
照」を値としてもっています。「参照」とはオブジェクトがメモリ上のどこに
あるかを表す「アドレス」みたいなものと思ってよいでしょう。

ですから、実はオブジェクトの参照の値自体を比較する時には == を使うこと
ができます。さっきのnullについても同じことです。

  String s = "ginseng";
  String k = "ginkgo";
  s = k;
  if (s == k)
    System.out.println("same pointer");
  else 
    System.out.println("different pointer");
    
例えば上のコードを実行することができます。

しかしアドレスの値だけではその中に何が入ってるのかはわかりません。アド
レスは違っても、中に入っているオブジェクトのある要素については同じかも
しれない。単純に==ではオブジェクトの内容までは調べられないというわけで
す。

今日の課題

今日は課題を出題しない。
次に進む