オブジェクト指向を学ぶためのJava超入門
1.目的
Javaによるプログラミングを体験することで、オブジェクト指向の考え方を学ぶ。また、サーバーやクライアントの構築についての理解も深める。既にC言語などを学んでいる大学3年生くらいを想定したテキストになっている。
2.関係知識
(1) Javaの特徴
異なるプラットフォームで実行可能なことがJavaの特徴である。C言語ではソースコードをコンパイルし、機械語に変換することで処理を実行していたが、Javaでは、コンパイルを行うとバイトコードと呼ばれるものが生成される。バイトコードは仮想マシン上で動作させることができる。仮想マシンがそのデバイスで実装されているのであれば、実際のハードウェアの種類や環境の違いを考慮せずJavaのプログラムを実行できるようになる。
(2) オブジェクトとクラスの考え方
これまで学習してきたC言語は手続き型の言語である。プログラムの中には複数の関数(入力された変数に対してどのような処理がなされるのか)が含まれている。一方で、Javaをはじめとしたオブジェクト指向の言語は、複数の変数とメソッド(変数に対してどのような処理がなされるのか)を一つのオブジェクトとして取り扱う。変数の集まりはフィールドと呼んでいる。
また、オブジェクトを抽象的に表現したものをクラスと呼んでいる。クラスはテンプレートの役割を担い、具体的にデータを入れるとオブジェクトが生成される。
例えば、銀行口座のクラスを考えてみる。そこには、銀行名・支店名・名義人・残額・暗証番号・取引履歴の変数がデータとして存在している。また、入金・出金・残額照会・暗証番号チェックなどのメソッドがある。これら全てが銀行口座というクラスの1つのまとまりの中に含まれていると考える。
新しく口座を開設したければ、銀行の支店に行って登録を行い、銀行口座のクラスのテンプレートの中にデータを入れていくことになる。そして、必要なデータが揃えばオブジェクトになる。普段、ATMから操作するときは、銀行口座のメソッドを呼び出して目的の動作(入金や出金など)を行う。
あるオブジェクトが別のオブジェクトに仕事を依頼し、内部の変数の操作を必要とするときはメッセージを送ることになる。中身を知らなくても、メッセージを受け取ったオブジェクトが仕事をするので問題ない。内部の変数を外部から直接いじることはできないようにクラスを定義するのが一般的である。これは、後述するようにカプセル化が行われているためである。
(3) オブジェクト指向の3大要素
以下の3つの要素がオブジェクト指向の特徴をよく表している。
①継承
共通の変数とメソッドを持たせたい場合、中身を引き継ぎながら新しい別のクラスを作れる。
②カプセル化
クラスを定義するとき、オブジェクトの内部のデータや構造を外部から見えなくするようにカプセル化して設計することができる。これはセキュリティを向上させるため、独立性を保つための仕組みである。内部の変数やメソッドの一部を非公開にし、利用可能な変数に対してはメソッドを介してアクセスすることができるように設定する。
③多態性
同じ名前のメソッドを呼び出しても異なる動作を定義することができる。これを多態性と呼んでいる。
(4) サーバーとクライアントの構築
インターネットを通して、サーバーはサービスを提供している。一方で、クライアントはそれを利用する側である。ネットワーク関係だとJavaの他にRuby、Pythonといった言語がよく使用されている。
3.環境設定
(1) 使用するOS
USBから起動できる「Puppy Linux」を用いる。
(2) フォルダの作成
rootには「my-documents」というフォルダが存在しているので、その直下に「my-code」というフォルダを作成する。まず、コマンドラインを立ち上げ、「cd my-documents」と入力して移動する。「mkdir my-code」と打ち込むと、フォルダが作られる。ここに自分で書いたプログラムを保存する。
(3) エディタの設定
この実習では「Geany」というエディタを使用する。まず、補完機能の設定を行う。「編集」タブから「設定」を選ぶ。出てきた画面の「エディタ」から「補完」のタブを選択。「スニペット補完」「複数行のコメントを自動挿入」「自動補完シンボル」「文章内の全ての単語を自動補完」にチェックを入れる。自動補完を表示する文字数は2にしておく。また、「引用符と括弧の自動補完」には全てチェックを入れておく。
次にコンパイルと実行の設定を行う。「ビルド」タブより「ビルドコマンドを設定」を選択する。出てきた画面のjavaコマンドの1.を選択し、「コンパイル」と打ち込む。その隣のコマンドには「javac “%f“」を、作業ディレクトリには「/root/my-documents/my-code」と打ち込む。「コマンドを実行」には「java “%e”」と入力し、作業ディレクトリには「/root/my-documents/my-code」と打ち込む。こうすることで、ビルドのタブからコンパイルや実行が簡単にできるようになる。そして、「文書」タブから「UnicodeのBOM」のチェックを外しておく。
最後にファイルの設定を行う。これは、新しいファイルを作成するたびに行う操作である。「文書」タブより、「ファイルの種類」→「プログラミング言語」→「javaソースファイル」を選択する。
4.実習
(1) コマンドラインに文字を表示させる
まず、「Hoge」という名前のクラスを作る。メインメソッドの含まれているクラスはファイル名と同じにしておかなければならないため、「Hoge.java」という名前で保存しておく。プログラムにある「class」はクラスであることを表している。「public」が先頭にあるのは、このクラスを利用したい人が誰でもアクセスすることができるようにするためである。これが無ければコマンドラインから実行できない。また、「public static void main(String[] args){ }」はメインメソッドであり、プログラムを実行したときには、ここに書かれた内容が処理される。
public class Hoge{
public static void main(String[] args){
System.out.print("ほげ");
}
}
(2) クラスを用いて文字を表示させる
「HogeHoge」と「Hoge」という名前のクラスを作り「HogeHoge.java」という名前で保存する。先述したように、クラスはあくまでもテンプレートのようなものである。そのため、「Hoge myHoge = new Hoge();」というコマンドで「Hoge」のクラスをテンプレートにして新しく「myHoge」というオブジェクトを生成している。「new」は新しくオブジェクトを作ることを表している。このようにオブジェクトを生成することをインスタンス化と呼ぶ。また、「Hoge();」でコンストラクタを呼び出している。これは新しいクラスを作成するときに必要な命令である。「hoge.run();」は「hoge」というオブジェクトにある「run()」というメソッドを呼び出すメッセージである。
class Hoge{
void run(){
System.out.print("ほげ"); // コマンドラインに表示
}
}
public class HogeHoge{ // publicなので誰でも呼び出せる
public static void main(String[] args){ // メインメソッド
Hoge myHoge = new Hoge(); // Hoge型の「myHoge」というオブジェクトを新しく生成
myHoge.run(); // myHogeのクラスにあるrunメソッドを呼び出し
}
}
(3) Javaの多態性を利用してみる
(2)のソースコードを別名で「HogePoly.java」という名前で保存する。ここでは、同じ名前で引数が異なるメソッドを作成し、多態性を体験してみることにする。例えば「void run(){ }」には引数が存在しないため、メインメソッドで「オブジェクト名.run();」で実行することができる。「void run(String moji,int kaisu){ }」であれば、メインメソッドで「オブジェクト名.run(“文字”, 回数);」で文字を繰り返し表示させることができる。このように、同じ名前のメソッドが存在するが、引数によって違う動作をさせることができる。これを多態性と呼んでいる。メソッドの呼び出し文をコメントアウトして動作を確認すること。
class Hoge{
void run(){
System.out.print("ほげ"); // コマンドラインに表示
}
void run(String moji){
System.out.print(moji); // コマンドラインに表示
}
void run(String moji,int kaisu){
int i;
for(i=0;i<kaisu;i++){
System.out.print(moji); // コマンドラインに表示
}
}
}
public class HogePoly{ // publicなので誰でも呼び出せる。
public static void main(String[] args){ // メインメソッド。
Hoge myHoge = new Hoge(); // Hoge型の「myHoge」オブジェクトを生成する。
myHoge.run();
myHoge.run("てすと");
myHoge.run("てすと",5);
}
}
(4) キーボードから入力された文字を表示させる
以下のプログラムを打ち込み、「InputHogeNum.java」として保存し実行する。Javaの特徴が現れてくるのは例外処理である。例えば、サーバーがクライアントから数字の入力を待機しているとき、クライアントが間違えて文字列を送ってくるようなときがある。その場合、例外として扱い、エラーとして何らかの処理をしなければならない。これは、try&catch構文にて行うことができる。基本的にはtryの処理を行うが、それを正常に終了できなかった場合、catchの中の処理を行うことになる。今回は文字列を取得するためにバッファーというクラスを用いている。これを使用するために「import java.io.*;」と記述している。
詳細は省略するが、「br.readLine( )」でキーボードからの入力を取得し、「Integer.parseInt( )」で文字列を数字に変換している。ここでもし、その文字列が数字ではなかった場合、エラーが出るので、catchの処理に移ることになる。数字ではないので「数字を入れてください」と表示して「System.exit(1);」でプログラムを終了させている。
import java.io.*;
public class InputHogeNum{
public static void main(String[] args){
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
try{
System.out.print("数字を入力してください>>");
System.out.println("入力したのは"+Integer.parseInt(br.readLine())+"です。");
}catch(Exception e){
System.out.print("数字をいれてください。");
System.exit(1);
}
}
}
(5) 入力された文字を繰り返し表示する
(4)のプログラムを「while(true){ }」で囲い、繰り返し処理をさせてみる。「System.exit(1);」をコメントアウトするなどして、その動作の違いを確認する。
(6) 四角形クラス「Tetragon」を作成する。
あらかじめ用意されている「TestTetragon.java」を開いて中身を確認する。この中にある四角形クラス「class Tetragon{ }」をこれから作成していく。このクラスは以下の要件を満たすものとする。長さと高さが分かれば面積が求められる種類の四角形(正方形・長方形・ひし形など)を取り扱うクラスである。
フィールド(変数)
長さ(length)・・・double型
高さ(height)・・・double型
面積(area)・・・double型
メソッド
getArea()・・・面積を返す
「class MessageIO{ }」は入出力を管理するクラスである。これはあらかじめ用意されている。中身について詳細な解説はしない。このクラスに文字列を入れると、コマンドラインにその文字列が出力され、その返答としてキーボードから打ち込まれた数字(少数)を返すクラスである。
class MessageIO{
double num;
public double InOutMessage(String msgQuestion){
try{
System.out.println(msgQuestion);
InputStreamReader keyboad = new InputStreamReader(System.in);
BufferedReader br = new BufferedReader(keyboad);
num =Double.parseDouble(br.readLine());
return num;
}catch(Exception e){
return 0;
}
}
}
(↑でも動くのですが、↓の様に修正した方が良いかもしれないと他人から指摘されました。どっちでも動くはす。2022年09月03日後から追記)
class MessageIO{
String buf;
double num;
public double InOutMessage(String msgQuestion){
System.out.print(msgQuestion);
InputStreamReaderkey = new InputStreamReader(System.in);
BufferedReaderbr = new BufferedReader(key);
try{
buf = br.readLine()
num = Double.parseDouble(buf);
return num;
}
catch(Exception e){
System.exit(1);
return 0;
}
}
}
四角形クラスがきちんと動作しているか確認するのが「public class TestTetragon{ }」である。これもあらかじめ用意されている。このテストプログラムが動作するように四角形クラスを作成する。
public class TestTetragon{
public static void main(String[] args){
//使うクラスの生成と宣言
Tetragon myTetragon= new Tetragon();
MessageIO msg = new MessageIO();
//クラスがうまく作れているかテスト
myTetragon.length=msg.InOutMessage("四角形の底辺の長さを入力してください。");
myTetragon.height =msg.InOutMessage("四角形の高さを入力してください。");
System.out.println(myTetragon.length+"×"
+myTetragon.height+"の四角形の面積は"+myTetragon.getArea()+"です。");
}
}
さて、要件を満たし、このテストを動作せることのできる四角形クラスを以下に示す。フィールドにある全ての変数をdoubleとして定義している。長さと高さをかけてareaという変数に格納し、それをメソッドの返り値にしている。this.areaはあくまでも、このクラスにあるareaという変数であることを表している。
class Tetragon{
double length,height,area,perimeter; //フィールド
public double getArea(){ //メソッド
this.area=this.length*this.height;
return area;
}
}
(7) カプセル化を行う
TestTetragonのメインメソッドに「myTetragon.length」とあるようにオブジェクトの中にある変数に直接アクセスしていることが分かる。しかし、カプセル化を行い、変数に直接アクセスしない方がセキュリティを向上させることができる。また、オブジェクトの独立性という観点からも望ましい。そのため、クラスを以下のように変更する。thisはクラス内の変数を指し示している。例えば、this.heightはクラス内の変数だが、heightはメソッドの引数である。
フィールド(変数) ⇒ 全て非公開
長さ(length)・・・double型
高さ(height)・・・double型
面積(area)・・・double型
メソッド
setLength()・・・長さを設定する
setHeight()・・・高さを設定する
getLength()・・・長さを返す
getHeight()・・・高さを返す
getArea()・・・面積を計算して返す
class Tetragon{
private double length,height,area;
public void setLength(double length){
this.length=length;
}
public void setHeight(double height){
this.height=height;
}
public double getLength(){
return this.length;
}
public double getHeight(){
return this.height;
}
public double getArea(){
this.area=this.length*this.height;
return area;
}
}
クラスの定義を変更したため、テスト用のメインメソッドも変更しなければならない。
public class TestTetragon{
public static void main(String[] args){
//使うクラスの生成と宣言
Tetragon myTetragon= new Tetragon();
MessageIO msg = new MessageIO();
//クラスがうまく作れているかテスト
myTetragon.setLength(msg.InOutMessage("四角形の底辺の長さを入力してください。"));
myTetragon.setHeight(msg.InOutMessage("四角形の高さを入力してください。"));
System.out.println(myTetragon.getLength()+"×"
+myTetragon.getHeight()+"の四角形の面積は"+myTetragon.getArea()+"です。");
}
}
(8) コンストラクタを利用する
クラスをテンプレートとしてオブジェクトを作成するとき、初期値としてあらかじめ値を指定することがある。コンストラクタと呼ばれているものである。四角形クラスに以下の内容を追加する。
public Tetragon(double length,double height){
this.length=length;
this.height=height;
}
テストメソッドも以下のように、コンストラクタを用いた記述に書き換える。「Tetragon myTetragon= new Tetragon(length,height);」によってオブジェクト作成時に長さと高さを指定している。
public class TestTetragon{
public static void main(String[] args){
//変数宣言
double length,height;
//使うクラスの生成と宣言
MessageIO msg = new MessageIO();
//クラスのがうまく作れているかテスト
length=msg.InOutMessage("四角形の底辺の長さを入力してください。");
height=msg.InOutMessage("四角形の高さを入力してください。");
Tetragon myTetragon= new Tetragon(length,height); // ここで四角形オブジェクトを作成
System.out.println(myTetragon.getLength()+"×"
+myTetragon.getHeight()+"の四角形の面積は"+myTetragon.getArea()+"です。");
}
}
(9) 継承を利用して長方形クラス「Rectangle」を作成する
長方形クラスを作成したいとする。長方形は四角形の一種であることから、四角形と同じ機能が必要となる。そのため、四角形クラスをコピー&ペーストして長方形のクラスとして編集し直すのも方法の一つである。しかし、継承をすれば、先ほど作成したフィールドやメソッドをそのまま利用できる状態で、その上に新しく機能を追加することもできる。
まず、「TestTetragon.java」をコピーして「TestRectangle.java」とリネームする。新しいクラスを以下のように作成する。「class Rectangle extends Tetragon{ }」は「Rectangle」のクラスであるが、「Tetragon」のクラスを拡張して(継承して)することを示している。「Tetragon」の機能を引き継いで、追加で新しい機能を追加して「Rectangle」としている。「public double getPerimeter( ){ }」は長方形の対角線の長さを求めるメソッドである。ちなみに四角形で一般的には成り立たず、長方形のみで成り立つ公式である。
class Rectangle extends Tetragon{
private double perimeter;
public double getPerimeter(){
this.perimeter=Math.sqrt(Math.pow(this.length,2)+Math.pow(this.height,2));
return perimeter;
}
}
テストメソッドも以下のように修正する。
public class TestRectangle{
public static void main(String[] args){
//変数宣言
double length=0;
double height=0;
//使うクラスの生成と宣言
Rectangle myRectangle= new Rectangle();
MessageIO msg = new MessageIO();
//クラスがうまく作れているかテスト
myRectangle.setLength(msg.InOutMessage("長方形の底辺の長さを入力してください。"));
myRectangle.setHeight(msg.InOutMessage("長方形の高さを入力してください。"));
System.out.println(myRectangle.getLength()+"×"+myRectangle.getHeight()
+"の長方形の対角線の長さは"+myRectangle.getPerimeter()
+"で、面積は"+myRectangle.getArea()+"です。");
}
}
(10) ピザのクラス「Pizza」を作成する(※時間が足らなければ省略⇒答えを提示)
あらかじめ用意されている「TestPizza.java」を開いて中身を確認する。先ほどは四角形クラスや長方形のクラスを作成せよとの課題であったが、記述すべき内容が全て指導書に示されていた。今回は答えを示さないので自力でクラスを作成すること。
フィールド(変数) ⇒ 全て非公開
ピザの種類(item)・・・String型
値段(price)・・・int型
いくつ頼むか(amount)・・・int型
メソッド setData()・・・種類と値段を設定
setAmount()・・・いくつ頼むか設定
getSales()・・・合計金額を返す
getSales(int amount)・・・合計金額を返す
getItem()・・・種類を返す
getPrice()・・・値段を返す
getAmount()・・・量を返す
getSales()・・・合計金額を返す
class Pizza{
private String item; private int price,amount;
public void setData(String item,int price){
this.item=item;
this.price=price;
}
public void setAmount(int amount){
this.amount=amount;
}
public int getSales(){
return this.price*this.amount;
}
public int getSales(int amount){
this.amount=amount;
return this.price*this.amount;
}
public String getItem(){
return this.item;
}
public int getPrice(){
return this.price;
}
public int getAmount(){
return this.amount;
}
}
テストメソッドを以下に示す。このテストが動作するようにピザのクラス「TestPizza」を作る。
public class TestPizza{
public static void main(String[] args){
//変数宣言
int tmp; int Type=3; int total=0;
int msgType =0; int msgNum =0; int continueflag=1;
//使うクラスの生成と宣言
Pizza[] pizzas = new Pizza[Type];
MessageIO msg = new MessageIO();
//ピザの初期化や値段設定など
pizzas[0]=new Pizza(); pizzas[1]=new Pizza(); pizzas[2]=new Pizza();
pizzas[0].setData("エビマヨ",800); //エビマヨは1つ800円設定
pizzas[1].setData("マルゲリータ",850); //マルゲリータは1つ850円設定
pizzas[2].setData("イタリアン",600); //イタリアンは1つ600円設定
pizzas[0].setAmount(0); //個数の初期化
pizzas[1].setAmount(0); //個数の初期化
pizzas[2].setAmount(0); //個数の初期化
try{
//注文フォームの繰り返し
while(true){
msgType=msg.InOutMessage("何のピザですか? 1:えびまよ 2:マルゲリータ 3:イタリアン");
msgNum =msg.InOutMessage("いくつ買いますか?");
pizzas[msgType-1].setAmount(msgNum);
continueflag =msg.InOutMessage("他の種類のピザも買うなら、1を入力してください。");
if(continueflag!=1){break;}
}
//合計金額を計算して表示
for(tmp=0;tmp<Type;tmp++){
System.out.println(pizzas[tmp].getItem()+"は"+pizzas[tmp].getAmount()+"個");
total=total+pizzas[tmp].getSales();
}
System.out.println("合計で"+total+"円になります。");
//確定処理
continueflag =msg.InOutMessage("これで注文確定するなら1を入力してください。");
if(continueflag==1){System.out.println("確定しました。宅配サービスはありません。店舗までご来店お願いします。");}
else{continueflag =msg.InOutMessage("最初からやり直してください。");}
}catch(Exception e){
System.err.println("エラーです");
System.exit(1);
}
}
}
(11) サーバーとクライアントを構築する
Javaによるプログラミングでサーバーとクライアントを構築できることを確認する。今回は既にプログラムが用意されており、その中身の解説と実行、IPアドレスとポートの設定のみを行う。まず、以下の手順で1つのPC上でサーバーとクライアントを実行してみる。
①テキストエディタで「PizzaServer.java」と「PizzaClient.java」を開き、コンパイルをかける。
②コマンドラインで「/etc/rc.d/rc.network start」と打ち込み、ネットワークを起動する。
③「PizzaServer」を実行しクライアントからの接続を待機する状態にしておく。
④新しくコマンドラインを立ち上げ、cdコマンドを用いて「PizzaClient」のある場所へ移動する。
⑤「PizzaClient」を実行する。コマンドラインに「java PizzaClient」と入力して実行する。
⑥ピザを注文してみる。
⑦サーバーから指定された文字以外を入力し、例外となるパターンでどのような動作をするか確認する。
次に、複数人でペアになり、2台のPCを用いて通信を行う。
①LANケーブルで2台のPCを接続する。
②「ip address show dev eth0」と打ち込み、IPアドレス(inetアドレス)を確認してメモする。
③サーバー側となるPCを1台決め、配布された「PizzaServer.java」を開く。「svSocket.bind(new InetSocketAddress("127.0.0.1",8765));」という行を探す。②で確認したアドレスにする。
④クライアント側のPCで配布された「PizzaClient.java」を開き、「csSocket = new Socket("127.0.0.1",8765);」を探す。このIPアドレスも②で確認したアドレスに置き換える。
⑤クライアントを複数用意して、サーバーに交互にアクセスして動作を確かめる。
※サーバーはどのようなIPのクライアントが接続されても対応できる。
※「127.0.0.1」というIPアドレスはループバックアドレスといい、自分自身のPCを指し示している。
※IPアドレスを指定したとしても、ポートが違うとサーバーにアクセスできない。窓口のようなもの。
※IPアドレスとポートの指定はソースコードではなく実行時にコマンドからも行うこともできる。
(12) 台形クラス「Trap」とそのテスト「TestTrap」を作成する (※早く終わってしまった人向けの追加課題)
さきほど、四角形クラスの機能を継承して長方形クラスを作成した。ここではまた、四角形クラスを継承して台形クラスを作ってみることにする。面積を求めるのに上底と下底が必要となるので「length2」という変数を追加する。それに伴い「setLength2()」と「getLength2()」が必要となる。また、面積を求める方法が台形の場合は普通の四角形と異なるので「getArea()」の再定義が必要である。
フィールド(変数) ⇒ 全て非公開
上底(length)・・・double型
下底(length2)・・・double型
高さ(height)・・・double型
面積(area)・・・double型
メソッド setLength()・・・長さを返す
setLength2()・・・長さを返す
setHeight()・・・高さを返す
getLength()・・・長さを返す
getLength2()・・・長さを返す
getHeight()・・・高さを返す
getArea()・・・面積を計算して返す
ただし、面積は(上底+下底)×高さ/2で求める。テストに関しては、コマンドラインより以下のように質問があった後、打ち込むものとする。「台形の下底の長さを入力してください。」「台形の上底の長さを入力してください。」「台形の高さを入力してください。」また、必要な情報を打ち込み終わった後、「この台形の面積は〇〇です。」と表示されるようにする。
class Trap extends Tetragon{
double length2;
public void setLength2(double length){
this.length2=length;
}
public double getArea(){
this.area=(length+length2)*height/2;
return area;
}
}
public class TestTrap{
public static void main(String[] args){
//変数宣言
double length=0;
double height=0;
//使うクラスの生成と宣言
Trap myTrap= new Trap();
MessageIO msg = new MessageIO();
//クラスがうまく作れているかテスト
myTrap.setLength(msg.InOutMessage("台形の下底の長さを入力してください。"));
myTrap.setLength2(msg.InOutMessage("台形の上底の長さを入力してください。"));
myTrap.setHeight(msg.InOutMessage("台形の高さを入力してください。"));
System.out.println("この台形の面積は"+myTrap.getArea()+"です。");
}
}
5.検討考察
(1) オブジェクト指向の言語を使用することのメリットは何か?簡潔に述べよ。
(2) 「PizzaClient」はどのような動作を繰り返し実行しているか?
(3) サーバーが想定していない動作をクライアントが行った場合、どうなるか?
(4) サーバーが他のクライアントと通信している間はアクセスできない。これを解決する方法を述べよ。
6.ピザを注文するサーバとクライアントのソースコード
//ピザを注文するサーバーのプログラム
import java.io.*;
import java.net.*;
import java.util.Date;
import java.text.SimpleDateFormat;
//ピザのクラス
//ピザの情報が格納されている
//情報を格納するメソッド、その種類のピザの合計金額を計算するメソッドがある。
class Pizza{
private String item;
private int price,amount;
public void setData(String item,int price){
this.item=item;
this.price=price;
}
public void setAmount(int amount){
this.amount=amount;
}
public int getSales(){
return this.price*this.amount;
}
public int getSales(int amount){
this.amount=amount;
return this.price*this.amount;
}
public String getItem(){
return this.item;
}
public int getPrice(){
return this.price;
}
public int getAmount(){
return this.amount;
}
}
class MyServer{
void run(){
/*ソケット、リーダー、ライターの宣言*/
ServerSocket svSocket=null;
Socket socket=null;
BufferedReader reader =null;
PrintWriter writer=null;
/*変数宣言*/
int tmp; //繰り返し処理用の変数
int Type=3; //ピザの種類は3種類
int total=0; //トータルの値段を表す変数
int msgType =0; //ピザの種類をやりとりする変数
int msgNum =0; //ピザの数をやりとりする変数
int continueflag=1; //次の処理に進んでいいか?を表す変数
String line=null; //一時的にクライアントからのメッセージ(文字列)を格納する変数
Date today=new Date(); //日付を取得するための変数
/*ピザのクラスを作成*/
Pizza[] pizzas = new Pizza[Type];
/*ピザの初期化や値段設定など*/
pizzas[0]=new Pizza(); //ピザのクラスを作成
pizzas[1]=new Pizza(); //ピザのクラスを作成
pizzas[2]=new Pizza(); //ピザのクラスを作成
pizzas[0].setData("エビマヨ",800); //エビマヨは1つ800円設定
pizzas[1].setData("マルゲリータ",850); //マルゲリータは1つ850円設定
pizzas[2].setData("イタリアン",600); //イタリアンは1つ600円設定
pizzas[0].setAmount(0); //個数の初期化
pizzas[1].setAmount(0); //個数の初期化
pizzas[2].setAmount(0); //個数の初期化
/*接続に関するクラス作成・ソケット開放・変数の定義*/
try{
svSocket = new ServerSocket();
svSocket.bind(new InetSocketAddress("127.0.0.1",8765));
socket=svSocket.accept(); //ソケットを開放する。
reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
writer=new PrintWriter(socket.getOutputStream(),true); //writerをソケットからの入力として定義する。
/*接続できない場合のエラーをキャッチ*/
}catch(Exception e){
System.out.println("接続エラー");
System.exit(1);
}
/*クライアントとのやりとり「聞かれる→答える」の連続*/
while(true){
/*ピザの種類と数を聞いてクラスに代入する処理。*/
try{
writer.println("何のピザですか? 1:えびまよ(800円) 2:マルゲリータ(850円) 3:イタリアン(600円)");
line=reader.readLine(); // クライアントから文字を受けとる。lineは使いまわし。
msgType=Integer.parseInt(line); // lineは文字データなので数字に変換してmsgTypeに代入。
writer.println("いくつ買いますか?");
line=reader.readLine(); // クライアントから文字を受けとる。lineは使いまわし。
msgNum=Integer.parseInt(line); // lineは文字データなので数字に変換してmsgNumに代入。
pizzas[msgType-1].setAmount(msgNum); // ここでピザの個数をクラスに代入する。
}catch(Exception e){
// 数字以外の文字が入ったら数字に変換するコマンドInteger.parseIntができないので例外処理になる。
// また、ピザの種類の配列を越えた場合も配列が存在しないため、エラーがでる。これらの場合はループ終了。
writer.println("変な数字が入力されました。最初からやり直してください。");
break;
}
try{
/*続けて選ぶならelse if(continueflag==0)の分岐に入り、スルーする。*/
writer.println("他の種類のピザも買いますか?続けるなら0、お会計なら1を入力。");
line=reader.readLine(); // クライアントから文字を受けとる。lineは使いまわし。
continueflag=Integer.parseInt(line); // lineは文字データなので数字に変換してcontinueflagに代入。
//お会計する場合の分岐
if(continueflag==1){
System.out.println(today.toString()+"の注文"); // 日付を取得してサーバー自身に表示
/*いまの注文状況の表示*/
for(tmp=0;tmp<Type;tmp++){
writer.println(pizzas[tmp].getItem()+"は"+pizzas[tmp].getAmount()+"個");
System.out.println(pizzas[tmp].getItem()+"は"+pizzas[tmp].getAmount()+"個");
total=total+pizzas[tmp].getSales(); //トータルの価格を計算
line=reader.readLine(); // ダミーでいれた。クライアントは「聞かれる→答える」の動作を繰り替えしているため「聞かれる→聞かれる」に対応することができない。
}
writer.println("合計で"+total+"円になります。これで注文確定しました。宅配サービスはありません。店舗までご来店お願いします。もし、誤入力などがあり、取り消すのであれば電話してください。");
svSocket.close(); //すべて終わったのでソケットを閉じる
socket.close(); //すべて終わったのでソケットを閉じる
}else if(continueflag==0){
//続ける場合は何もしない
}else{
//0と1以外が入力されたときはループ終了。
writer.println("変な数字が入力されました。最初からやり直してください。");
break; //クライアントとのやりとりを終了。
}
}catch(Exception e){
/*数字以外が入力されたときの例外処理*/
writer.println("文字が入力されました。最初からやり直してください。");
break; //クライアントとのやりとりを終了。
}
}
//クライアントとのやりとりが終わった後の処理
try{
svSocket.close();
socket.close();
}catch(Exception e){
System.exit(1);
}
}
}
public class PizzaServer{
public static void main(String[] args){
MyServer Server=new MyServer(); //サーバをたてる
while(true){
Server.run(); //繰り返し実行
}
}
}
/*
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.InetSocketAddress;
import java.net.Socket;
*/
//ピザを注文するクライアントのプログラム
import java.io.*;
import java.net.*;
class MyClient{
void run(){
/*ソケット、リーダー、ライターの宣言*/
Socket csSocket=null;
BufferedReader csInput =null;
PrintWriter writer=null;
BufferedReader reader =null;
String line=null;
try{
/*接続に関するクラス作成・ソケット開放・変数の定義*/
csSocket = new Socket("127.0.0.1",8765);
csInput=new BufferedReader(new InputStreamReader(System.in));
writer=new PrintWriter(csSocket.getOutputStream(),true);
reader = new BufferedReader(new InputStreamReader(csSocket.getInputStream()));
/*サーバーとのやりとり「聞かれる→答える」の連続*/
while(true){
line=reader.readLine(); // サーバから文字を受けとる。lineは使いまわし。
if(line==null){
break; // サーバから文字が受け取れなかった場合。終了。
}
System.out.println(line); // 受け取った文字を表示する。
line=csInput.readLine(); // 文字を入力する。
writer.println(line); // 入力した文字をサーバに文字を送る。lineは使いまわし。
}
}catch(Exception e){
e.printStackTrace();
}finally{
try{
if(reader!=null){
reader.close();
}
if(writer!=null){
writer.close();
}
if(csSocket!=null){
csSocket.close();
}
if(csSocket!=null){
csSocket.close();
}
System.out.println("クライアント側が終了");
}catch(IOException e){
e.printStackTrace();
}
}
}
}
public class PizzaClient{
public static void main(String[] args){
MyClient Client=new MyClient();
Client.run();
}
}