プログラム勉強ブログ

やったことをまとめるブログ

これまでに作ったプラグインは主に管理画面での操作用に作ってきました。

大まかな動きとしては、DBの登録/修正/削除が管理画面からできていましたが、今度はそれをユーザ画面側に表示することを行います。

ユーザ画面側では、「投稿」「固定ページ」へ自由に表示する為に「ショートコード」という書き方をすることで、DBの値を表示することができます。

これまでに作ったプラグインの中身を表示する為に、以下のような書き方をします。
(便宜上、プラグインプログラムの一番下に書き足します)

/**
 * ショートコード
 */
function samplelistFunction()
{
	//データ一覧
	$disp_html =<<< EOL
<form action="" method="post">
<h2>データ一覧(ショートコードテスト)</h2>
<table>
	<tr>
		<th nowrap>ID</th>
		<th nowrap>名前</th>
		<th nowrap>登録日時</th>
	</tr>
EOL;

	global $wpdb;

	$tbl_name = $wpdb->prefix . 'sample_mst';
	$sql = "SELECT * FROM {$tbl_name} ORDER BY id;";
	$rows = $wpdb->get_results($sql);

	foreach($rows as $row) {

$disp_html .=<<<EOL
	<tr>
		<td>{$row->id}</td>
		<td>{$row->sample_name}</td>
		<td>{$row->create_date}</td>
		<td>
	</tr>
EOL;
	}

	$disp_html .=<<< EOL
	</table>
	</form>
EOL;

	return $disp_html;
}
//「[samplelist]という記述を投稿画面や固定ページに記述する」
add_shortcode('samplelist', 'samplelistFunction');

上記の書き方をすることにより、ユーザ側で、ショートコードタグ「[samplelist]」を記述すると、その部分にDBアクセスした一覧表示テーブルを表示することができます。

色々と応用することができますが、まずは表示できるところまでで、一区切りとします。

データの更新系を考えてみます。

データを更新する場合、当然のように入力値をチェックしないと、整合性が取れないデータがユーザからの入力されてしまいます。

今回はデータの新規登録時に値をチェックする仕組みを入れてみます。

まずは、データをチェックするタイミングを考えます。
新規登録からデータを登録する「登録内容を確認する」(下記、画像ファイルを参照)というボタンを押した時の動作をきっかけにチェックを入れます。

次にソース上で、そのボタンが押された箇所の処理をみます。

    /**
     * 登録確認
     */
    function regist_check()
    {
        $sample_name = $_REQUEST["sample_name"];

        //データ一覧
        echo <<< EOL
<form action="" method="post">
    <h2>データ登録確認</h2>
<table border="1">
    <tr>
        <td>NAME</td>
        <td>{$sample_name}</td>
    </tr>
</table>

<input type="hidden" name="sample_name" value="{$sample_name}">

<input type='submit' name='submit[regist_exec]' class='button-primary' value='登録する' />
<input type='submit' name='submit[regist]' class='button-primary' value='戻る' />
EOL;
        echo "</form>";
    }

単純に入力画面で入力した文字列をそのまま変数に代入して、確認画面を出していることがわかります。

このままでは空白や、危険な文字列が入力された場合に、意図しない動作になってしまいます。

次のように修正します。

    /**
     * 登録確認
     */
    function regist_check()
    {
        //入力値が空白の場合の処理(ここでは単純に0バイトだったら)
        if (!strlen($_REQUEST["sample_name"])) {
            self::regist(false);
            return;
        }
        
        $sample_name = esc_attr($_REQUEST["sample_name"]);

        //データ一覧
        echo <<< EOL
<form action="" method="post">
    <h2>データ登録確認</h2>
<table border="1">
    <tr>
        <td>NAME</td>
        <td>{$sample_name}</td>
    </tr>
</table>

<input type="hidden" name="sample_name" value="{$sample_name}">

<input type='submit' name='submit[regist_exec]' class='button-primary' value='登録する' />
<input type='submit' name='submit[regist]' class='button-primary' value='戻る' />
EOL;
        echo "</form>";
    }

チェック方法が少し荒っぽいですが、未入力チェックと、< > & ” ‘ (小なり、大なり、アンパサンド、ダブルクォート、シングルクォート) 文字参照をエンコードする関数「esc_attr」を処理に入れています。

また、もし入力された値が空白だった場合「self::regist(false)」という形で、また登録画面を呼び出しています。

引数にfalseを渡し、チェック時に何が起きたのかを新規登録画面へ伝えます。

新規登録画面側の処理は、以下のように修正します。

    /**
     * 登録
     */
    function regist($error_message_flg = null)
    {
        if ($error_message_flg !== false) {
            $sample_name = esc_attr($_REQUEST["sample_name"]);
        }

        echo <<< EOL
<form action="" method="post">
    <h2>データ登録</h2>
EOL;

        //エラーメッセージのフラグがfalseの場合、メッセージを表示する
        if ($error_message_flg == false) {
            echo "<div class='updated fade'><p><strong>";
            echo _e('NAMEを入力してください');
            echo "</strong></p></div>";
        }

        echo <<< EOL
    <table border="1">
        <tr>
            <td>NAME</td>
            <td>
                <input type="text" name="sample_name" value="{$sample_name}">
            </td>
        </tr>
    </table>

    <input type='submit' name='submit[regist_check]' class='button-primary' value='登録内容を確認する' />
    <input type='submit' name='submit[]' class='button-primary' value='戻る' />
</form>
EOL;
    }

このように処理を書くことで、未入力の場合には「NAMEを入力してください」
というメッセージが表示されるようになります。

今回は未入力チェックのみの実装でしたが、その他にも入力項目の種類に合わせたチェック方法を実装する場面が出てきます。
当然のごとくエラーの表示場所や、文言、レイアウトなどはもっと工夫する必要があります。

書き方は数多くあるので、もっと良い書き方を考えて改良する予定です。

前回までのプログラムに、新規登録画面を追加します。

まずは、「登録画面」「登録確認画面」「登録完了画面」の処理の為に分岐を作っておきます。
そしてそれぞれ違うメソッドを呼ぶように関数を追加。

	/**
	 * 管理画面のHTMLの生成と表示
	 */
	function sample_plugin()
	{
		if (isset($_REQUEST["submit"]["detail"])) {
			//詳細
			self::detail();
		} else if (isset($_REQUEST["submit"]["edit"])) {
			//修正
			self::edit();
		} else if (isset($_REQUEST["submit"]["edit_check"])) {
			//修正確認
			self::edit_check();
		} else if (isset($_REQUEST["submit"]["edit_exec"])) {
			//修正実行
			self::edit_exec();
		} else if (isset($_REQUEST["submit"]["delete_check"])) {
			//削除確認
			self::delete_check();
		} else if (isset($_REQUEST["submit"]["delete_exec"])) {
			//削除実行
			self::delete_exec();
		} else if (isset($_REQUEST["submit"]["regist"])) {
			//新規登録
			self::regist();
		} else if (isset($_REQUEST["submit"]["regist_check"])) {
			//新規登録確認
			self::regist_check();
		} else if (isset($_REQUEST["submit"]["regist_exec"])) {
			//新規登録
			self::regist_exec();
		} else {
			//初期表示
			self::disp();
		}
	}

次に、プログラム内の任意の箇所に、以下の関数を追加します。

	/**
	 * 登録
	 */
	function regist()
	{
		if (isset($_REQUEST["sample_name"])) {
			$sample_name = $_REQUEST["sample_name"];
		}

		echo <<< EOL
<form action="" method="post">
	<h2>データ登録</h2>
	<table border="1">
		<tr>
			<td>NAME</td>
			<td>
				<input type="text" name="sample_name" value="{$sample_name}">
			</td>
		</tr>
	</table>

	<input type='submit' name='submit[regist_check]' class='button-primary' value='登録内容を確認する' />
	<input type='submit' name='submit[]' class='button-primary' value='戻る' />
</form>
EOL;
	}

	/**
	 * 登録確認
	 */
	function regist_check()
	{
		$sample_name = $_REQUEST["sample_name"];

		//データ一覧
		echo <<< EOL
<form action="" method="post">
	<h2>データ登録確認</h2>
EOL;

		echo <<<EOL
<table border="1">
	<tr>
		<td>NAME</td>
		<td>{$sample_name}</td>
	</tr>
</table>

<input type="hidden" name="sample_name" value="{$sample_name}">

<input type='submit' name='submit[regist_exec]' class='button-primary' value='登録する' />
<input type='submit' name='submit[regist]' class='button-primary' value='戻る' />
EOL;
		echo "</form>";
	}


	/**
	 * 登録実行
	 */
	function regist_exec()
	{
		global $wpdb;

		$sample_name = $_REQUEST["sample_name"];

		//投稿を登録
		$table_name = $wpdb->prefix . 'sample_mst';
		$result = $wpdb->insert(
			$table_name,
			array(
				'sample_name' => $sample_name,
				'create_date' => current_time('mysql')
			)
		);

		//データ一覧
		echo <<< EOL
<form action="" method="post">
	<h2>データ登録</h2>
	<div class='updated fade'><p><strong>
EOL;
		echo _e('登録が完了しました');
		echo <<<EOL
</strong></p></div>
<input type='submit' name='submit[]' class='button-primary' value='戻る' />
EOL;
		echo "</form>";
	}

これで、新規登録ボタンを押した後に、登録フォーム→確認画面→完了画面へ遷移し、最後にDB内にデータを保存する流れができます。

■新規登録ボタン追加

■登録フォームの表示

■確認画面の表示

■完了画面の表示

ちなみに入力値チェックは確認画面のタイミングで書く必要がありますので、次の課題として進めます。

かなり簡単な登録画面(項目も1個のみ)ですが、文字列の登録が管理画面上から可能になりました。

作成中のプログラムに、削除機能をつけてみます。

まずは、function sample_plugin()に対して、削除確認画面と、
削除実行画面のメソッドをつけます。

	/**
	 * 管理画面のHTMLの生成と表示
	 */
	function sample_plugin()
	{
		if (isset($_REQUEST["submit"]["detail"])) {
			//詳細
			self::detail();
		} else if (isset($_REQUEST["submit"]["edit"])) {
			//修正
			self::edit();
		} else if (isset($_REQUEST["submit"]["edit_check"])) {
			//修正確認
			self::edit_check();
		} else if (isset($_REQUEST["submit"]["edit_exec"])) {
			//修正実行
			self::edit_exec();
		} else if (isset($_REQUEST["submit"]["delete_check"])) {
			//削除確認
			self::delete_check();
		} else if (isset($_REQUEST["submit"]["delete_exec"])) {
			//削除実行
			self::delete_exec();
		} else {
			//初期表示
			self::disp();
		}
	}

次に関数delete_checkとdelete_execをそれぞれ新規作成します。

削除確認画面は以下のように書きます。
これは修正確認画面の応用なので、書き方によっては同一にできるかと思います。

	/**
	 * 削除確認
	 */
	function delete_check()
	{
		$form_id = $_REQUEST["form_id"];

		global $wpdb;

		$tbl_name = $wpdb->prefix . 'sample_mst';
		$sql = "SELECT * FROM {$tbl_name} WHERE id = %d;";
		$prepared = $wpdb->prepare($sql, $form_id);
		$rows = $wpdb->get_results($prepared, ARRAY_A);

		//データ一覧
		echo <<< EOL
<form action="" method="post">
	<h2>データ削除確認</h2>
	<table border="1">
		<tr>
			<td>ID</td>
			<td>{$form_id}</td>
		</tr>
		<tr>
			<td>NAME</td>
			<td>{$rows[0]["sample_name"]}</td>
		</tr>
		<tr>
			<td>登録日時</td>
			<td>{$rows[0]["create_date"]}</td>
		</tr>
	</table>

	<input type="hidden" name="form_id" value="{$form_id}">

	<input type='submit' name='submit[delete_exec]' class='button-primary' value='削除する' />
	<input type='submit' name='submit[edit]' class='button-primary' value='戻る' />
EOL;
		echo "</form>";
	}

次に削除実行の関数を追加します。

	/**
	 * 削除実行
	 */
	function delete_exec()
	{
		$form_id = $_REQUEST["form_id"];

		//データを削除
		global $wpdb;
		$tbl_name = $wpdb->prefix . 'sample_mst';
		$sql = "DELETE FROM {$tbl_name} WHERE id = %s;";
		$dlt = $wpdb->query($wpdb->prepare($sql, $form_id));

		//データ一覧
		echo <<< EOL
<form action="" method="post">
	<h2>データ編集認</h2>
EOL;

		echo "<div class='updated fade'><p><strong>";
		echo _e('削除が完了しました');
		echo "</strong></p></div>";

		echo <<<EOL
<input type='submit' name='submit[]' class='button-primary' value='戻る' />
EOL;
		echo "</form>";
	}

上記の機能を書くことで、データ一覧画面から詳細画面→削除確認画面→削除実行の流れができています。
書き方は千差万別でもっと効率のよい書き方もあると思います。
そこは今度プラグインの調整を続けていく際に強化していきます。

前回作った詳細一覧に対し、「編集」ボタンをつけて、
1レコードづつデータ編集できるようにする。

各ボタンごとにメソッドを分けて処理を書いたので、ソースは少し長めになります。
ボタンごとにname属性に違う記述(やりたい役割ごとに名称を変える)し、
そのname属性をもとにfunction sample_plugin()の中で呼び出すメソッドを分けています。

なにも押下されなかった場合は、初期表示となり、その後、詳細表示(一覧)、
編集、編集確認、編集完了、というそれぞれのメソッドを用意しています。

もっと良い書き方があると思いますが、ひとまず更新はできているのでブログ記事化します。

	/**
	 * 管理画面のHTMLの生成と表示
	 */
	function sample_plugin()
	{
		if (isset($_REQUEST["submit"]["detail"])) {
			//詳細
			self::detail();
		} else if (isset($_REQUEST["submit"]["edit"])) {
			//修正
			self::edit();
		} else if (isset($_REQUEST["submit"]["edit_check"])) {
			//修正確認
			self::edit_check();
		} else if (isset($_REQUEST["submit"]["edit_exec"])) {
			//修正実行
			self::edit_exec();
		} else {
			//初期表示
			self::disp();
		}
	}

	/**
	 * 初期表示
	 */
	function disp()
	{
		//データ一覧
		echo <<< EOL
<form action="" method="post">
	<h2>データ一覧</h2>
	<table>
		<tr>
			<th nowrap>ID</th>
			<th nowrap>名前</th>
			<th nowrap>登録日時</th>
			<th nowrap>詳細</th>
			<th nowrap>編集</th>
		</tr>
EOL;

		global $wpdb;

		$tbl_name = $wpdb->prefix . 'sample_mst';
		$sql = "SELECT * FROM {$tbl_name} ORDER BY id;";
		$rows = $wpdb->get_results($sql);

		foreach($rows as $row) {
			echo "<tr>";
			echo "<td>" . $row->id . "</td>";
			echo "<td>" . $row->sample_name . "</td>";
			echo "<td>" . $row->create_date . "</td>";
			echo "<td>";
			echo "<input type='submit' name='submit[detail][" . $row->id . "]'";
			echo " class='button-primary' value='詳細' />";
			echo "</td>";
			echo "<td>";
			echo "<input type='submit' name='submit[edit][" . $row->id . "]'";
			echo " class='button-primary' value='編集' />";
			echo "</td>";
			echo "</tr>";
		}
		echo "</table>";
		echo "</form>";
	}

	/**
	 * 詳細表示
	 */
	function detail()
	{
		//押されたボタンのIDを取得する
		if (array_search("詳細", $_REQUEST["submit"]["detail"])) {
			$form_id = array_search("詳細", $_REQUEST["submit"]["detail"]);
		}

		//データ一覧
		echo <<< EOL
<form action="" method="post">
	<h2>データ詳細</h2>
EOL;

		global $wpdb;

		$tbl_name = $wpdb->prefix . 'sample_mst';
		$sql = "SELECT * FROM {$tbl_name} WHERE id = %d;";
		$prepared = $wpdb->prepare($sql, $form_id);
		$rows = $wpdb->get_results($prepared, ARRAY_A);

		echo <<<EOL
<table border="1">
	<tr>
		<td>ID</td>
		<td>{$rows[0]["id"]}</td>
	</tr>
	<tr>
		<td>NAME</td>
		<td>{$rows[0]["sample_name"]}</td>
	</tr>
	<tr>
		<td>登録日時</td>
		<td>{$rows[0]["create_date"]}</td>
	</tr>
</table>
<input type='submit' name='submit[]' class='button-primary' value='戻る' />
EOL;
		echo "</form>";
	}

	/**
	 * 修正
	 */
	function edit()
	{
		if (isset($_REQUEST["form_id"])) {
			$form_id = $_REQUEST["form_id"];
			$sample_name = $_REQUEST["sample_name"];
			$create_date = $_REQUEST["create_date"];
		} else {
			//押されたボタンのIDを取得する
			if (array_search("編集", $_REQUEST["submit"]["edit"])) {
				$form_id = array_search("編集", $_REQUEST["submit"]["edit"]);
			}

			global $wpdb;

			$tbl_name = $wpdb->prefix . 'sample_mst';
			$sql = "SELECT * FROM {$tbl_name} WHERE id = %d;";
			$prepared = $wpdb->prepare($sql, $form_id);
			$rows = $wpdb->get_results($prepared, ARRAY_A);

			$sample_name = $rows[0]["sample_name"];
			$create_date = $rows[0]["create_date"];
		}

		//データ一覧
		echo <<< EOL
<form action="" method="post">
	<h2>データ編集</h2>
EOL;


		echo <<<EOL
<table border="1">
	<tr>
		<td>ID</td>
		<td>{$form_id}</td>
	</tr>
	<tr>
		<td>NAME</td>
		<td>
			<input type="text" name="sample_name" value="{$sample_name}">
		</td>
	</tr>
	<tr>
		<td>登録日時</td>
		<td>{$create_date}</td>
	</tr>
</table>

<input type="hidden" name="form_id" value="{$form_id}">
<input type="hidden" name="create_date" value="{$create_date}">

<input type='submit' name='submit[edit_check]' class='button-primary' value='編集内容を確認する' />
<input type='submit' name='submit[]' class='button-primary' value='戻る' />
EOL;
		echo "</form>";
	}

	/**
	 * 編集確認
	 */
	function edit_check()
	{
		$form_id = $_REQUEST["form_id"];
		$sample_name = $_REQUEST["sample_name"];
		$create_date = $_REQUEST["create_date"];

		//データ一覧
		echo <<< EOL
<form action="" method="post">
	<h2>データ編集確認</h2>
EOL;

		echo <<<EOL
<table border="1">
	<tr>
		<td>ID</td>
		<td>{$form_id}</td>
	</tr>
	<tr>
		<td>NAME</td>
		<td>{$sample_name}</td>
	</tr>
</table>

<input type="hidden" name="form_id" value="{$form_id}">
<input type="hidden" name="sample_name" value="{$sample_name}">
<input type="hidden" name="create_date" value="{$create_date}">

<input type='submit' name='submit[edit_exec]' class='button-primary' value='編集する' />
<input type='submit' name='submit[edit]' class='button-primary' value='戻る' />
EOL;
		echo "</form>";
	}

	/**
	 * 編集実行
	 */
	function edit_exec()
	{
		global $wpdb;

		$form_id = $_REQUEST["form_id"];
		$sample_name = $_REQUEST["sample_name"];
		$update_date = date("Y-m-d H:i:s");

		//投稿を更新
		$tbl_name = $wpdb->prefix . 'sample_mst';
		$result = $wpdb->update(
			$tbl_name,
			array('sample_name' => $sample_name,),
			array('id' => $form_id,),
			array('%s'),
			array('%d')
		);

		//データ一覧
		echo <<< EOL
<form action="" method="post">
	<h2>データ編集認</h2>
EOL;

		echo "<div class='updated fade'><p><strong>";
		echo _e('更新が完了しました');
		echo "</strong></p></div>";

		echo <<<EOL
<input type='submit' name='submit[]' class='button-primary' value='戻る' />
EOL;
		echo "</form>";
	}

上記のコードを実行した時の画面を張っておきます。
一覧表示から、編集、確認、完了という、一般的な編集処理ができます。

見た目のレイアウト調整については別な機会にまとめて、整理しようと思っています。
まずは確実に動くものを仕上げていきます。

■一覧画面

■編集を押した時

■編集確認画面

■編集完了画面

管理画面に詳細画面を追加する

先日追加した「function sample_plugin()」には、テーブルの値をだた一覧表示しただけでしたので、今回はその一覧から詳細画面を表示する。という部分を作ってみます。

まず、sample_plugin()という関数は以下のように書いていました。

	/**
	 * 管理画面のHTMLの生成と表示
	 */
	function sample_plugin()
	{
		echo <<< EOL
<h1>sample_plugin メインページ</h1>
ここにHTMLテンプレートを生成していきます。
EOL;

		//データ一覧
		echo <<< EOL
		<h2>データ一覧</h2>
		<table>
			<tr>
				<th nowrap>ID</th>
				<th nowrap>名前</th>
				<th nowrap>登録日時</th>
				<th nowrap>詳細</th>
			</tr>
EOL;

		global $wpdb;
		$tbl_name = $wpdb->prefix . 'sample_mst';
		$sql = "SELECT * FROM {$tbl_name} ORDER BY id;";
		$rows = $wpdb->get_results($sql);

		foreach($rows as $row) {
			echo "<tr>";
			echo "<td nowrap>" . $row->id . "</td>";
			echo "<td nowrap>" . $row->sample_name . "</td>";
			echo "<td nowrap>" . $row->create_date . "</td>";
			echo "<td nowrap>";
			echo "<input type='submit' name='submit[detail][" . $row->id . "]'";
			echo " class='button-primary' value='詳細' />";
			echo "</tr>";
		}
		echo "</table>";
	}

上記の部分を、別な関数に移し、ボタンによって表示する情報を切り替えるように改造します。

ボタンごとに画面を切り替える為、function sample_plugin()を以下のように変更しました。

/**
 * 管理画面のHTMLの生成と表示
 */
function sample_plugin()
{
	if (isset($_REQUEST["submit"]["detail"])) {
		self::detail(); //詳細
	} else if (isset($_REQUEST["submit"]["edit"])) {
		self::edit(); //修正
	} else {
		self::disp(); //初期表示
	}
}

「$_REQUEST[“submit”][“detail”]」という部分が、HTML上にあるボタンのname属性によって切り替えるポイントとなります。(詳細か編集か初期表示かを分けています)
もっと良い方法があるかもしれませんが、間違いなく動作するので、一旦これで進めます。

次に「詳細画面」を表示する為の関数function detail()を新規に書きます。

	/**
	 * 詳細表示
	 */
	function detail()
	{
		//押されたボタンのIDを取得する
		if (array_search("詳細", $_REQUEST["submit"]["detail"])) {
			$form_id = array_search("詳細", $_REQUEST["submit"]["detail"]);
		}

		//データ一覧
		echo <<< EOL
<form action="" method="post">
	<h2>データ詳細</h2>
EOL;

		global $wpdb;

		$tbl_name = $wpdb->prefix . 'sample_mst';
		$sql = "SELECT * FROM {$tbl_name} WHERE id = %d;";
		$prepared = $wpdb->prepare($sql, $form_id);
		$rows = $wpdb->get_results($prepared, ARRAY_A);

		echo <<<EOL
<table border="1">
	<tr>
		<td>ID</td>
		<td>{$rows[0]["id"]}</td>
	</tr>
	<tr>
		<td>NAME</td>
		<td>{$rows[0]["sample_name"]}</td>
	</tr>
	<tr>
		<td>登録日時</td>
		<td>{$rows[0]["create_date"]}</td>
	</tr>
</table>
<input type='submit' name='submit[]' class='button-primary' value='戻る' />
EOL;
		echo "</form>";
	}

上記の関数を用意することで、一覧表示の「詳細」ボタンを押した後に、詳細画面へ遷移し、各データの詳細を表示することが可能になります。

応用次第では、管理画面の色々な場面で編集用の画面が作れるようになります。

管理画面にアクセスした際、初期表示を行います。

あらかじめテーブル「wp_sample_mst」にテスト用データを3件程登録しておきます。
前回の投稿で作成した関数「function sample_plugin()」に、下記のようにデータベースにアクセスしてデータを取得するSQLを書きます。

	/**
	 * 管理画面のHTMLの生成と表示
	 */
	function sample_plugin()
	{
		echo <<< EOL
<h1>sample_plugin メインページ</h1>
ここにHTMLテンプレートを生成していきます。
EOL;
		//データ一覧
		echo <<< EOL
<h2>データ一覧</h2>
<table>
<tr>
<th nowrap>ID</th>
<th nowrap>名前</th>
<th nowrap>登録日時</th>
			</tr>
EOL;
		global $wpdb;
		$tbl_name = $wpdb->prefix . 'sample_mst';
		$sql = "SELECT * FROM {$tbl_name} ORDER BY id;";
		$rows = $wpdb->get_results($sql);
		foreach($rows as $row) {
			echo "<tr>";
			echo "<td nowrap>" . $row->id . "</td>";
			echo "<td nowrap>" . $row->sample_name . "</td>";
			echo "<td nowrap>" . $row->create_date . "</td>";
			echo "</tr>";
		}
		echo "</table>";
	}

上記の例では、テストデータを3件取得して表示しています。

実行結果は以下のようになります。

テーブルに登録したテストデータ3件の表示がされました。
今後、このデータを修正、削除、登録ができるように進めます。

前回までの投稿で、wordpress管理画面のメニューに「SamplePlugin1」というメニューが追加されました。

(下記画像を参照)

001

今回はこのメニューをクリックした時、右側のページにHTMLを生成して表示する動作を作ってみます。

コードは以下のように記述します。
関数「add_pages」の部分の「add_menu_page」の引数に
「array($this, ‘sample_plugin’)」と書いている部分があります。

	function add_pages()
	{
		add_menu_page(
			'SamplePlugin1 Plugin Settings',
			'SamplePlugin1',
			'manage_options',
			'SamplePluginMenu',
			array($this, 'sample_plugin')
		);
	}

この「sample_plugin」という名前の関数(メソッド)を新規作成します。
例えば、以下のように書きます。

	/**
	 * 管理画面のHTMLの生成と表示
	 */
	function sample_plugin()
	{
		
		echo <<< EOL



<h1>sample_plugin メインページ</h1>


ここにHTMLテンプレートを生成していきます。

EOL;
		
	}

上記のように記述して、管理メニューの「SamplePlugin1」を押すと、画面は以下のように表示されます。

002

このようにして、管理項目などを表示するHTMLを生成していきます。

プラグインのアンインストール時の動きを書きます。

まず、前回のプラグイン停止時の動きと同じように、クラスの外で
次のように書きます。

//アンインストール時の設定
register_uninstall_hook(__FILE__, array('SamplePlugin1', 'myplugin_uninstall'));

続いて、クラスの中で次のように書きます。

	/**
	 * 停止時の実行
	 */
	function myplugin_unactivate()
	{
		global $wpdb;

		//削除するテーブル名の決定
		$table_name = $wpdb->prefix . 'sample_mst';

		//テーブル削除
		$sql_drop = "DROP TABLE " . $table_name . ";";
		$wpdb->query($sql_drop);
	}

こうすることで、プラグインのアンインストール時に、メソッド「myplugin_unactivate」が呼び出されることになります。

プラグイン停止時の動き

前回の投稿では、プラグインのインストール時にテーブル作成を行いました。

今回はプラグイン停止時の処理を追加します。

停止時はプログラムの先頭で以下の処理を書いてから

//停止時の設定
register_deactivation_hook(__FILE__, array('SamplePlugin1', 'myplugin_unactivate'));

↓のように停止時用の関数を作成します。

/**
* 停止時の実行
*/
function myplugin_unactivate()
{
	//テーブルデータ消去などなど
	global $wpdb;

	//削除するテーブルの決定
	$table_name = $wpdb->prefix . 'sample_mst';

	//テーブル削除
	$sql_delete = "DELETE FROM " . $table_name . ";";
	$wpdb->query($sql_delete);
}

こうする事で、プラグイン停止時の動きを制御します。

また、上記の例では、deleteを行ってテーブルの内容をクリアしていますが、
AUTO_INCREMENTを使用しているテーブルの場合などの場合は

$sql_delete = "DELETE FROM " . $table_name . ";";

のところを

$sql_delete = "TRUNCATE TABLE " . $table_name . ";";

としてもいいと思われます。

削除時になにを残して、何を消すのか、プラグインの性質をよく考えて
決めるのがよいと思います。

次回はプラグインのアンインストール時の動きを書きます。

前回の投稿ではプラグインのインストール時に特定のメソッドを呼ぶ方法を書きました。
それを踏まえて、今回はプラグインのインストール時にデータベース内に、簡単なテーブルを作成することをやってみます。

まずは、myplugin_activateメソッドが呼ばれた時に(インストールされた時に)、テーブル作成するSQL(DDL文)を呼びます。

function myplugin_activate()
{
	//テーブル作成などなど
	self::create_tables_sample_mst();
	
}

メソッド名に特に決まりはなく、任意の名前のものを書きます。

次に、呼ばれる側のメソッドを書きます。

function create_tables_sample_mst()
{
	//ここでテーブル作成を行う
	
	
}

これで、インストール時にcreate_tables_sample_mstメソッドが呼ばれて
テーブル作成などができるようになります。

では、実際に、create_tables_sample_mstメソッド内にテーブル作成のSQLを書いてみます。

function create_tables_sample_mst()
{
	global $wpdb;

	$charset_collate = "";

	//接頭辞の追加(socal_count_cache)
	$table_name = $wpdb->prefix . 'sample_mst';

	//charsetを指定する
	if (!empty($wpdb->charset)) {
		$charset_collate = "DEFAULT CHARACTER SET {$wpdb->charset} ";
	}

	//照合順序を指定する(ある場合、通常デフォルトのutf8_general_ci)
	if (!empty($wpdb->collate)) {
		$charset_collate .= "COLLATE {$wpdb->collate}";
	}

	$sql = <<< EOL
CREATE TABLE {$table_name} (
id				INT NOT NULL AUTO_INCREMENT,
sample_name		VARCHAR(128),
create_date		DATETIME,
PRIMARY KEY(id)
) {$charset_collate};
EOL;

	//dbDeltaを実行する為に必要
	require_once(ABSPATH . 'wp-admin/includes/upgrade.php');
	dbDelta($sql);
}

コードを一つづつ追うとわかるのですが、まず、

global $wpdb;

で、wordpress内のデータベースにアクセスする為のクラスを読み込んでいます。
$wpdbはSQLの実行時にいたるところで使うので、この読み込み方はよく覚えておくといいと思います。

作成するテーブル名称は「wp_sample_mst」となります。
wp_というのはwordpressに関するテーブルの接頭辞(接頭句?)になり、よほどのことがない限り、
この「wp_」という名称に従ったほうがよいと思います。

その次に、charset

//charsetを指定する
if (!empty($wpdb->charset)) {
	$charset_collate = "DEFAULT CHARACTER SET {$wpdb->charset} ";
}

と照合順序を

//照合順序を指定する(ある場合、通常デフォルトのutf8_general_ci)
if (!empty($wpdb->collate)) {
	$charset_collate .= "COLLATE {$wpdb->collate}";
}

それぞれセットして、テーブル作成文を実行します。

実際にプラグインのインストールを試して、テーブルが作成されることを確認します。

最後に、少し長くなりますが、ソースコード全体を書いておきます。

<?php
/*
Plugin Name: SamplePlugin1
Plugin URI:  http://
Description: サンプルプラグイン。
Version:	 1.0
Author: 	 sample
Author URI:  http://
License:	 GPL2
License URI: https://www.gnu.org/licenses/gpl-2.0.html
*/

register_activation_hook(__FILE__, array('SamplePlugin1', 'myplugin_activate'));
register_deactivation_hook(__FILE__, array('SamplePlugin1', 'myplugin_unactivate'));
register_uninstall_hook(__FILE__, array('SamplePlugin1', 'myplugin_uninstall'));

class SamplePlugin1
{

	function __construct()
	{
		add_action('admin_menu', array($this, 'add_pages'));
	}

	function add_pages()
	{
		add_menu_page(
			'SamplePlugin1 Plugin Settings',
			'SamplePlugin1',
			'manage_options',
			'SamplePluginMenu',
			array($this, 'sample_plugin')
		);
	}

	function myplugin_activate()
	{
		//テーブル作成などなど
		self::create_tables_sample_mst();
		
	}

	function myplugin_unactivate()
	{
		//テーブルデータ消去などなど
		
		
	}
	 
	function myplugin_uninstall()
	{
		//テーブル削除などなど
		
		
	}

	function create_tables_sample_mst()
	{
		global $wpdb;

		$charset_collate = "";

		//接頭辞の追加(socal_count_cache)
		$table_name = $wpdb->prefix . 'sample_mst';

		//charsetを指定する
		if (!empty($wpdb->charset)) {
			$charset_collate = "DEFAULT CHARACTER SET {$wpdb->charset} ";
		}

		//照合順序を指定する(ある場合、通常デフォルトのutf8_general_ci)
		if (!empty($wpdb->collate)) {
			$charset_collate .= "COLLATE {$wpdb->collate}";
		}

		$sql = <<< EOL
CREATE TABLE {$table_name} (
id				INT NOT NULL AUTO_INCREMENT,
sample_name		VARCHAR(128),
create_date		DATETIME,
PRIMARY KEY(id)
) {$charset_collate};
EOL;

		//dbDeltaを実行する為に必要
		require_once(ABSPATH . 'wp-admin/includes/upgrade.php');
		dbDelta($sql);
	}

}

new SamplePlugin1;

プラグインを「有効化」するタイミングで、そのプラグインを使うにあたっての初期設定をプログラムすることができます。

例えば、プラグイン内で使用するデータベース内のテーブルを作成する等といったことを記述します。

また、プラグインを停止したタイミング。プラグインをアンインストールしたタイミングでそれぞれの動作をプログラムすることができます。

試しに、プラグインを有効化したタイミングの挙動を書いてみます。

register_activation_hook(__FILE__, array('SamplePlugin1', 'myplugin_activate'));

まず、プラグイン内のクラスの外に、上記の命令を記述します。

次にクラス内に以下のメソッドを追加します。

function myplugin_activate()
{
	//テーブル作成などなど
	
	
}

ここでは、具体的なテーブル作成の命令は書いていませんが、このメソッド内に初期化の命令をまとめて記載しておくといいと思います。

これと同様に、停止時、アンインストール時には、次のように書きます。

register_deactivation_hook(__FILE__, array('SamplePlugin1', 'myplugin_unactivate'));
register_uninstall_hook(__FILE__, array('SamplePlugin1', 'myplugin_uninstall'));

同様にメソッドもクラス内に書いて用意しておきます。

function myplugin_unactivate()
{
	//テーブルデータ消去などなど
	
	
}

function myplugin_uninstall()
{
	//テーブル削除などなど
	
	
}

以上になります。
出来上がったプログラムの全体は、以下のようになります。

<?php
/*
Plugin Name: SamplePlugin1
Plugin URI:  http://
Description: サンプルプラグイン。
Version:	 1.0
Author: 	 sample
Author URI:  http://
License:	 GPL2
License URI: https://www.gnu.org/licenses/gpl-2.0.html
*/

register_activation_hook(__FILE__, array('SamplePlugin1', 'myplugin_activate'));
register_deactivation_hook(__FILE__, array('SamplePlugin1', 'myplugin_unactivate'));
register_uninstall_hook(__FILE__, array('SamplePlugin1', 'myplugin_uninstall'));

class SamplePlugin1
{

	function __construct()
	{
		add_action('admin_menu', array($this, 'add_pages'));
	}

	function add_pages()
	{
		add_menu_page(
			'SamplePlugin1 Plugin Settings',
			'SamplePlugin1',
			'manage_options',
			'SamplePluginMenu',
			array($this, 'sample_plugin')
		);
	}

	function myplugin_activate()
	{
		//テーブル作成などなど
		 
		 
	}

	function myplugin_unactivate()
	{
		//テーブルデータ消去などなど
		 
		 
	}
	 
	function myplugin_uninstall()
	{
		//テーブル削除などなど
		 
		 
	}

}

new SamplePlugin1;

まだ、具体的な処理は入れていませんが、全体的な骨格が少しづつできてきました。

メニュー追加

前回作成したプラグインを、管理画面から有効にしてみます。

メニューのプラグインの中にアクセスすると、前回作ったプラグイン「SamplePlugin1」が見えるので、その下の有効化をクリックします。

20160624_001

ただ、この状態ではなにも変化は起きません。
プラグインの中になにもプログラムを書いていないので、変化しないのですね。

では、次にやることと言えば、プラグインを管理画面のメニューの表示させてみます。

<?php
/*
Plugin Name: SamplePlugin1
Plugin URI:  http://
Description: サンプルプラグイン。
Version:     1.0
Author:      sample
Author URI:  http://
License:     GPL2
License URI: https://www.gnu.org/licenses/gpl-2.0.html
*/

class SamplePlugin1
{

	function __construct()
	{
		add_action('admin_menu', array($this, 'add_pages'));
	}

	function add_pages()
	{
		add_menu_page(
			'SamplePlugin1 Plugin Settings',
			'SamplePlugin1',
			'manage_options',
			'SamplePluginMenu',
			array($this, 'sample_plugin')
		);
	}

}

new SamplePlugin1;

上記のコードをプログラムの中に記載します。

まず、__constructというコンストラクタ内に

add_action('admin_menu', array($this, 'add_pages'));

と書きます。
これは同じクラス内のメソッド「add_pages」を呼ぶ為のアクションとなります。
アクションについてはwordpressに備わっている考え方で、フィルターフック、アクションフックという考え方があり、それぞれ何らかのきっかけがあった時に実行する命令。という意味で覚えておくといいと思います。

次にコンストラクタに書いた命令で「add_pages」という記載がありますが、これは同じクラス内のメソッド「function add_pages()」を呼ぶという意味になります。

なので、このメソッドを新しく作り、内部にメニューに表示する命令を書きます。

		add_menu_page(
			'SamplePlugin1 Plugin Settings',
			'SamplePlugin1',
			'manage_options',
			'SamplePluginMenu',
			array($this, 'sample_plugin')
		);

ここまで書いたらファイルを保存して、wordpressの管理画面内にアクセスしてみます。
すると「SamplePlugin1」というメニューが表示されます。
クリックはできるのですが、この状態だと、まだなにも表示はされません。

20160624_002

add_actionと、add_menu_pageの関数はwordpressに実装済みの関数なので、以下のページを参考にするといいかと思います。

■add_action
https://wpdocs.osdn.jp/%E9%96%A2%E6%95%B0%E3%83%AA%E3%83%95%E3%82%A1%E3%83%AC%E3%83%B3%E3%82%B9/add_action

■add_menu_page
https://wpdocs.osdn.jp/%E9%96%A2%E6%95%B0%E3%83%AA%E3%83%95%E3%82%A1%E3%83%AC%E3%83%B3%E3%82%B9/add_menu_page

前回までで、プラグイン開発の準備を進めていました。
ここからは具体的にプラグインを形にしていこうと思います。

まず、なんでもいいのでサンプル用のphpファイルを作成し、
前回に書いた、wordpressのプラグインフォルダ内にサンプルphpを設置します。

/wordpressのドキュメントルート/wp-content/plugins/SamplePlugin1/SamplePlugin1.php

次にSamplePlugin1.phpの中に以下のようにコードを書きます。
ここで注意が必要なのは、プラグインファイルの文字コードはUTF-8にしておかないと、
後々文字化け等の現象が出てくることがあるので、注意します。

<?php
/*
Plugin Name: SamplePlugin1
Plugin URI:  http://
Description: サンプルプラグイン。
Version:     1.0
Author:      sample
Author URI:  http://
License:     GPL2
License URI: https://www.gnu.org/licenses/gpl-2.0.html
*/

class SamplePlugin1
{
	function __construct()
	{

	}

}

new SamplePlugin1; 

当然、このコードだけではプラグインとしての動作はなにもありません。
ですが、このファイルを設置するだけで、wordpressの管理画面の「プラグイン」の画面に
プラグイン名称が表示されます。

001

一旦、ここまで作ってみて、動作を確認し、次の段階にいきます。

早速プラグインを作っていきますが、まずはプラグインのファイルを
格納するディレクトリの確認からです。

pluginは、wordperssのドキュメントルート配下から、以下のようなパス配下に
ディレクトリを作ることで、認識されます。

/wordpressのドキュメントルート/wp-content/plugins/プラグイン名

注意が必要なのは、ここでいうプラグイン名というのは、世界中にある
プラグインの名前と重複しないこと。
重複しないプラグイン名(ローマ字)を考えて、ディレクトリを作ります。

その後は、「任意のプラグイン名.php」というphpファイルを作成し、
行頭からコメントを記載します。

<?php
Plugin Name: SamplePlugin1
Plugin URI: http://
Description: サンプルのプラグインです
Version: 1.0
Author: p
Author URI: http://
License: GPL2
License URI: https://www.gnu.org/licenses/gpl-2.0.html
*/

//ここからphpのプログラムを書き始める

こうすることで、wordpressの管理画面のプラグインメニューの中に
「SamplePlugin1」という名前のプラグインが表示されるようになります。

wordpressでplugin開発を始めます

前回の投稿から時間がたってしまいましたが、これからPHPの勉強を開始します。

PHPの勉強を兼ねて、wordpressのプラグイン開発メモを記述していきます。
まずは、プラグインの管理画面側の実装からします。

HashMapを使う

HashMapを使って次のようなプログラムを書きます。

package TestPackage;

import java.util.HashMap;
import java.util.Map;

public class LinkedListTest6 {

	/**
	 * @param args
	 */
	public static void main(String[] args) {

		Map<String, Integer> map = new HashMap<String, Integer>();

		//要素の追加
		map.put("test2", 100);
		map.put("qqq", 67);
		map.put("www", 34);
		map.put("eee", 81);

		System.out.println("map -> " + map);
                
                //ここに、コレクションの操作を記述する
	}

}

実行結果は次のようなります。

map -> {qqq=67, www=34, test2=100, eee=81}

「キー項目と値」のセットになっていることがわかります。
Hashmapはこのように、2つセットのものを、要素分、格納するコレクションと言えます。

次に、このコレクションを操作してみます。
「ここに、コレクションの操作を記述する」という箇所に次のようなプログラムを書いて見ます。

■キーと値を同時に取得する

		for (Map.Entry<String, Integer> entry : map.entrySet()){
			System.out.println(entry.getKey() + "=>" + entry.getValue());
		}

実行結果は次のようになります。

qqq=>67
www=>34
test2=>100
eee=>81

■キーを取得する

		for (String name : map.keySet()){
			System.out.println(name);
		}

実行結果は次のようになります。

qqq
www
test2
eee

■値を取得する

		for (Integer name : map.values()) {
			System.out.println(name);
		}

実行結果は次のようになります。

67
34
100
81

■キーと値を取得する

		for (String name : map.keySet()) {
			System.out.println(name + "->" + map.get(name));
		}

実行結果は次のようになります。

qqq->67
www->34
test2->100
eee->81

■あるキーをもとに値を取得する

		System.out.println(map.get("qqq"));

実行結果は次のようになります。

67

これは2つ目の要素の値を取得してきていることがわかります。

コレクションの使い方は上記のほかにもまだまだ使い方があるので、別途追求しようと思います。

HashSetを使う

HashSetを使ってみます。

HashSetは要素の集合に、目的の要素が含まれているかを調べるときに使います。

含まれているかどうかを調べる時に、要素の順番は関係ありません。

また、nullを追加することも可能です。

package TestPackage;

import java.util.HashSet;
import java.util.Set;

public class LinkedListTest5 {

	/**
	 * @param args
	 */
	public static void main(String[] args) {

		Set<String> set = new HashSet<String>();

		//要素の追加
		set.add("test2");
		set.add("qqq");
		set.add("www");
		set.add("eee");

		System.out.println("set -> " + set);

		if (set.contains("www")){
			System.out.println("要素に含まれている");
		} else {
			System.out.println("要素に含まれていない");
		}

	}

}

実行結果は次のようになります。

set -> [qqq, www, test2, eee]
要素に含まれている

LinkedListをキューとして使う

LinkedListをキューとして使ってみます。
プログラムは次のように記述します。

package TestPackage;

import java.util.LinkedList;
import java.util.Queue;

public class LinkedListTest2 {

	/**
	 * @param args
	 */
	public static void main(String[] args) {

		Queue<String> queue = new LinkedList<String>();

		//要素の追加
		queue.offer("test2");
		queue.offer("ttt");
		queue.offer("mmm");
		queue.offer("ppp");

		//要素の中身を出力する
		System.out.println(queue);

		//先頭要素を取り出す
		queue.poll();

		//要素の中身を出力する
		System.out.println(queue);

		//要素の先頭を「参照」して変数へ代入する
		String peekStr = queue.peek();

		System.out.println(peekStr);

		//要素の中身を出力する
		System.out.println(queue);
}

}

実行結果は次のようになります。

[test2, ttt, mmm, ppp]
[ttt, mmm, ppp]
ttt
[ttt, mmm, ppp]

注意する点は、データ構造がキューを意味している点と、pollメソッドを使って要素を取り出す時には先に挿入したものから取り出されることです。(末尾から操作するメソッドも用意されています)

また、「参照」を行っている箇所は値そのものを取り出さず、値を見ているだけの動きになっています。(一旦、変数に代入してprint文をわかりやすくしているだけです)

LinkedListを使用したコレクションを書いて見ます。
LinkedListは要素の挿入と削除を高速に行います。

一見、ArrayListに似た記述になりますが、先頭に「rrr」を挿入する際のメソッドや宣言の仕方などが微妙に違います(詳しい使い方は別途調べて書いていくことにします)。

package TestPackage;

import java.util.LinkedList;

public class LinkedListTest {

	/**
	 * @param args
	 */
	public static void main(String[] args) {

		LinkedList<String> list = new LinkedList<String>();

		//要素の追加
		list.add("test2");
		list.add("ttt");
		list.add("mmm");
		list.add("ppp");

		//要素の中身を出力する
		System.out.println(list);

		//先頭に「rrr」を挿入
		list.addFirst("rrr");

		//要素の中身を出力する
		System.out.println(list);

	}

}


上記を実行した結果は次のように出力されます。

[test2, ttt, mmm, ppp]
[rrr, test2, ttt, mmm, ppp]

ArrayListを使用したコレクションを書いて見ます。

package TestPackage;

import java.util.ArrayList;
import java.util.List;

public class ArrayListTestX {

	/**
	 * @param args
	 */
	public static void main(String[] args) {

		List<String> list = new ArrayList<String>();

		//要素の追加
		list.add("test1");
		list.add("aaa");
		list.add("bbb");
		list.add("ccc");

		//要素の中身を出力する
		System.out.println("最初->" + list.get(0));
		System.out.println("最後->" + list.get(list.size() - 1));

		//すべての要素を出力する
		for (int i = 0; i < list.size(); i++) {
			System.out.println(list.get(i));
		}

		//bbbが格納されている要素の添え字を調べる
		//(要素は0から始まるので、出力結果は2と表示される)
		System.out.println(list.indexOf("bbb"));

	}

}

上記を実行した結果は次のように出力されます。

最初->test1
最後->ccc
test1
aaa
bbb
ccc
2

javaの引数パラメータに渡したパス(指定ディレクトリ)の中にあるファイルを一覧表示するプログラムを書きます。

実行した結果は、引数で指定したディレクトリ内のディレクトリとファイルが混在された形でコンソールに表示されます。

package TestPackage;

import java.io.File;

public class DirShow {

	/**
	 * @param args
	 */
	public static void main(String[] args) {

		if (args.length != 1){
			System.out.println("args not");
			System.exit(0);
		}

		String dirname = args[0];

		File dir = new File(dirname);

		String[] dirlist = dir.list();

		for (int i=0; i < dir.length(); i++)  {
			System.out.println(dirlist[i]);
		}

	}

}

ファイルの削除を行う

ファイルの削除を行ってみます。
ソースを下記のように記述し、実行をしてみます。

このとき、あらかじめD:\直下にa.txtというテキストファイルが存在するものとします。

package TestPackage;

import java.io.File;

public class DeleteFile {

	/**
	 * @param args
	 */
	public static void main(String[] args) {

		if (args.length != 1) {
			System.exit(0);
		}

		String filename = args[0];

		File file = new File(filename);

		if (file.delete()) {
			System.out.println("file delete ok");
		} else {
			System.out.println("file delete ok");
			System.exit(0);
		}

	}
}

実行した結果、a.txtファイルは消えたことを確認しました。

ファイルへの書き込みを行う

javaからファイルに書き込みを行うプログラムを書いてみました。

実行する時はeclipseの実行→実行構成で引数を2つ指定する必要があるので注意です。
下記画像は引数を設定した例をキャプチャしたものです(ご参考までに)
001

package TestPackage;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;

public class WriteFile {

	/**
	 * @param args
	 */
	public static void main(String[] args) {

		/*
		if (args.length != 1) {
			System.out.println("len 1 !");
			System.exit(0);
		} else {
			System.out.println("len 2 !");
		}
		*/

		String inputData = args[0];
		String filename = args[1];

		try{
			//BufferedReader reader = new BufferedReader(new FileReader(filename));
			//BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
			BufferedReader reader = new BufferedReader(new FileReader(inputData));

			PrintWriter writer = new PrintWriter(new BufferedWriter(new FileWriter(filename)));

			String line;

			//ファイルの中を開いて1行ずつに出力用変数に代入します
			while ((line = reader.readLine()) != null){
				writer.println(line);
			}

			//reader削除します
			reader.close();
			writer.close();

		} catch(FileNotFoundException e){
			System.out.println("filename -> なし");
		} catch(IOException e){
			System.out.println(e);
		}

	}
}

ファイルの読み込みを書いてみます。

package TestPackage;

import java.io.BufferedReader;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;

public class ShowFile {

	/**
	 * @param args
	 */
	public static void main(String[] args) {

		if (args.length != 1) {
			System.out.println("len 1 !");
			System.exit(0);
		} else {
			System.out.println("len 2 !");
		}

		String filename = args[0];

		try{
			BufferedReader reader = new BufferedReader(new FileReader(filename));
			String line;
			//ファイルの中を開いて1行ずつコンソールに出力する
			while ((line = reader.readLine()) != null){
				System.out.println(line);
			}
		} catch(FileNotFoundException e){
			System.out.println("filename -> なし");
		} catch(IOException e){
			System.out.println(e);
		}

	}

}

上記のソースを書いてプログラムを実行するのですが、ひとつ気をつけなければならないことは

String filename = args[0];

の部分で、プログラム実行時の引数に値が渡ってくる。という概念があります。

eclipseの場合は、メニューの「実行」→「実行構成」から別ウィンドウを開き、その中でJAVAの目的のソースコード名を選択して、「引数」タブ内に引数情報を入力することでプログラムの動作を確認しました。

下記のようなソースを書きます。

package ThreadPackage;

public class JoinTest extends Thread {

	/**
	 * @param args
	 */
	public static void main(String[] args) {

		JoinTest th = new JoinTest();

		System.out.println("main start");

		th.start();

		System.out.println("main wait");

		try{
			th.join();
		} catch(InterruptedException e){
			System.out.println(e);
		}

		System.out.println("main end");
	}

	public void run(){
		System.out.println("run start");

		try {
			Thread.sleep(3000);
		}catch(InterruptedException e){
			System.out.println(e);
		}
		System.out.println("run end");
	}

}

実行すると次のようなメッセージがコンソールに表示されます

main start
main wait
run start
run end
main end

いったい何が起きているのかというと、

JoinTest th = new JoinTest();

でいったんスタートしたthというスレッドのインスタンスが実行している間に、

th.join();

というメソッドが起動したタイミングで

public void run(){

のメソッドで定義した別スレッドが起動され、その終了をthというスレッドが待っている状態になっています。

他のスレッドの終了を待ってからメインだったスレッドが再開する。という動作ができます。

注意として、joinというメソッドの使い方には3つの方法があります。

//タイムアウトなし、スレッドの終了を待つ
void join() throws InterruptedException

//タイムアウトあり、スレッドの終了を待つ(ミリ秒)
void join(long msec) throws InterruptedException

//タイムアウトあり、スレッドの終了を待つ(ミリ秒+ナノ秒)
void join(long msec, int nsec) throws InterruptedException

スレッド動作を指定秒ストップさせることができます。

次のようなプログラムを作成して実行してみます。

package ThreadPackage;

public class Ts {

	/**
	 * @param args
	 */
	public static void main(String[] args) {

		//スレッドの動作がsleep秒ごとにどんどん遅くなっていく
		for(int i=0; i<10; i++){
			int tm = i  * 1000;
			System.out.println("Start tm -> " + tm);
			try {
				Thread.sleep(tm);
			}catch(InterruptedException e){

			}
		}

	}

}

実行結果は以下のようになり、下に行けば行くほど表示する時間間隔が長くなっていきます。

Start tm -> 0
Start tm -> 1000
Start tm -> 2000
Start tm -> 3000
Start tm -> 4000
Start tm -> 5000
Start tm -> 6000
Start tm -> 7000
Start tm -> 8000
Start tm -> 9000

単純にとあるループ処理を10回繰り返すだけですが、ループ中に

Thread.sleep(tm);

と記載し、tm秒ごとに処理を一時停止しています。

スレッドを止める場合

既に実行しているスレッドを止める場合には以下のよう書きます。

public void stopRunning() {
    running = false;
}

使い方については上記のメソッドを起動するタイミング(操作)を既存のスレッドを利用しているプログラムに適用するように使います。
(実際の例は別途追記)

ひとつ前の記事と違う方法でスレッドを書いてみます。

package ThreadPackage;

public class ThreadTest2 implements Runnable {

	/**
	 * @param args
	 */
	public static void main(String[] args) {

		ThreadTest2 tt2 = new ThreadTest2();
		Thread th = new Thread(tt2);

		th.start();
		for (int i = 0; i < 10; i++) {
			System.out.println("main : i -> " + i);
		}

	}

	public void run() {
		for (int i = 0; i < 10; i++) {
			System.out.println("run : i -> " + i);
		}
	}

}

実行した結果は次のようになります。

run : i -> 0
main : i -> 0
run : i -> 1
main : i -> 1
run : i -> 2
run : i -> 3
main : i -> 2
run : i -> 4
main : i -> 3
run : i -> 5
run : i -> 6
main : i -> 4
run : i -> 7
main : i -> 5
run : i -> 8
main : i -> 6
run : i -> 9
main : i -> 7
main : i -> 8
main : i -> 9

前回の記事と同じ、ログの出力はランダムな順になります。
呼び出し方が違うことに注意する以外は、考え方とログの出方は一緒です。

スレッドを書いてみます

スレッド処理を書いて動かしてみます

package ThreadPackage;

public class ThreadTest1 extends Thread {

	/**
	 * @param args
	 */
	public static void main(String[] args) {

		ThreadTest1 tt = new ThreadTest1();
		tt.start();

		for(int i=0; i<10; i++){
			System.out.println("main:i->" + i);
		}
	}

	public void run() {
		for (int i=0; i<10; i++) {
			System.out.println("sub:i->" + i );
		}
	}

}

実行した結果は次のようになります。

1回目
sub:i->0
sub:i->1
sub:i->2
sub:i->3
sub:i->4
sub:i->5
main:i->0
sub:i->6
sub:i->7
sub:i->8
sub:i->9
main:i->1
main:i->2
main:i->3
main:i->4
main:i->5
main:i->6
main:i->7
main:i->8
main:i->9
2回目
main:i->0
main:i->1
sub:i->0
sub:i->1
sub:i->2
sub:i->3
main:i->2
main:i->3
sub:i->4
main:i->4
main:i->5
sub:i->5
sub:i->6
sub:i->7
sub:i->8
sub:i->9
main:i->6
main:i->7
main:i->8
main:i->9
3回目
main:i->0
main:i->1
sub:i->0
sub:i->1
sub:i->2
main:i->2
main:i->3
sub:i->3
sub:i->4
sub:i->5
sub:i->6
sub:i->7
sub:i->8
sub:i->9
main:i->4
main:i->5
main:i->6
main:i->7
main:i->8
main:i->9

3回実行した結果を載せたのは、スレッドのスタートタイミングが実行時でそれぞれランダムなので掲載した。

1つのスレッドのみのプログラムの場合は、毎回同じタイミングで同じログが出力されるだけだが、スレッドを利用したプログラムでは、1つ目のプログラムが走る裏でもうひとつのプログラムが走るので、処理が同時に平行してタイミングがまちまちにログが出力されていることがわかります。

インターフェイスを拡張することをができます

interface FastRunnable extends Runnable {
	void fastrun();
}

FastRunnableはRunnableのサブインターフェイス
Runnable はFastRunnable のスーパーインターフェイス
といいいます。

サブインターフェイスはスーパーインターフェイスのメソッドを継承します。

実際にインターフェイスを書いてみます

//Lockableインターフェイスの宣言
interface Lockable {
	//↓これは抽象メソッド
	//abstractキーワードがない場合、自動的に抽象メソッドになります
	boolean lock();
	boolean unlock();
}

インターフェイスの宣言は↑のように行います。

上記のインターフェイスを実装するには、implementsキーワードを使って以下のように書きます。

/*
インターフェイスが持つフィールドは必ず定数
インターフェイスが持つメソッドは必ず抽象メソッド
インターフェイスはインスタンスを作ることはできない
*/

//インターフェイスの実装
class Kinko implements Lockable {

	public boolean lock() {
		return true;
	}

	public boolean unlock() {
		return false;

	}

}

インターフェイスはインスタンスを作ることはできません。

//これはできない
Lockable obj = new Lockable();

インターフェイス型の変数を使うことはできます。

Lockable obj = new Kinko();

obj.lock();
という形で呼び出すことができます。

インターフェイスについて

インターフェイスは、公開すべき必要な操作をまとめたクラスの仕様(取り決め)です。
実際に行う処理の内容は、インターフェイスには記述しないです。
インターフェイスは抽象クラスと同様にインスタンス化はできません。
宣言方法は次のようにします。

interface xxxxx {}

インターフェイスは次の特徴があります。
・インターフェイスで宣言できるメソッドは抽象メソッドのみ
・インターフェイスではpublic staticな定数を宣言できる。定数なので初期化しておく。
・インスタンス化はできず、利用する場合は実装クラスを作成し、実装クラス側では抽象メソッドをオーバーライドして使用する
・実装クラスを定義するにはimplementsキーワードを使用する
・インターフェイスを元にサブインターフェイスを作成する場合はextendsキーワードを使用する

catch文を連続して書く方法

下記のように、catch文を連続して書く方法もあります。

try {

} catch (例外クラス名1 変数名) {

} catch (例外クラス名2 変数名) {

} catch (例外クラス名3 変数名) {

}

ただし、注意しないといけないのは、クラスに継承関係がある場合は、必ずサブクラス側から順に書かないとコンパイルエラーになることです。

ダメな例

try {

} catch (例外クラス名1(親クラス) 変数名) {

} catch (例外クラス名2(子クラス) 変数名) {

} catch (例外クラス名3(子クラス) 変数名) {

}

良い例

try {

} catch (例外クラス名1(子クラス) 変数名) {

} catch (例外クラス名2(子クラス) 変数名) {

} catch (例外クラス名3(親クラス) 変数名) {

}

finallyの使用

プログラム内のエラーを受け、例外処理を行った後、最後に必ず実行したい処理を書く領域(ブロック)

構文は以下のように書きます

try {

} catch (例外クラス名 変数名) {

} finally {
    //ここに必ず実行したい処理を書きます
}

■注意点
finallyの中にreturn文を書くことはできないです。

スタックトレースの使用

例外処理を行っている「e」の変数に対し、printStackTraceを使うと、コールスタックが表示されます。
e.printStackTrace()と書くと、どのメソッドから例外が投げられたのかを表示することができます。

package TestPackage;

public class ExecptionTest1 {

	public static void main(String[] args) {

		int[] TestArray = new int[3];

		System.out.println("Void Main start !");

		try {
			System.out.println("Try start !");
			TestFunc(TestArray, 100, 0);
			System.out.println("Try end !");
			//TestArray[50] = 0;
		} catch (Exception e) {
			//スタックトレースを使うと、コールスタックが表示され、
			//どのメソッドの中から例外が投げられているのかがわかります
			e.printStackTrace();
			System.out.println("例外 -> " + e);
		}

		System.out.println("Void Main end !");
	}

	static void TestFunc(int[] arr, int index, int value) {
		System.out.println("TestFunc start !");
		arr[index] = value;
		System.out.println("TestFunc end !");
	}

}

出力結果は以下のようになります

Void Main start !
Try start !
java.lang.ArrayIndexOutOfBoundsException: 100
	at TestPackage.ExecptionTest1.TestFunc(ExecptionTest1.java:28)
	at TestPackage.ExecptionTest1.main(ExecptionTest1.java:13)
TestFunc start !
例外 -> java.lang.ArrayIndexOutOfBoundsException: 100
Void Main end !

こうすることで、(ExecptionTest1.java:28)と(ExecptionTest1.java:13)という情報がわかるので、そのソースファイルのエラー原因(上記の場合は配列のインデックスを超えて代入しようとした際のエラー)を注意深く追うことができます。

例外の種類について

JAVAの例外クラスは、Throwableというクラスがあります。

throw文で投げることができ、catch節で受け止めることができるクラスはすべてThrowableクラスが元になっています。

Throwableは、大きく分けてErrorとExceptionの2つに分類でできます。

  • Error

もはや動作を継続するのは期待できないときに投げられるエラー

  • Exception

正しく例外処理を行って、動作が継続することを期待するときのエラー
————————————————————————
さらに「Exception」は2種類に分けられます

  • Exception → RuntimeException

実行中に起こり、コンパイラによって前もってチェックされない例外(unchecked例外とも言う)

  • Exception → RuntimeException以外のエラー

コンパイラによって前もってチェックされる例外(checked例外とも言う)

————————————————————————
checked例外とunchecked例外の2種類に分かれます。

それぞれ取り扱う例外の種類が違うので、以下にまとめます。

  • unchecked例外

実行中のプログラムが原因で発生する例外(任意記述)
メモリ不足やプログラムの例外処理では復旧できない例外

  • checked例外

必ず任意の例外処理を記述しなければならないです。(必須記述)
記述しない場合はコンパイルエラーになります。
DBが原因だったり、java実行環境以外の環境が原因の例外

checked例外の場合、次の2種類の方法で必ず例外処理を書かなければなりません

  • ①メソッド内のcatch節でその例外をキャッチします
void Test() {
	try {
		throw new IOException();
	} catch (IOException e) {
		
	}
}
  • ②メソッド内のthrows節でその例外を投げることを宣言します
void Test() throws IOException {
	throw new IOException();
}

例外を自分で投げる場合

例外処理は自分自身で起動することができます。
次のようにコーディングします。
予約語throwの後に投げたい例外のインスタンスを書きます。

throw new IOException();

コールスタックについて

例外時のエラー処理を行った場合、通常のメソッドのreturn時の挙動とは異なります。

通常のreturn時には、メソッドのひとつ前に戻る。動作を行いますが、例外時のエラーはcatchに飛びます。catchに飛ぶ場所は、コーディングの仕方によってケースバイケースなので、ひとつ前のcatchに飛ぶこともあり、メソッドが何重にも深いところから呼ばれている場合には、おおもとのcatchまで飛んでいくケースもあるので注意が必要です。

この何重にもcatchが書かれている場合のことをコールスタックと呼びます。

例外について

javaの例外は、プログラムの誤りを通知し、適切なエラー処理を行う為の機能になります。

例外をうまく使うことで、エラーが起こった時にうまく処理する仕組みにすることができます。

例外の例

ArrayIndexOutOfBoundsException 配列の長さを超えている
FileNotFoundException ファイルが見つからなかった
IllegalArgumentException 引数が異常な値だった
OutOfMemoryError メモリが足りなくなった

等のエラーを拾うことができ、エラー内容に応じた処理にするとキレイなコードになります。

実際にテストコードを書いて、例外をおこしてみます。
下記のコードを書いてコンパイルすると、コンパイルエラーがおきます。

public class TestException {

    public static void main(String[] args) {
        
        int[] TestArray = new int[3];
        
        TestArray[50] = 0;
        
    }

}

上のコードのエラーを、例外をキャッチしてエラー処理を行うようにしてみます。

public class TestException {

    public static void main(String[] args) {
        
        int[] TestArray = new int[3];
        
        try {
            TestArray[50] = 0;
        } catch(Exception e) {
            System.out.println("例外 -> " + e);
        }
        
    }

}

これを実行すると、次のような結果が出力されます。

例外 -> java.lang.ArrayIndexOutOfBoundsException: 50

さらに、public static void mainの中でメソッドを呼び、そのメソッドの中で例外を発生させる。

package TestPackage;

public class ExecptionTest1 {

	public static void main(String[] args) {

		int[] TestArray = new int[3];

		System.out.println("Void Main start !");

		try {
			System.out.println("Try start !");
			TestFunc(TestArray, 100, 0);
			System.out.println("Try end !");
			//TestArray[50] = 0;
		} catch (Exception e) {
			System.out.println("例外 -> " + e);
		}

		System.out.println("Void Main end !");
	}

	static void TestFunc(int[] arr, int index, int value) {
		System.out.println("TestFunc start !");
		arr[index] = value;
		System.out.println("TestFunc end !");
	}

}

上記のコードを実行すると、次のような出力になる。

Void Main start !
Try start !
TestFunc start !
例外 -> java.lang.ArrayIndexOutOfBoundsException: 100
Void Main end !

Tryの中でTestFuncメソッドが呼ばれ、そのメソッド内で例外が発生したら、
もともとのTryにあるcatchが働き、例外処理がされる。

抽象クラスとは

抽象クラスについて勉強します。

クラスの前にまずは、抽象メソッドについてです。
抽象メソッドとは、メソッドの名前と引数の型だけが定まっているものを言います。
別名でabstractメソッドとも言います。

abstract class TestClass{
    public abstract void TEST();
}

TESTというメソッドを宣言していても、中身はありません。(抽象メソッド)

このように、中身がないメソッドを含むクラスのことを抽象クラスといいます。

抽象クラスに書いた抽象メソッドは、中身がないので、具体的な挙動はサブクラスのほうで書く必要がある。
サブクラスで動作を書くことを前提として抽象クラスを設計するケースが多く、プログラムをする時にはその意図を汲み取ってコーディングをする。

finalクラスの拡張について

修飾子のfinalをクラスにつけて宣言すると、そのクラスは拡張が禁止されます。

拡張が禁止されたクラスのことをfinalクラスと言います。

finalクラスの例

final class FClass {

}

例えば、FClassを継承したクラスを作ろうとすると

class FSUBClass extends FClass {

}

とするとコンパイルエラーになります。

クラスライブラリとパッケージ

クラスを数多くあつめたものをクラスライブラリと呼びます。

また、関連のあるクラスを1つのグループにまとめたものをパッケージと呼びます。
利用する場合には「import」宣言をして呼び出します。
パッケージを作る場合は「package」宣言を行って作ります。

この辺りは実際に作ってみないとまだまだ実感がわかない概念です。
今後の為に言葉だけは覚えておこうと思います。

アクセスメソッドについて

クラス内のフィールドの修飾子についてのメモ。

一般的にはクラスを設計する際には、クラス内のフィールドについては、privateに設定しておくのが良いとされています。

フィールド自体はprivateで、その値を参照したり、代入したりするのは、その専用のメソッドを書くのが設計上よいです。

そこで、フィールドの内容を取得するメソッドのことをアクセスメソッドまたはアクセサと呼びます。

フィールドの値をprivateにしておくことによって、他のクラスからの直接的な参照を防ぐ考えを、情報隠蔽という。
クラスの設計をする際には、この考え方をベースにしたほうがよさそうです。

少し時間が空いてしまいました。

久しぶりのブログになりますが、今日はスーパークラスとサブクラスの関係について、もうすこし詳しく勉強します。

クラスの、スーパークラスのフィールドとメソッドを継承するという動作について、ひとつ掘り進んで考えてみます。

一口に継承といっても、スーパークラス側でprivateの修飾子で宣言されたメソッドについては継承することができません。

finalメソッドについて

スーパークラスをもとにしたサブクラスでは、スーパークラス側にfinalをつけられたメソッドを呼び出すことはできますが、オーバーライドすることはできません。

finalをつけるメソッドは、スーパークラスにて、システムの根幹に関わるメソッドに対してつけます。
書き換えてしまってシステムが誤動作させたくないメソッドについて使用します。

多様性について

多様性について勉強します。
正直、なにがなんだかよくわかりませんが、オブジェクト指向ではよく登場するようです。

言葉の意味では、以下のような要約になります。

オブジェクト指向での多様性とは、サブクラスのインスタンスを、スーパークラスのインスタンスのように使うことといいます。

例えばこれまで書いてきた以下のようなテストコードがあるとすると、サブクラスのインスタンスを作った後に、そのインスタンスをもとに、スーパークラスの型の変数に代入することができます。

TestClassChild tcc = new TestClassChild();
TestClassParent tcp = tcc;

上記のtcpから、スーパークラスのフィールドやメソッドが呼び出すことができます。(tccも通常どおり使うことができる)
このような使い方を多様性(ポリモルフィズム)と呼ぶようです。

このあたりは実践で使ってみないとピンとこないかもしれません。

public class SuperClassTest3{

    public static void main(String[] args) {
        
        //オブジェクトのインスンタンスを生成する
        TestClassChild tcc = new TestClassChild();
       	
       	System.out.println(tcc.TestA);
       	System.out.println(tcc.TestB);
       	
       	
       	//tcc.PrintTextC();
       	//tcc.PrintTextP();

    }
}

class TestClassChild extends TestClassParent
{
    TestClassChild() {
    	//引数なしコンストラクタ用
    	
    	//スーパークラスの引数なしコンストラクタを呼ぶ
    	super();
    	
    }

    TestClassChild(int TestAA, int TestBB) {
    	
    	//スーパークラスの引数つきコンストラクタを呼ぶ
    	super(TestAA, TestBB);

    }

    void PrintTextC() {
    	System.out.print("PrintTextC !! \n");
    }
    
    void PrintTextP() {
    	System.out.print("PrintTextP オーバーライドテスト !! \n");
    }

}

class TestClassParent
{
	int TestA = 10;
	int TestB = 20;
	
    TestClassParent(int a, int b) {
        //やっていることは「setTest」メソッドを同じ
        TestA = a;
        TestB = b;
        
        System.out.print("引数つきコンストラクタ !! \n");
        
    }
	
    TestClassParent() {
    	//引数なしコンストラクタ用

        System.out.print("コンストラクタ !! \n");
    	
    }
    
    void PrintTextP() {
    	System.out.print("PrintTextP !! \n");
    }
    
    void PrintParentTest() {
    	System.out.print("PrintParentTest !! \n");
    }
    
    

}













@Overrideという記述について

JKD1.5以降では、オーバーライドされているメソッドかどうかという区別をする為に

@Override

という記述をメソッドの上に書く様式が出てきました。
コンパイル時にこのメソッドは親クラスのメソッドをオーバーライドしているものかどうか、をチェックする動作になり、オーバーライド漏れを防ぐ場面で有効な手段となります。

(2015.05.09追記)
この書き方はアノテーションといい、Override以外にも使用方法は多岐にわたる

シグニチャについて

親クラス内のメソッドをオーバーライドする条件として、以下の点を考えます。

・メソッド名が同じか
・引数列の型が同じか

上記の条件がそろった時にはじめて、子クラス内のメソッドでオーバーライドが成立します。
この条件(メソッド名、引数列の型)のことをメソッドのシグニチャとよび、signatureと書きます。

HAS-A関係

HAS-A関係について勉強します。

オブジェクト指向では、おもに包含と呼ぶこともあるようです。
正直よくわかりませんので、調べてみました。

HAS-A関係を簡単に表すと

犬は頭、胴体、を含んでいる。

胴体は首、シッポ、ヘソ、足を含んでいる

足は、指、肉球(?)を含んでいる

という関係になり、犬というものを分割して捕らえていく考え方になります。

オブジェクト指向に、このHAS-A関係、の考え方がどのように関係してくるのか、じっくりと勉強したいと思います。

道は長い

IS-A関係

オブジェクト指向では、「AはBの一種である」ということがいえる関係のことをIS-A関係という。

例えば、サブクラスはスーパークラスの一種である。というように、次のような形で表します。

人間は哺乳類の一種である。

哺乳類は動物の一種である。

鳥は鳥類の一種である。

鳥類は動物の一種である。

というような例をIS-A関係と考えます。

前回の例では、継承したスーパークラスを何気に使っていましたが、スーパークラス側で初期化(コンストラクタ)が必要な設計になっている場合、どのように実装したらいいのかが気になります。

javaの言語仕様としては、コンストラクタの呼び出しは自動的に呼ばれる仕様になっているようです。

このときに呼ばれるのは「引数なし」コンストラクタとなるようです。

この自動的に呼び込まれるコンストラクタを自分の明示により呼び出す仕組みが「super()」という呼び出し方です。

以下に例を書きます。

public class SuperClassTest3{

    public static void main(String[] args) {
        
        //オブジェクトのインスンタンスを生成する
        TestClassChild tcc = new TestClassChild();
       	
       	System.out.println(tcc.TestA);
       	System.out.println(tcc.TestB);
       	
       	
       	//tcc.PrintTextC();
       	//tcc.PrintTextP();

    }
}

class TestClassChild extends TestClassParent
{
    TestClassChild() {
    	//引数なしコンストラクタ用
    	
    	//スーパークラスの引数なしコンストラクタを呼ぶ
    	super();
    	
    }

    TestClassChild(int TestAA, int TestBB) {
    	
    	//スーパークラスの引数つきコンストラクタを呼ぶ
    	super(TestAA, TestBB);

    }

    void PrintTextC() {
    	System.out.print("PrintTextC !! \n");
    }
    
    void PrintTextP() {
    	System.out.print("PrintTextP オーバーライドテスト !! \n");
    }

}

class TestClassParent
{
	int TestA = 10;
	int TestB = 20;
	
    TestClassParent(int a, int b) {
        //やっていることは「setTest」メソッドを同じ
        TestA = a;
        TestB = b;
        
        System.out.print("引数つきコンストラクタ !! \n");
        
    }
	
    TestClassParent() {
    	//引数なしコンストラクタ用

        System.out.print("コンストラクタ !! \n");
    	
    }
    
    void PrintTextP() {
    	System.out.print("PrintTextP !! \n");
    }
}

実行した結果は以下のようになります。

コンストラクタ !!
10
20

結果として「コンストラクタ !! 」と表示されているのは

TestClassChild tcc = new TestClassChild();

でTestClassChildのインスタンスが生成された際に、子クラス(TestClassChild)の引数なしコンストラクタ「TestClassChild()」が反応し

「super();」が呼ばれた結果

親クラスの引数なしコンストラクタ(TestClassParent())が呼ばれて

「System.out.print(“コンストラクタ !! \n”);」が実行されたから

になります。

少しややこしいけど、書きなれて体得していくしかないかと思います。

フィールドの継承

先ほど書いた実験コードに、フィールドが継承されるかどうかの検証をしてみます。

以下のようなコードを書いてみました。

public class SuperClassTest3{

    public static void main(String[] args) {
        
        //オブジェクトのインスンタンスを生成する
        TestClassChild tcc = new TestClassChild();
       	
       	System.out.println(tcc.TestA);
       	System.out.println(tcc.TestB);
       	
       	
       	//tcc.PrintTextC();
       	//tcc.PrintTextP();

    }
}

class TestClassChild extends TestClassParent
{
    TestClassChild() {
    	//引数なしコンストラクタ用
    	
    }

    void PrintTextC() {
    	System.out.print("PrintTextC !! \n");
    }
    
    void PrintTextP() {
    	System.out.print("PrintTextP オーバーライドテスト !! \n");
    }

}

class TestClassParent
{
	int TestA = 10;
	int TestB = 20;
	
    TestClassParent() {
    	//引数なしコンストラクタ用
    	
    }
    
    void PrintTextP() {
    	System.out.print("PrintTextP !! \n");
    }
    
    

}

結果は下記のようになり、親クラスのTestA、TestBというフィールドの値を参照することができています。

10
20

■注意■
フィールドとメソッドは継承元(親クラス)のものが参照できますが、コンストラクタは継承されず、使うことができません。

先ほど作成したテストコードを改造して、親クラスをextendsした子クラス内で親クラスと同名のメソッドを記述してオーバーライドしてみます。

書いたコードは以下のとおり

public class SuperClassTest2{

    public static void main(String[] args) {
        
        //オブジェクトのインスンタンスを生成する
        TestClassChild tcc = new TestClassChild();
       	
       	
       	tcc.PrintTextC();
       	tcc.PrintTextP();
        

    }
}

class TestClassChild extends TestClassParent
{
    TestClassChild() {
    	//引数なしコンストラクタ用
    	
    }

    void PrintTextC() {
    	System.out.print("PrintTextC !! \n");
    }
    
    void PrintTextP() {
    	System.out.print("PrintTextP オーバーライドテスト !! \n");
    }

}

class TestClassParent
{
    TestClassParent() {
    	//引数なしコンストラクタ用
    	
    }
    
    void PrintTextP() {
    	System.out.print("PrintTextP !! \n");
    }
    
    

}

子クラス(TestClassChild)内に「PrintTextP」というメソッドを書いて、親クラスのメソッドをオーバーライドしています。
これを実行すると以下のような結果になります。

PrintTextC !!
PrintTextP オーバーライドテスト !!

もともと親クラスにあったメソッドが子クラス内に書いたメソッドに置き換えられて実行されている。

extendsを使ってみる

スーパークラスから拡張したクラスを作ってみます。

下記のようなテストコードを書いて実行してみます。

public class SuperClassTest{

    public static void main(String[] args) {
        
        //オブジェクトのインスンタンスを生成する
        TestClassChild tcc = new TestClassChild();
       	
       	
       	tcc.PrintTextC();
       	tcc.PrintTextP();
        

    }
}

class TestClassChild extends TestClassParent
{
    TestClassChild() {
    	//引数なしコンストラクタ用
    	
    }

    void PrintTextC() {
    	System.out.print("PrintTextC !! \n");
    }
    
    void PrintTextP() {
    	System.out.print("PrintTextP !! \n");
    }

}

class TestClassParent
{
    TestClassParent() {
    	//引数なしコンストラクタ用
    	
    }
    
    void PrintTextP() {
    	System.out.print("PrintTextP !! \n");
    }
}

メインの処理の「SuperClassTest」のクラスの中から「TestClassChild」のクラスのインスタンスを生成して、そのTestClassChild内のメソッドを呼びました。

結果は

PrintTextC !!

という表示がされ、TestClassChild内のメソッドを呼べていることがわかります。

次にに、TestClassChildクラスが継承している「TestClassParent」クラスのメソッドも、同じTestClassChildインスタンス(ここではtccという名前)から呼んでみました。

すると結果は

PrintTextP !!

と表示され、継承元の「PrintTextP」メソッドが正しく動作していることがわかります。

今回の例はシンプルな継承の例ですが、一つの親クラスから、多数の子クラスを派生させることも可能なので、後ほどつめて実験してみようと思います。

スーパークラスについて

スーパークラスについて勉強します。

続きは後日、、

2014.10.09追記
スーパークラスという前に、まずは継承という概念について。
javaやc++等のオブジェクト指向言語で、作成したクラスをもとに、その機能を引き継ぎつつ、違うクラスを作成することができます。

この概念を継承といいます。

元のクラスを継承してできた新しいクラスのことを、サブクラスと呼び、元のクラスのことをスーパークラスといいます。

スーパークラスは原則として1つのみです。
スーパークラスは1個のみですが、サブクラスは1個のみという制約はなく、いくつでも作り出すことができます。

サブクラスは多段に拡張していくことができます。

javaはクラスが階層的に積み重なるように作ることができ、この構造をクラス階層という。

public、protected、privateは、変数やクラスを、どの範囲から参照可能かを決める修飾子です。(他にも色々ありますが、もっとも良く使う修飾子です)

public     自ファイルおよび他ファイル、全てのクラスから参照可能
protected  他ファイルの他クラス以外、全てのクラスから参照可能
private    自ファイルの自クラスのみ参照可能
指定なし    自ファイル内の自クラス、サブクラス、他クラスから参照可能

ちょっとわかりづらいですが、上記のような関係性があり、それらの動きをよく考えつつプログラムを組みます。

修飾子について

クラス内のフィールドやメソッドに対して、修飾子をつけて区別します。

■final
変更不可能な値やクラスを指し示します
主に、「定数」のような使い方をします。
また、finalで指定した変数には、プログラムの途中で値を代入することはできません。

■abstract
抽象クラスや抽象メソッドであることを示します
メソッド本体がないメソッドのことを言います。
メソッドにはつけられるけど、フィールドにはつけられない。

■static
クラスフィールドやクラスメソッドであることを示します

■synchronized
synchronizedメソッド

■native
JAVA言語以外で書かれたメソッドであることを示します

、、、と列挙してみましたが、まだよくわかっていないです。

クラスメソッドについて

クラス内にあるメソッドについても、フィールドと同じようにクラスメソッドと呼ぶような宣言の仕方があります。

クラスフィールドと同じように「static」という修飾子をつけて表します。
例として、下記の「countUpTestD」というメソッドをクラスフィールドにしています。

class TestClassSub
{
    //フィールドの初期化
    int TestA = 10;
    int TestB = 30;
    static int TestC = 20;
    int TestD = 40;
     
    TestClassSub(int a, int b) {
        //やっていることは「setTest」メソッドを同じ
        TestA = a;
        TestB = b;
    }
     
    TestClassSub() {
    	//引数なしコンストラクタ用
    	
    }
     
    void setTest(int a, int b){
        TestA = a;
        TestB = b;
    }

    int sumAB(){
        return TestA + TestB;
    }

    int countUpTestC(){
        return TestC++;
    }

    void countUpTestC2(){
        TestC++;
    }

    static int countUpTestD(){
        return TestD++;
    }


}

クラスメソッドは、別名「staticメソッド」という場合もある。

staticをつけずに宣言したメソッドは「インスタンスメソッド」や「staticではない(通常の)メソッド」などと呼び、クラスメソッドと区別をします。

クラスメソッドは特定のインスタンスに関連していな為、インスタンスが生成されていない状態でも呼び出すことができます。
他のクラスから呼び出すときには次のように書きます。

クラス名.メソッド名(引数)

クラスフィールドについて

例えば、以下のクラスをnewしてインスタンスを生成する際、インスタンスをnewした回数を取り扱いたい場合は、クラスの中にクラスフィールドという、全インスタンスに共通の情報保存場所が必要になります。
この保存場所をクラスフィールド(またはクラス変数、スタティックフィールド)と呼び、クラス内に宣言することができます。

これまでに作ってきたクラス

class TestClassSub
{
    //フィールドの初期化
    int TestA = 10;
    int TestB = 30;
     
    TestClassSub(int a, int b) {
        //やっていることは「setTest」メソッドを同じ
        TestA = a;
        TestB = b;
    }
     
    TestClassSub() {
    	//引数なしコンストラクタ用
    	
    }
     
    void setTest(int a, int b){
        TestA = a;
        TestB = b;
    }

    int sumAB(){
        return TestA + TestB;
    }
}

上記のクラスに対し、クラスフィールドを加えたクラス

class TestClassSub
{
    //フィールドの初期化
    int TestA = 10;
    int TestB = 30;
    
    static int TestC = 20;
     
    TestClassSub(int a, int b) {
        //やっていることは「setTest」メソッドを同じ
        TestA = a;
        TestB = b;
    }
     
    TestClassSub() {
    	//引数なしコンストラクタ用
    	
    }
     
    void setTest(int a, int b){
        TestA = a;
        TestB = b;
    }

    int sumAB(){
        return TestA + TestB;
    }
}

上記のTestClassSubクラスを使う場合、一度、クラスのインスタンスを生成し、クラスフィールドを加算するメソッドを何度か呼んでみます。

public class TestClass5 {

    public static void main(String[] args) {
        
        int retA;
        
        System.out.print("hello\n");
        
        //引数なしコンストラクタ
        TestClassSub tc = new TestClassSub();
       
        retA = tc.countUpTestC();
        System.out.print("retA -> " + retA + "\n");
        retA = tc.countUpTestC();
        System.out.print("retA -> " + retA + "\n");
        retA = tc.countUpTestC();
        System.out.print("retA -> " + retA + "\n");
        retA = tc.countUpTestC();
        System.out.print("retA -> " + retA + "\n");
        retA = tc.countUpTestC();
        System.out.print("retA -> " + retA + "\n");
        
    }
}

class TestClassSub
{
    //フィールドの初期化
    int TestA = 10;
    int TestB = 30;
    
    static int TestC = 20;
     
    TestClassSub(int a, int b) {
        //やっていることは「setTest」メソッドを同じ
        TestA = a;
        TestB = b;
    }
     
    TestClassSub() {
    	//引数なしコンストラクタ用
    	
    }
     
    void setTest(int a, int b){
        TestA = a;
        TestB = b;
    }

    int sumAB(){
        return TestA + TestB;
    }

    int countUpTestC(){
        return TestC++;
    }

}


すると、実行した結果は次のようになります。

hello
retA -> 20
retA -> 21
retA -> 22
retA -> 23
retA -> 24

スタックとヒープ

前例のプログラムで、次のように、クラスからインスタンスを生成しました。

        //引数なしコンストラクタ
        TestClassSub tc = new TestClassSub();

この時、tcが確保されている領域をスタック(stack)と呼びます。
また、TestClassSubのインスタンスが確保されている領域をヒープ(heap)と呼びます。

フィールドの初期化

フィールドの初期化を行うには、コンストラクタの内部でもよく、コンストラクタの外でもOK。

前回に投稿したTestClassSubクラスのフィールドを初期化するには、次のように書いてもOK。

class TestClassSub {
    int TestA = 10;
    int TestB = 30;
     
    TestClassSub(int a, int b) {
        //やっていることは「setTest」メソッドを同じ
        TestA = a;
        TestB = b;
    }
     
    TestClassSub() {
    	//引数なしコンストラクタ用
    	
    	
    	
    }
     
    void setTest(int a, int b){
        TestA = a;
        TestB = b;
    }
    int getAB(){
        return TestA + TestB;
    }
}

初期化されていないフィールドの値は、その型に応じて初期化される。
変数の値については、未定義になる。

具体的には、値は次のようになる。

Boolean型 false;
整数型 0
浮動小数点型 0.0
参照型 null

引数なしコンストラクタ

前回に書いたクラスで、コンストラクタを呼び出した時は以下のように書きました。

TestClass tc = new TestClass(100, 200);

これはコンストラクタを呼ぶと同時に引数、100、200をつけています。
このコンストラクタの呼び方を変更して、引数をつけないで呼び出す方法もやってみます。
具体的には下記のように記述します。

TestClass tc = new TestClass();

また、ひとつのクラスの中にはコンストラクタが複数あってもエラーにはならないです。
全体として下記のように記述してみました。
詳しい動作検証は、また後ほどやります。

public class TestClass2 {

    public static void main(String[] args) {
        
        int retA;
        
        System.out.print("hello\n");
        
        //これだとエラーになる
        //TestClassSub tc = new TestClassSub();
        
        //引数をつけるとエラーにならない(コンストラクタで使う為?)
        TestClassSub tc = new TestClassSub(100, 200);
        
        retA = tc.getAB();
        System.out.print("retA -> " + retA);
        
    }
}


class TestClassSub {
    int TestA;
    int TestB;
     
    TestClassSub(int a, int b) {
        //やっていることは「setTest」メソッドを同じ
        TestA = a;
        TestB = b;
    }
     
    TestClassSub() {
    	//引数なしコンストラクタ用
    	
    }
     
    void setTest(int a, int b){
        TestA = a;
        TestB = b;
    }
    int getAB(){
        return TestA + TestB;
    }
}

実行した結果は次のようになります

hello
retA -> 0

実際に動かしてみる

次のようなプログラムを作り、コンパイル後、実行しました。

public class TestClass {

    public static void main(String[] args) {
        
        int retA;
        
        System.out.print("hello\n");
        
        //これだとエラーになる
        //TestClassSub tc = new TestClassSub();
        
        //引数をつけるとエラーにならない(コンストラクタで使う為?)
        TestClassSub tc = new TestClassSub(100, 200);
        
        retA = tc.getAB();
        System.out.print("retA -> " + retA);
        
    }
}


class TestClassSub {
    int TestA;
    int TestB;
     
    TestClassSub(int a, int b) {
        //やっていることは「setTest」メソッドを同じ
        TestA = a;
        TestB = b;
    }
     
    void setTest(int a, int b){
        TestA = a;
        TestB = b;
    }
    int getAB(){
        return TestA + TestB;
    }
}

一番最初に、TestClassSubのインスタンスを作ろうとした時に、下記のようなエラーが表示されました。

d:\data\java>javac TestClass.java
TestClass.java:7: シンボルを見つけられません。
シンボル: コンストラクタ TestClassSub()
場所    : TestClassSub の クラス
        TestClassSub tc = new TestClassSub();
                          ^
エラー 1 個

少し悩んでいたところ、TestClassSubのコンストラクタは引数を書いていたので、インスタンスを作る際にも引数がないとエラーになることに気づき、引数をつけると無事にコンパイルがとおりました。
インスタンスを作り、「getAB」というメソッドを試したまでですが、無事に下記のように表示されました。

d:\data\java>java TestClass
hello
retA -> 300

まずはエラーにならずにサクっと動作させるようにして進みたいと思います。
エラーでつまずくと、少し後ろ向きになってしまいますが、そうゆう場合には、一旦できているところまでを見直しつつ、なるべく前向きに勉強していきます。
道は長い

コンストラクタを使う

先ほど作成した下記のクラス

class TestClass {
    int TestA;
    int TestB;
     
    void setTest(int a, int b){
        TestA = a;
        TestB = b;
    }
    int getAB(){
        return TestA + TestB;
    }
}

に対して、コンストラクタを追加します。

class TestClass {
    int TestA;
    int TestB;
    
    TestClass(int a, int b) {
        //やっていることは「setTest」メソッドを同じ
        TestA = a;
        TestB = b;
    }
    
    void setTest(int a, int b){
        TestA = a;
        TestB = b;
    }
    int getAB(){
        return TestA + TestB;
    }
}

コンストラクタを呼び出すのは、以下のように書きます。

TestClass tc = new TestClass(100, 200);

メソッドの呼び出し

さきほど作成したTestClass内にあるメソッドを呼び出して使って見ます。

TestClass tc = new TestClass();
tc.setTest(100, 200);

上記は「setTest」というメソッドを呼んで値を渡しています。
その後処理はメソッドに基づいて計算されます。

フィールドへのアクセス方法

新しいインスタンスを作成した後は、フィールドに値を代入してみます。

TestClass tc = new TestClass();
tc.TestA = 1000;
tc.TestB = 2000;

TestAやTestBという変数名は意味としてわかりづらいですが、まずは感触をつかむ為にこのように書いてみます。

インスタンスの作り方

先ほど作ったTestClassを使うためには、インスタンスを作る必要があります。

具体的には次のように書きます。

new TestClass();

newで宣言するだけなら、なにも計算ができないので、次のようにしてTestClass型として変数に代入します。

TestClass tc = new TestClass();

フィールド、メソッド

これまでに何度も出てきている「フィールド」について勉強します。

フィールドは情報を保存する変数のようなものです。

例えば、下記のように書きます。

class TestClass {
    int TestA;
    int TestB;
}

TestAとTestBはTestClassのフィールドと呼びます。

続いてメソッドについて、考えます。
メソッドは関数のような意味で、宣言します。

class TestClass {
    int TestA;
    int TestB;
    
    void setTest(int a, int b){
        TestA = a;
        TestB = b;
    }
    int getAB(){
        return TestA + TestB;
    }
}

抽象的なインタフェースを具体的に実装するクラスを宣言することもできます。

class TestClass implements TestAAA{

}

TestAAAインタフェースを実装したTestClassというクラスを宣言する。という意味になります。
が、まだよくわかっていません。そもそもインタフェースとはなにか、これから掘り下げてみたいと思います。

既に存在しているクラスをもとにして、拡張するクラスを宣言することもできます。

class TestClass extends Thread{
 
}

上記の場合は、Threadクラスを拡張してTestClassを宣言しています。

クラスの宣言

javaに標準搭載されているクラスの他に自分でクラスを作る場合は、次のように書きます。

class TestClass{

}

クラスの名前は大文字で始めるのが慣習となっているようです。
うむむ。

クラスの宣言と同時にフィールドの宣言、メソッドの宣言も、書くと次のようになります。

class TestClass{
    フィールドの宣言
    メソッドの宣言
}

「フィールド」は、そのクラスにおける変数と考え、
「メソッドは、関数、と考えられます。

クラスの種類

クラスには様々な種類があります。

例えば

文字列を表す「String」クラス
ファイルを表す「File」クラス
ファイルを読み込む「FileReader」クラス
ファイルに書き込む「FileWrite]クラス
Javaシステム全体で扱う「System」クラス

javaで開発を行う際には、もともと搭載されているクラスにどんなものがあるのかを一旦把握した上で開発するのがよいと思います。実際になにかを開発して、手足のように使いこなすまでは、しらばく勉強を続けたいと思います。

クラスとインスタンス

オブジェクト指向を勉強するにあたり、クラスとインスタンスの考え方からやっていきます。

インスタンスはクラスから生成される。という原則をもとに考えると、文字列もまたStringクラスのインスタンス。と考えることができます。

プログラム内で「”Hellow”」と記述した場合は、Stringクラスのインスタンスが生成されたものと同じことになります。
そのクラスの性質としては、「文字列を表す」「文字列の長さを取得できる」といった文字列に関係する操作(関数)ができるようになります。

「インスタンス」とは、上記の「”Hellow”」のように、「特定のもの」を表すと考えることができます。
全てのインスタンスは、クラスというものに属しています。

「インスタンスは具体的な特定のもの」といえます。また、「同じクラスのインスタンスは共通の性質を持つ」といえます。

クラス宣言とフィールド

「クラスとオブジェクト」の時に書いた以下のコード

class TestClass {
	String test_a;
	int test_b;
}

の部分を詳しくみると、「String test_a;」という部分があります。
この「String」の部分は型、「test_a」の部分はフィールドと呼びます。(クラス内にある関数はメソッドと呼びます)

オブジェクト指向のプログラムを行うときは、インスタンスを生成してフィールド内の(変数の)値を参照したりすることが多くあるので、用語として覚えておきます。

また、クラスの宣言を確認するときは、必ず「フィールド」と「メソッド」をチェックしてプログラムを行います。

コンストラクタについて

インスタンスを生成する際に、初期化を行ったり、初期値を設定したりする時に、コンストラクタというものを使います。

コンストラクタはクラスの中に書き、インスタンスの初期化を行う(生成時に一度実行される性質の為)
下記のように書きます。

また、コンストラクタには戻りの型を書きません。

	public class TestClass {
		String test_a;
		int test_b;
		
		//コンストラクタ
		public TestClass(String test_a, int test_b) {
			
		}
		
	}

また、コンストラクタ内ではthisという「現在のインスタンス」を表す変数が使えます。
thisはjavaの予約語で、頻繁に使います。

	public class TestClass {
		String test_a;
		int test_b;
		
		//コンストラクタ
		public TestClass(String test_a, int test_b) {
			this.test_a = test_a;
			this.test_b = test_b;
		}
		
	}

クラスとオブジェクト

javaの基本的な書き方の次のステップとして、クラスとオブジェクトに差し掛かっていきます。
まずはクラスとはなにかを勉強し、その後にオブジェクト指向の勉強に進めていきます。

2014.09.24 追記
クラスとは、関連した情報を1つにまとめたもの。
以下のように宣言します。

public class TestClass {
	String test_a;
	int test_b;
}

実際にプログラムして、動きを確かめようと思い、次のようなソースを作りました。(ファイル名は「array_test2.java」としました)

public class array_test2 {

    public static void main(String[] args) {
        
        //「tc」はTestClassのインスタンスという
        TestClass tc = new TestClass();
        
        tc.test_a = "A";
        tc.test_b = 1;
        

        //ひとつのクラスから2個目のインスタンスを作る
        TestClass tc2 = new TestClass();
        tc2.test_a = "B";
        tc2.test_b = 2;
        
    }

	public class TestClass {
		String test_a;
		int test_b;
	}
}

コンパイルを実行する

c:\javac array_test2.java

すると、次のような結果がでます(エラー)。

array_test2.java:6: static でない 変数 this を static コンテキストから参照することはできません。
        TestClass tc = new TestClass();
                       ^

ネットを調べたりしましたが、少しの時間悩み、次のように書き直したところ、動作しました。

public class array_test2 {

    public static void main(String[] args) {
        
        TestClass tc = new TestClass();
        
    }

}

class TestClass {
	String test_a;
	int test_b;
}

結局、TestClassのクラスが「public class array_test2 」の外側にないとエラーになるようです。
理由としては、staticのついたメソッド(クラスメソッド)の中で、自クラス内のstaticでないメソッドや
フィールド変数、クラス内クラスを参照することはできない。為でした。

うーん、まだ、感覚的にわからないです。

配列について

配列を書いてみます。

public class array_test {

    public static void main(String[] args) {
        
        //配列を宣言
        int[] test_array;
        
        //配列を3つ設定
        test_array = new int[3];
        
        test_array[0] = 10;
        test_array[1] = 20;
        test_array[2] = 30;
        
        System.out.print("配列の中身は " + test_array[0] + "\n");
        System.out.print("配列の中身は " + test_array[1] + "\n");
        System.out.print("配列の中身は " + test_array[2] + "\n");
        
        //配列の長さ(要素数)を取得する
        System.out.print("配列の中身の数は " + test_array.length + "\n");
        
        
        
        //配列の初期化(宣言と同時に値を入れる)
        int [] test_array_s = {10, 20, 30};
        
        //配列「test_array」にまとめて代入する書き方も可能
        test_array_s = new int[]{10, 20, 30};
        
        //2次元配列の場合
        int[][] test_array_ss;
        
        //初期化と同時に値を代入
        int[][] test_arrays_sss = {
        	{10, 20, 30},
        	{40, 50, 60},
        	{70, 80, 90}
        };
        
        //出力する場合は次のように書く
        System.out.print("2次元配列の3行目の2列は " + test_arrays_sss[2][1] + "\n");

        //2次元配列の要素数は一定でなくてもOK
        int[][] test_arrays_ssss = {
        	{10, 20, 30},
        	{40, 50},
        	{70, 80, 90}
        };
        
        
        
    }
}


実行結果は

配列の中身は 10
配列の中身は 20
配列の中身は 30
配列の中身の数は 3
2次元配列の3行目の2列は 80

となります。

メソッドの種類について

java言語の基礎的な部分がC言語と多いので、基礎的な文法の練習は省きたいと思いますが、メソッド(関数)の書き方についてはjava特有の書き方があるので、勉強してみます。

まずは、以下のサンプル

public static void main (String[] args) {

}

このメソッドの宣言を見てみると、次のように解釈できます。

public → アクセス制限(公開されているかどうか)
static → クラスメソッド
int → 戻り値の型を指定
main → メソッドの名前
String[] args → 引数はString型 argsはこれから勉強する

publicというアクセス制限の部分については、次の段階で細かく勉強していきます。

ランダム関数を使ってみます

ランダムな値を表示させるプログラムを書いてみます。

下記のようなプログラムを書いて、コンパイル→実行、をしてみます。

import java.io.*;

public class random_test {

    public static void main(String[] args) {
        
        //乱数のテスト
        int x;
        
        x = (int)(Math.random() * 9);

        System.out.print("乱数の結果->" + x + "\n");
        
    }

}

実行結果は

乱数の結果->6

という表示になり、実行の度に数値がランダムに変わります。
少しjavaを書くことに慣れてきました。

C言語と同じような部分が多く、文法的な練習については省きます。

入力待ち状態のプログラムを書きます。

import java.io.*;

public class input_test {

    public static void main(String[] args) {
        
        System.out.print("なにか入力してください\n");
        
        BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
        
        try {
	        String line = reader.readLine();
	        
	        System.out.print("入力した文字は" + line + "です\n");
	    } catch (IOException e) {
            System.out.print(e);
	    } catch (NumberFormatException e) {
            System.out.print("なにか間違っています\n");
	    }
        
    }

}

実行すると、次のようになります。

なにか入力してください
129873469127384
入力した文字は129873469127384です

初歩の為に書いたプログラムは簡単でしたが、上記のプログラムは見たことがない記述が出て気ました。

まず行頭でいきなりimportという命令が来ます。
これはクラスライブラリを利用する時に宣言する記述です。

そもそもBufferedReaderというものを見たことも書いたこともなかったです。
どうやらこれはデータの読み込みを行う為のクラスということでした。

C言語を勉強した時には出てこなかったtryという書き方もjavaでは頻繁に使うようです。
あとは、クラスをnewして使う部分もC言語では出てこなかったと思います。

とにかく一度書いて動かしてみて慣れていくのがいいのかもしれません。

言語に慣れる為に、いろいろと書いて実行をさせてみます。
文法的にはC言語に近い部分があるので、初歩の段階では得に違和感もなく書いて動かしてみました。

public class hello {
    public static void main(String[] args) {
        
        System.out.print("hello\n");
        
        //改行を試す
        System.out.print("hello2\n");
        System.out.print("hello3\n");

        //日本語を試す
        System.out.print("でますか?\n");
        
        //加減乗除を試す
        System.out.print("たしざん" + (3+2) + "\n");
        System.out.print("ひきざん" + (3-2) + "\n");
        System.out.print("かけざん" + (3*2) + "\n");
        System.out.print("わりざん" + (3/2) + "\n"); //結果は余りが表示される
        
        //変数を試す
        int x;
        x = 5;
        System.out.print("変数の中身は" + x + "です");
        
    }
}

実行すると次のような結果になります。

hello
hello2
hello3
でますか?
たしざん5
ひきざん1
かけざん6
わりざん1
変数の中身は5です

JAVAでソースコードを書いて、コンパイルし、hellow worldを出力したとがないので、やってみます。(いままではIEDとかを使用していました)

次のコードを書いて、hello.javaというファイルに保存します。

public class Hellow {
	public static void main(String[] args) {
		System.out.print("hello");
	}
}

次にDOSコマンドプロンプトを立ち上げて、ソースコードを保存したディレクトリに移動します。

//便宜的に下記のディレクトリを作業ディレクトリにしました
cd C:\works\java

次にソースコードをコンパイルするコマンドを入力します。
コンパイルは「javac」の後にスペースを空けてソースコードを指定します。

C:\works\java>javac hello.java

と、ここで下記のようなエラーが発生しました。

'javac' は、内部コマンドまたは外部コマンド、
操作可能なプログラムまたはバッチ ファイルとして認識されていません。

少し調べてみましたが、原因はまだわかっていません。

PC内の環境変数をチェックしてみましたが、正しくPATHが追加されていました。

(20140828追記)
その後、一旦PCから離れ改めてjavacを実行してみると次のように表示されました。
おそらくですが、JDKをインストールし、環境変数のpathを変更したあとにPCの再起動をした為かと思います。

C:\works\java>javac
使い方: javac <options> <source files>
使用可能なオプションには次のものがあります。
  -g                         すべてのデバッグ情報を生成する
  -g:none                    デバッグ情報を生成しない
  -g:{lines,vars,source}     いくつかのデバッグ情報だけを生成する
  -nowarn                    警告を発生させない
  -verbose                   コンパイラの動作についてメッセージを出力する
  -deprecation               推奨されない API が使用されているソースの位置を出力する
  -classpath <path>          ユーザークラスファイルおよび注釈プロセッサを検索する位置を指定する
  -cp <path>                 ユーザークラスファイルおよび注釈プロセッサを検索する位置を指定する
  -sourcepath <path>         入力ソースファイルを検索する位置を指定する
  -bootclasspath <path>      ブートストラップクラスファイルの位置を置き換える
  -extdirs <dirs>            インストール済み拡張機能の位置を置き換える
  -endorseddirs <dirs>       推奨規格パスの位置を置き換える
  -proc:{none,only}          注釈処理やコンパイルを実行するかどうかを制御します。
  -processor <class1>[,<class2>,<class3>...]実行する注釈プロセッサの名前。デフォルトの検出処理をバイパス
  -processorpath <path>      注釈プロセッサを検索する位置を指定する
  -d <directory>             生成されたクラスファイルを格納する位置を指定する
  -s <directory>             生成されたソースファイルを格納する場所を指定する
  -implicit:{none,class}     暗黙的に参照されるファイルについてクラスファイルを生成するかどうかを指定する
  -encoding <encoding>       ソースファイルが使用する文字エンコーディングを指定する
  -source <release>          指定されたリリースとソースの互換性を保つ
  -target <release>          特定の VM バージョン用のクラスファイルを生成する
  -version                   バージョン情報
  -help                      標準オプションの概要を出力する
  -Akey[=value]              注釈プロセッサに渡されるオプション
  -X                         非標準オプションの概要を出力する
  -J<flag>                   <flag> を実行システムに直接渡す

さらにコンパイルを実行してみると、コンパイルに失敗。

C:\works\java>javac hello.java
hello.java:1: クラス Hellow は public であり、ファイル Hellow.java で宣言しなければなりません。
public class Hellow {
       ^
エラー 1 個

プログラム内のクラス名がおかしいので修正をする。

public class hello {
	public static void main(String[] args) {
		System.out.print("hello");
	}
}

改めてコンパイル

C:\works\java>javac hello.java
C:\works\java>

何事もなく完了。コンパイルしたjavaを実行します。

C:\works\java>java hello
hello

と表示されました。
ケアレスミスが多くとまどりましたが、環境構築はひとまず完了です。

JAVAの開発環境を整える

まず最初にJAVAの開発環境を用意します。

 

JAVAは移植性の高い言語と呼ばれています。

 

その理由は一度書いたソースコードはJVMと呼ばれる、プラットフォーム上で、ネイティブコードに変換して実行する仕組みになっている為、どの機械でも動作しやすいように設計されています。

 

配布時にはプラットフォームから独立したJAVAバイトコードになっていて、それをプラットフォーム固有のネイティブコードに変換して実行します。

 

この変換と実行を行う部分がJVMと呼ばれるものです。
実行前にまとめて変換することで実行時のオーバーヘッドをなくして実行速度を向上させたものをJITコンパイラと呼びます。

 

■開発環境を整える

まずは、JAVAを動作させる為に、JDKをインストールします。
公式のサイトからJDKをダウンロードし、自マシンにインストールします。
■公式サイト
http://www.oracle.com/technetwork/java/javase/downloads/index-jsp-138363.html#javasejdk
■JDKのダウンロード(ここから適宜、バージョンを選択してダウンロードする)
http://www.oracle.com/technetwork/java/javase/downloads/jdk8-downloads-2133151.html

JAVAの勉強を開始します

これからJAVAの言語を勉強していきます。

今まではおぼろげながらJAVAに触れてきましたが、ひととおりの知識を得る為に、ひとつひとつ実験しながら、体系的にやっていきます。

体系的に知ることが目的なので、深すぎる部分については、次の段階で落とし込もうと思います。

プログラムを中止する

任意の場所でプログラムを中止させる場合は、exit()関数を使います。

stdlib.hが必要です。

#include <"stdio.h">
#include <"strlib.h">

int main(void)
{
    int i;
    if (i) {
        exit(); //ここで突然終わります。
    }
}

ソート(データを並び替える)

配列内の数値や文字列を並びかえるにはqsort()関数を使います。

参考例を実行してみましたが、動作せず・・・。

#include <stdio.h>
#include <stdlib.h>

int compare(const void *a, const void*b)
{
	if (*a > *b) {
		return 1;
	} else if(*a < *b) {
		return -1;
	} else if(*a == *b) {
		return 0;
	}
}

int main(void)
{
	int num[] = {4, 6, 5};
	qsort(num, sizeof(int), compare);
	printf("%d\n", num);
	
	return 0;
}

エラーメッセージは次のように出ました。

関数呼び出しに指定されているパラメータ数が少ないです。
呼び出している関数の引数の数を再確認してください。

「qsort(num, sizeof(int), compare);」この行でエラーが出ていることがわかっていますので、C言語ライブラリのマニュアルを検索してみると、次のように書かれています。

#include <stdlib.h>

void qsort( void * data , size_t data_cnt
       , size_t data_size
       , int( * func )( const void * , const void * );

■戻り値:
なし

数学関数

指数や平方根などの数学レベルでの計算が必要な場合に使います。

実際に使う場合は、math.hで定義された数学用の関数を読み込みます。

■乱数を作る
ランダムな文字列を作成するには、stdlib.hを読み込み、rand()関数を使います。

実際に書いてみます。

#include <stdio.h>
#include <stdlib.h>

int main(void)
{
	
	int n;
	srand(time(NULL));
	n = rand();
	
	printf("%d\n", n);
	
	return 0;
}

結果は、実行するたびにランダムな数字が表示されます。

時間に関する関数

日時を取得する為には、time()関数とlocaltime()関数を組み合わせて使います。

これらの関数の定義はtime.hの中で定義されています。

■現在時刻を得る

time_t ct;  //time_t型という型になります
ct = time(NULL);
struct tm *now;
now = localtime(&ct); //「&ct」はtime関数で得た値が入った変数のアドレスを指定します。

time.hを利用したtm構造体のメンバはtm_secやtm_min等があります。
その他のメンバや、使い方等はC言語の標準ライブラリについて調べる必要があります。

シフト演算子

シフト演算子とは、ビット列を左右に指定した分だけずらす動きをします。

シフト演算子は2種類あり、「右シフト演算子」と「左シフト演算子」があります。

■右シフト演算子(ビット列を右に2ビットシフトする)

                    ↓最上位ビット ↓最下位ビット
元のビット列         1 0 1 1 0 0 1 0
                     ~~~ここが
シフトしたビット列   1 1 0 0 1 0 1 0
                                 ~~~ここにシフトされる

■左シフト演算子(ビット列を左に2ビットシフトする)

                    ↓最上位ビット ↓最下位ビット
元のビット列         1 0 1 1 0 0 1 0
                                 ~~~ここが
シフトしたビット列   1 0 1 0 1 1 0 0
                     ~~~ここにシフトされる

ビット演算子

データをビット単位で、比較、操作する際に使うのがビット演算子です。

■論理積(and) &
各ビットを比べて、「両方とも1なら1、そうでなければ0」を返す演算

参考例
a = 170, b = 245

変数名    10進数    2進数
a         170		1	0	1	0	1	0	1	0
b         245		1	1	1	1	0	1	0	1
a&b       160		1	0	1	0	0	0	0	0

■論理和(or) |
「片方が1なら1、そうでなければ0」を返す演算」(これはどちらも1の場合は1となる)

変数名    10進数    2進数
a         170		1	0	1	0	1	0	1	0
b         245		1	1	1	1	0	1	0	1
a|b       160		1	1	1	1	1	1	1	1

■排他的論理和(xor) ˆ
「片方が1で、もう片方が0なら1、そうでなければ0」を返す演算」

変数名    10進数    2進数
a         170		1	0	1	0	1	0	1	0
b         245		1	1	1	1	0	1	0	1
aˆb       160		1	1	1	1	1	1	1	1

■1の補数表現(not) ~
「各ビットを反転させたもの」

変数名    10進数    2進数
a         170		1	0	1	0	1	0	1	0
a~        160		0	1	0	1	0	1	0	1

列挙型

列挙型とは、整数値に特定の名前を与えることです。

列挙型を使うと、int型の整数値に名前をつけることができます。
列挙型の宣言はenumという記述ではじめます。

サンプルを書いて動かしてみます

#include <stdio.h>

int main(void)
{
	//列挙型の宣言
	enum _mon{
		Jan,
		Feb,
		Mar,
		Apr,
		Jun
	} mon;
	
	printf("%d\n", mon); //結果「4198543」と表示される
	
	//明示的な値を代入する場合

	//列挙型の宣言
	enum _mon{
		Jan = 1,
		Feb,
		Mar = 5,  //ここはわざと5にする
		Apr,
		Jun
	} mon;
	
	
	//↑の例では、1,2,5,6,7,という値が自動的に割り振られることになる
	
	
	//列挙型の値を使う時の例。列挙指定子のいずれかの名前を使った代入や参照ができる
	mon = Feb;
	
	printf("%d\n", mon); //結果「2」と表示される
	
	return 0;

}

共用体

共用体とは、1つのアドレスに異なるデータを割りあてることです。

共用体は、1つのメモリ領域で異なる型の変数のどれかひとつを選んで使うことができます。
共用体の宣言は以下のようになります。

//「uniondata」は共用体名、「unilist1」は共用体変数
union uniondata{
    int no;
    char name[10];
    float weight;
};
union uniondata unilist1;

共用体を使ったサンプルを試してみます。

#include <stdio.h>

int main(void)
{

	//「uniondata」は共用体名、「unilist1」は共用体変数
	union uniondata{
	    int no;
	    char name[10];
	    float weight;
	};
	union uniondata unilist1;


	unilist1.no = 1;
	printf("%d\n", unilist1.no);
	
	strcpy(unilist1.name, "test!!");
	printf("%s\n", unilist1.name);
	
	unilist1.weight = 123.4;
	printf("%f\n", unilist1.weight);

	//結果以下のように表示されました
//1
//test!!
//123.400002

	return 0;
}

unilist1がメモリに占める領域は一番大きな型にあわせた大きさになる。
共用体はまだまだ深いので、改めて追記して理解を深めることにします。

引数つきマクロ

#defineを使うと、引数をもち、関数のように動作するマクロを定義することができます。

#define HIKU(x, y) ((x) - (y))

上記の書き方は有効範囲は1行です。
複数行の場合は次のように定義します。

#define hiku2(a, b, c) ((a) * (b) * (c) )\
+ a+b+c)

実際に動かしてみます

#include <stdio.h>

#define HIKU(x, y) ((x) - (y))

int main(void)
{
    int i;
    
    printf("マクロ!%d\n", HIKU(10, 8));
    
    return 0;
}

//結果は「マクロ!2」と表示されます

引数つきマクロは、実行する処理全体とその中の引数はカッコでくくります。

//正しい例
#define HIKU(x, y) ((x) - (y))

//間違った例
#define HIKU(x, y) (x - y)

マクロ名と()の間にスペースを入れてしまうと、区切りが正しく認識されません。

//正しい例
#define HIKU(x, y) ((x) - (y))

//間違った例
#define HIKU (x, y) ((x) - (y))

条件に応じたコンパイル指示

条件に応じて、必要な部分だけ抜き出してコンパイルしたい場合、次のように書きます。

#if 条件
    指定範囲
#endif
#ifdef 識別子
    指定範囲
#endif
#ifndef 識別子
    指定範囲
#endif

また、複数の条件を判断することもできます。

#ifdef 識別子
    範囲指定A   //指定範囲Aをコンパイルする
#elsif 条件B
    範囲指定B   //指定範囲Bをコンパイルする
#else
    範囲指定C   //指定範囲Cをコンパイルする
#endif

複数のプログラムファイル内でヘッダファイルを使うと、同じヘッダファイルを重複インクルードしてしまう場合があります。
これを防ぐためには次のように書きます。

#ifndef _MYHEADER_
#define _MYHEADER_
    //2回目以降は既に呼ばれているので、この内容は読み込まれない
    void MyFunc();
    extern int x;
#endif

マクロ

#からはじまる1行分のことをマクロと呼びます。
マクロはプログラムのソースコードをコンパイルする前にプリプロセッサが処理します。

プログラムの最初に記述する「#include」もマクロの一種です。

マクロは次のような書式で書きます。

//マクロの中にはスペースやタブは入れてはダメ
#define マクロ名 パターン

■置換
#defineは、文字列を置換するマクロです。

#define VALNUM 3;

次のように書くと、DEBUG_MODEが定義されているということを表します。

#define DEBUG_MODE;

簡単なサンプルを書いてみましたが、うまく動作せず。

#include <stdio.h>

#define VALNUM 3;

int main(void)
{
    printf("%d \n", VALNUM);
    
    
    return 0;
}


//実行結果
「7行目」で記述エラーを発見しました。
「,」を付け忘れています。

もうひとつ、サンプルを書きましたが、これもうまく動作しませんでした。原因について調査中、、、

#include <stdio.h>

#define VALNUM 3;

int main(void)
{
	int i;
	
	for (i=0; i<VALNUM; i++) {
	    printf("%d \n", i);
	}
    
    return 0;
}

//実行結果
「9行目」で記述エラーを発見しました。
「identifier」を付け忘れています。

エラーの原因から地道に紐解いていこうかと思います。
解決したら、ブログに追記します。

2014.09.25追記
コメントからご指摘いただいた点を元に、下記のようなプログラムを作って動かしてみました。

#include <stdio.h>
 
#define VALNUM 3
 
int main(void)
{
    int i;
     
    for (i=0; i<VALNUM; i++) {
        printf("%d \n", i);
    }
     
    return 0;
}

エラーが出ずに実行され、コンソール上には「0 1 2」という表示が出ました。
ケアレスミスでした。

static宣言

グローバル変数をstaticをつけて宣言すると、変数の有効範囲はその宣言をしたファイル内に限られます。

外部変数を宣言して、他のファイルから参照することはできません。

例として次のような場合

//ソースファイル1で記述
static int gvar;

//ソースファイル2で記述
extern int gvar;

//ソースファイル3で記述
extern int gvar;

上記のようにソースファイル2やソースファイル3からgvarを呼び出そうとしてもエラーになります。

■static宣言したローカル変数
static変数はプログラムの開始から終了まで、値が削除されません。
ローカル変数は関数の中で宣言され、関数の処理が終わると同時に破棄されます。

■const宣言
const宣言は変数の値を書き換えられないようにする宣言です。
定数として変数を使用する場合は、この宣言を使います。

const宣言を関数の引数で指定している場合、その引数は関数の中で値が保持されることを保障する動きになります。

ファイルの分割と組み立て

ソースファイルの分割

ひとつのソースファイルに大量のプログラムコードを書くと良くないので、分割をしてソースファイルを管理します。

通常、プログラムを構成する「機能」ごとにソースファイルを分割します。

ソースファイルを分割すると、どのプログラムファイルからも参照できる変数が必要になってきます。
その変数を「外部変数」とよび、変数を宣言することを「外部変数宣言」といいます。

ソースファイルの分割例

本体ファイル①

int global_val;

分割ファイル①

extern int global_val;

分割ファイル②

extern int global_val;

上記のように記述します。

■ヘッダファイルを利用する
ヘッダファイルを利用することにより、さらに効率的にプロジェクトを管理できるようになります。

コンパイルとリンク

実行ファイルを作るには、C言語のソースを書いて、「コンパイル」、「リンク」することで完成します。

コンパイルとリンクを合わせて「ビルド」または「メイク」と呼びます。