JavaでDAOパターンを使ってみる
他の記事でJavaからSqliteのデータベースに接続して簡単なデータの取得の例を書きました.この記事ではプログラムからデータベースを操作しやすくするための工夫について書いていきたいと思います.その工夫というのがDAOパターンです.
DAOパターンについて
DAOパターンというのはデータベースへの操作(SELECT,INSERT,UPDATE,DELETE)をテーブルごとに記述するというプログラムの書き方のパターンのことを言います.テーブルごとにクラスを用意し,そのクラスに操作を行うメソッドを書いておくことでとあるテーブルに対して操作を行いたい時に,メソッドを呼び出すだけでテーブルに対する操作を行うことができるようになります.図に表すと以下のようになります. テーブルごとにDAOを書くという手間は大きいですが,呼び出す側のプログラムがスッキリします.
JavaでDAOパターンを実装
それでは実際にDAOパターンを用いたプログラムの例を書いていきたいと思います.今回書くプログラムは以前の記事の例を引き続き用いたいと思います.まずは,PROFILEテーブルのデータ構造をJavaのクラスで表現したものを作ります.これはDTO(Data Transfer Object)と呼ばれるものになります.例を次に示します.
public class Profile { private int id; private String firstName; private String lastName; private String gender; public Profile(int id, String firstName, String lastName, String gender) { this.id = id; this.firstName = firstName; this.lastName = lastName; this.gender = gender; } public int getId() { return this.id; } public String getFirstName() { return this.firstName; } public String getLastName() { return this.lastName; } public String getGender() { return this.gender; } public String toString() { return "id: " + this.id + ", first-name: " + this.firstName + ", last-name: " + this.lastName + ", gender: " + this.gender; } }
次にDAOパターンのクラスを示します.今回はPROFILEテーブルのデータを全て取ってくるという操作だけ書いています.
import java.util.List; import java.util.ArrayList; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; public class ProfileDAO { private final Connection conn; public ProfileDAO(Connection conn) { this.conn = conn; } public List<Profile> getAll() { List<Profile> data = new ArrayList<Profile>(); PreparedStatement pstmt = null; ResultSet rs = null; try { String sql = "select * from PROFILE"; pstmt = conn.prepareStatement(sql); rs = pstmt.executeQuery(); while(rs.next()) { int id = rs.getInt("id"); String firstName = rs.getString("first_name"); String lastName = rs.getString("last_name"); String gender = rs.getString("gender"); Profile prf = new Profile(id, firstName, lastName, gender); data.add(prf); } } catch (SQLException e) { e.printStackTrace(); } finally { try { if(pstmt != null) { pstmt.close(); } if(rs != null) { rs.close(); } } catch (SQLException e) { e.printStackTrace(); } } return data; } }
次に記述した操作を試すためのMainクラスを示します.
import java.util.List; import java.util.Iterator; import java.sql.Connection; import java.sql.DriverManager; import java.sql.SQLException; public class Main { public static void main(String[] args) { Connection conn = null; try { Class.forName("org.sqlite.JDBC"); conn = DriverManager.getConnection("jdbc:sqlite:sample.db"); ProfileDAO profileDAO = new ProfileDAO(conn); List<Profile> profileData = profileDAO.getAll(); for(Iterator it = profileData.iterator(); it.hasNext(); ) { System.out.println(it.next()); } } catch (ClassNotFoundException e) { e.printStackTrace(); } catch (SQLException e) { e.printStackTrace(); } finally { try { if(conn != null) { conn.close(); } } catch (SQLException e) { e.printStackTrace(); } } } }
Mainクラスを見ていただくと,何も工夫しない場合よりもプログラムがかなりスッキリしています.
終わりに
今回はDAOパターンを使ってデータベースを操作する例を書きました.Mainクラスがかなりスッキリしたことがわかると思います.このDAOパターンですが,扱うテーブルの数が大きくなると,作成する必要があるクラスやメソッドが増えるためかなり大変になります.テーブルの構造に変更が生じるとなるとDAOやDTOのクラスの変更が手に負えない状態になります.DAOパターンはテーブル数があまり多くないときに使うといいかもしれません.
JavaからSqliteのデータベースを動かしてみる
何らかのデータを貯蔵したり,データを取ってきたりする必要が生じた際にデータベースという技術を使うことになります.データの貯蔵はあらゆる場面で必要となる手順です.貯蔵の対象となるデータの例を挙げると,商品のリスト,プロフィール,図書館の本など,数えるとキリがありません.ということは,データベースを扱う場面が数え切れないほどあるということです.
データベースのデータのやり取りはSQL文を入力することで可能です.しかし,毎回SQL文を打ってデータを取得するのは非効率的です.そこで,現在出回っているプログラミング言語の多くではプログラムからデータベースを扱うためのライブラリが用意されており,プログラムを通してデータベースからデータをやり取りすることができます.
データベースを扱う場面が多いためプログラムからデータベースを操作することができることにこした事はありません.今回は数あるデータベースのソフトウェアの中で設定などをしなくて済むSqlite3を用いて,Javaからデータベースを操作する例を書いていきます.
Javaプログラムからデータベースを操作する
今回はSqlite3を使うのでこのサイトからダウンロードしてインストールしてください.OSごとにインストール方法が違うのですが,お手元のPCのOSに合うインストーラをダウンロードしてインストールしてください.
JavaでSqlite3を使えるようになるためにSqlite3用のJDBCをダウンロードしておいてください.ダウンロードできるサイトはこちらになります.ちなみにJDBCというのは,Javaからデータベースに接続するためのAPIです.最新のJDBCをダウンロードした後,ダウンロードしたJDBCをJavaのコードを置くディレクトリに移動またはコピーしておいてください.
Javaのコードや後ほど作成するSQLファイル,データベースファイルを置くディレクトリについてですが,特にどのディレクトリなのか指定しません.適当な場所に作業用ディレクトリを作っておいて,そこに置くようにすると作業しやすくなると思うのでおすすめします.
Javaからデータベースを操作する前にSqlite3のテーブルを作成します.まずは以下のSQL文が書かれたファイルを作成します.ファイル名はprofile.sqlとします.
CREATE TABLE PROFILE (id integer,first_name text,last_name text,gender text); INSERT INTO PROFILE VALUES(10,'taro','yamada','male'); INSERT INTO PROFILE VALUES(11,'yoko','hanada','female');
そして次のコマンドを実行します.
$ sqlite3 sample.db < profile.sql
このコマンドによってsample.db
というデータベースファイル内にprofile.sql
に記述された通りにPROFILEテーブルが作成されており,2件のレコードが入っています.現時点で作業用ディレクトリは以下のようになっていると思います.
$ ls profile.sql sample.db sqlite-jdbc-3.27.2.1.jar
次にJavaからデータベースを操作するプログラムの例を示します.
import java.sql.Connection; import java.sql.DriverManager; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.PreparedStatement; public class UseSqlite { public static void main(String[] args) { Connection conn = null; PreparedStatement pstmt = null; try { Class.forName("org.sqlite.JDBC"); conn = DriverManager.getConnection("jdbc:sqlite:sample.db"); String sql = "select * from PROFILE"; pstmt = conn.prepareStatement(sql); ResultSet rs = pstmt.executeQuery(); while(rs.next()) { int id = rs.getInt("id"); String name = rs.getString("first_name") + " " + rs.getString("last_name"); String gender = rs.getString("gender"); System.out.println("id: " + id + ", name: " + name + ", gender: " + gender); } } catch (SQLException e) { e.printStackTrace(); } catch (ClassNotFoundException e) { e.printStackTrace(); } finally { try { if (pstmt != null) { pstmt.close(); } if (conn != null) { conn.close(); } } catch (SQLException e) { e.printStackTrace(); } } } }
行数は例外処理のため少し大きくなりましたが,流れはそんなに複雑ではありません.重要な部分を抜き出すとすると,以下のようになります.
Class.forName("org.sqlite.JDBC"); conn = DriverManager.getConnection("jdbc:sqlite:sample.db"); String sql = "select * from PROFILE"; pstmt = conn.prepareStatement(sql); ResultSet rs = pstmt.executeQuery();
1行目の部分でどのJDBCを使うか指定します.2行目でデータベースsample.db
に接続します.3行目ではSQL文をString型の変数sqlに格納しておき,そのSQL文を4行目で実行します.5行目で実行結果を取得します.プログラムにあるwhile文のようにする事でrs
に格納された結果を取り出すことができます.
注意すべきなのはプログラム実行時です.コンパイルはいつも通りのコマンドでいいのですが,プログラム実行時は以下のようにする必要があります.
$ java -classpath sqlite-jdbc-3.27.2.1.jar: UseSqlite
UseSqliteというのは上記のコードを私が書いた時につけたjavaコードのファイル名です.実行結果は次のようになります.
id: 10, name: taro yamada, gender: male id: 11, name: yoko hanada, gender: female
終わりに
今回はJavaからデータベースを操作する簡単な手順を説明しました.プログラムからデータベースを操作する際は,プログラムの内容が分かりやすくなるために色々な工夫がなされることが多いです.今回はとりあえず最低限でもプログラムからデータベースを操作するために,そういった工夫を一切せずにデータベースを操作するプログラムを例としてあげました.どのような工夫がなされるかについては他の記事で書きたいと思います.
覚えておきたいosモジュールのメソッド
様々なプログラミング言語にはOSが行うファイルやプロセスの操作を行う処理をプログラミング言語上で扱うためのライブラリが用意されています.Pythonではそれがosモジュールに当たります.今回はosモジュールの中でもより頻繁に使うメソッドを紹介したいと思います.覚えていただくと作業効率が向上します.
対話モードで使うosモジュールのメソッド
Pythonの特徴として代表すべきものの1つは対話モードと言っていいでしょう.ライブラリを少し使ってみたいと思ったときは対話モードを使うとすぐに試すことができます.一応ですがPythonの対話モードがどのようなものであるかを次に示したいと思います.
$ python Python 3.7.1 (default, Dec 14 2018, 13:28:58) [Clang 4.0.1 (tags/RELEASE_401/final)] :: Anaconda, Inc. on darwin Type "help", "copyright", "credits" or "license" for more information. >>>
pythonがインストールされている状態で,コマンドラインでpython
と打つと上記のようになります.コマンドプロンプトが>>>
として表示されており,その状態でpythonの命令文を打つとその処理が実行されます.
対話モードでは,しばしばディレクトリの移動やカレントディレクトリにあるファイルを表示したいことがあります.しかし,いつも使っているUNIXのls
コマンドやcd
コマンドは使えません.ディレクトリ移動やファイル表示をするにはosモジュールにあるメソッド使う必要があります.カレントディレクトリの表示,ディレクトリ移動,ファイル表示をosモジュールを使って実行した例を次に示します.
>>> import os >>> os.getcwd() /home/xxx/works/python >>> os.chdir(‘projects’) >>> os.getcwd() /home/xxx/works/python/projects >>> os.listdir() [‘hello.py’, ‘test.py’]
unixコマンドとpythonのosモジュールの対応表を次にまとめます.
UNIXコマンド | osモジュール | |
---|---|---|
カレントディレクトリ の表示 |
pwd | os.getcwd() |
ディレクトリ移動 | cd | os.chdir() |
ファイル表示 | ls | os.listdir() |
これだけでも覚えていただけるとPythonの対話モードがかなり使いやすくなると思います.
スクリプト内で使うosモジュールのメソッド
Pythonのスクリプト内でファイルやディレクトリを一斉に消去したり,ファイル名を取得したりする際にosモジュールを用いることがあります.ファイルやディレクトリのコピーや移動に関してはshutilモジュールで操作することができますが,これに関しては別の記事で書こうと思います.
先ほど紹介したos.listdir()
がスクリプト内でも便利な場合があります.あるディレクトリ内のファイルをまとめて扱いたいときに利用できます.対話モードで実行例を少しお見せしました通り,os.listdir()
の返り値はリスト型です.このリストの中身をfor文で順番に取り出すことでディレクトリにあるファイルをまとめて扱うことができます.例を次に示します.
import os if __name__ == ‘__main__’: files = os.listdir() for file in files: # 何らかの処理
終わりに
今回はosモジュールのファイル操作に関するメソッドについて説明しました.osモジュールはファイル操作だけでなくOSに関わる操作を色々できます.興味がある人は調べてみてください.
対話モードではカレントディレクトリは何か,カレントディレクトリにはどんなファイルがあるのかというようなことを調べる必要が生じることがあります.そこで先ほど説明したメソッドを使うことでそれらを調べることが可能になります.上記の3つだけでも覚えるとかなり便利になります.
スクリプト内ではos.listdir()
を使う場面について説明しました.ファイルを一斉に読み込んだりするときなどに使えます.スクリプト内でのファイル操作はshutilモジュールで可能となります.特定のファイルの移動やまとめてファイルをコピーする場面に使います.
プログラミング入門後にプログラミングを継続しないとどうなるか
プログラミング入門書を真面目に1ページも飛ばさずに読んでプログラミング言語について学んだ方はその時点ではそのプログラミング言語についての知識が頭に入った状態でしょう.しかし,数ヶ月そのプログラミング言語を使って何かアプリケーションを作るなどをしなければ驚くほどそのプログラミング言語に関する知識が頭から消え去っています.今回は自分の体験を交えてプログラミング言語入門書読後に何をすべきかについて書いていきたいと思います.
入門書読後にプログラミングを継続しなかった体験談
数年前にWebアプリケーションにある機能を追加する必要あったためJavaScriptを使えるようになる必要があり,入門書を読んで勉強しました.また,JavaScriptで広く用いられているフレームワークであるJQueryも少し勉強しました.JavaScriptの入門書をある程度読み,基本的な文法は頭に入った状態になりました.そして,Webアプリケーションの機能追加もなんとか完了しました.
その機能追加が完了して数ヶ月経った後,再びJavaScriptを使う機会が訪れました.それまでの間JavaScriptのプログラムは一切書きませんでした.さて,JavaScriptのプログラムを書こうとしたところ,使おうとしていたJQueryはおろかJavaScriptの文法すら思い出せませんでした.その結果JavaScriptの文法を調べながらプログラムを書く羽目になり,作業時間が大きく増加することになりました.
入門書読後の対処
上記のような事態を防ぐために考えられる対処法がいくつか考えられます.まず,定期的に何らかのプログラムを書くことです.本当に何でもいいと思います.誰かのまたは自分の役に立つようなアプリケーションを作ろうなどと考える必要はないと思います.
とはいえ,何でもいいから何も考えずに自由にプログラムを作れと言われてもなかなか何をしていいのかわからないと思います.かくいう私も自由にプログラムを書けと言われると手が止まってしまいます.プログラミングが三度の飯よりも好きならば何らかのプログラムを書いて面白いと思えるので色々なプログラムを書けるかもしれませんが,私は三度の飯よりも好きではないのでできません.
というわけでこのブログでは無理やり何か面白そうなライブラリを触ってみたり,小規模なアプリケーションを作ってみたりなどして,現時点で仕事などで使うことがないプログラミングを次の機会に向けて上達しながら継続するための方法を示していけたらと思っています.
もう1つ上記のように時間を無駄にしないためには,今後使わないと思われる言語は学習しないという手もアリだと思います.私はC++の入門書を読んでC++で必要となる知識を蓄えましたが,その後C++を全く使う機会がなかったためほとんどの知識が頭から消え去りました.なせC++を学んだかというと単なる好奇心でC++がどんな言語か知りたかったためであり,重要な事柄を学ぶことができて一部の知識はまだ頭に残っています.使う機会がないと思われる言語を学ぶことは全く無駄なことではありませんが,時間が無駄になことを嫌う人は避けるべきです.
終わりに
今回は自分の体験をもとに入門後に何もしないとどうなるかについて書きました.ここ最近は小中高生を中心にとにかくプログラミングを勉強しなければという風潮が高まっています.とりあえずとか世間の流れに乗るためにとかの動機で何かしらを勉強してもすぐに頭から抜け落ちます.それでも勉強したことは無駄ではなく,なんとかして頭に残しておいて何かに活用できないかと多くの人が考えると思います.せっかく勉強したことが頭から抜け落ちると私の失敗にあるように多くの時間を無駄にしてしまいます.ということで,皆さんもプログラミング入門後はとにかく手を動かし続けることを心がけてください.このブログでも皆さんが手を動かし続けられるようにするために何か役に立ちそうなことを書いていきたいと思います.
Pythonのリスト内包表記を使いこなそう
Pythonではプログラムの行数を削減する様々な工夫がなされています.その中の1つにリスト内包表記があります.リスト内包表記とはfor文を使ってリストに任意の複数の値を入れる処理をfor文を使わずに[]で囲った書き方のことを言います.リスト内包表記を使うことでプログラムの行数の削減と処理の高速化が可能となります.
Pythonのリスト内包表記
Pythonのリスト内包表記の一例を次に示します.
list1 = [] for i in range(10): list1.append(i) # 上のfor文と下のリスト内包表記は同じ処理 list1_comp = [ i for i in range(10) ]
この通り,for文を使って書く場合に3行を要した物をリスト内包表記を使えば1行で書くことができます.if文を含むfor文とif-else文を含むfor文のリスト内包表記は次のようになります.
# if文を含むfor文 list2 = [] for i in range(10): if i % 3 == 0: list2.append(i) list2_comp = [ i for i in range(10) if i % 3 == 0 ] #if-else文を含むfor文 list3 = [] for i in range(10): if i % 2 == 0: list3.append(i) else: list3.append(-i) list3_comp = [ i if i % 2 == 0 else -i for i in range(10) ]
注意していただきたいのはif分だけならfor文の後ろに書くのですが,if-else文ではfor文の前に書く必要があります.さらにif-else文はかなり複雑で,どのような内容の処理であるかわかりづらくなっています.
リスト内包表記の欠点
リスト内包表記は行数を少なくして処理速度を上げるかなり便利な物です.しかし,上記にある例より少し複雑な処理になると格段に複雑さが増し,処理を理解するのにかなり時間がかかるようになります.for文の内部にif-else if-else文を含むリスト内包表記を次に示します.
list3 = [] for i in range(100): if i % 2 == 0: list3.append(i) elif i % 3 == 0: list3.append(i * 0.1) else list3.append(-i) list3_comp = [ i if i%2 == 0 else i*0.1 if i%3 == 0 else -i for i in range(100) ]
ご覧の通りぱっと見では全く理解できません.8行のコードを1行でまとめられたのは圧巻ですが,プログラムの理解度が著しく低下した代償はかなり大きいです.
このように,無闇にリスト内包表記を使うのは危険です.リスト内包表記は各自利用制限を設けるべきだと思います.例えば,if-else文よりも複雑な条件分岐構造を含むfor文はリスト内包表記にしないようにするという決まりを自分の中で決めておけばいいと思います.
終わりに
リスト内包表記は便利なものですが,プログラムの理解度を低下させるという欠点を持っています.その欠点に関しては自分の中で利用制限を設けると軽減できると考えております.単純なリスト内包表記は適度に使えばプログラムの行数を減らしつつプログラムの理解度が向上します.単純なリスト内包表記は積極的に使いましょう.
Javaの例外処理をちょっと使ってみる
Javaなどの高級言語では例外処理という機能を使うことができます.例外処理とはプログラムで何らかの障害や不正などの例外が発生したことをプログラマやユーザに知らせ,例外に対して任意の処理をする機能です.入門書ではゼロ除算の例外処理を見たことがあるかもしれません.しかし,入門書に載っている例だけではこれから例外処理をどう扱っていけばいいかわからないと思います.今回はどんな時に例外処理を使うと効果的かを書いていきたいと思います.
Javaでの例外処理
Javaでの例外処理は次のようになります.
try { // 例外が発生する可能性がある処理 } catch (〇〇Exception e) { // 例外後の処理 }
try
の内部で例外が発生するとcatch
に記述された処理が実行されます.よく用いられるのはe.printStackTrace()
です.どの部分で例外が発生したのかを示すスタックトレースが出力されます.
入門したてでは例外処理の使いどころがわかりませんが,用いるライブラリによっては強制的に例外処理を記述する必要がある場合があります.その中の1つとしてあげられるのは入出力のライブラリです.ファイルの読み込みなどの処理を書く際に例外処理を書かなかった場合,「例外IOExceptionは報告されません。スローするには、捕捉または宣言する必要があります」というメッセージが出てコンパイルエラーが発生します. ファイル入出力を書く際には次のように例外処理を書く必要があります.
import java.io.*; public class FileTrial { public static void main(String args[]) { try { FileReader fr = new FileReader("../index.html"); FileWriter fw = new FileWriter("copy.html"); int i; while ((i = fr.read()) != -1) { fw.write((char) i); } fr.close(); fw.close(); } catch (IOException e) { System.out.println("Exception: " + e); } } }
例外処理を書く色々な場面
それでは,どのような場面で例外処理を書くのかをいくつかあげていきたいと思います.あらゆるライブラリを使う時にどのような例外が発生するかは,そのライブラリのリファレンスマニュアルに載っている「〇〇Exception」を見ればわかります.そして,その例外が発生しそうな処理を書く場合にtry catch文のcatchの所で適した「〇〇Exception」を使うようにすれば,例外が発生した際に例外処理を実行できるようになります.
先ほど例に挙げた入出力のライブラリである「java.io」を使う際にはIOExceptionを使います.「java.util」にあるStackクラスを使う際にはEmptyStackExceptionを使います.
各ライブラリ固有の例外ではなくてもJavaでは標準パッケージに含まれる例外を使うことができます.配列を使う際に範囲外の領域を読み込もうとする例外が発生した際にはArrayIndexOutOfBoundsExceptionを使います.この例外に対する例外処理の例を以下に示します.
public class ArrayBounds { public static void main(String[] args) { try { int nums[] = new int[3]; nums[4] = 9; } catch (ArrayIndexOutOfBoundsException e) { e.printStackTrace(); } } }
配列numsの範囲外の領域であるnums[4]
に9を代入しようとしています.このプログラムをコンパイルするとエラーは出ませんが,実行するとe.printStackTrace()
が実行されて,以下のように実行結果が出力されてどこで例外が発生しているかわかります.
java.lang.ArrayIndexOutOfBoundsException: 4 at ArrayBounds.main(ArrayBounds.java:5)
終わりに
例外処理は作るアプリケーションの規模が大きくなるほど気をつけてこまめに書く必要があります.入門してすぐでは小規模なアプリケーションしか作れず,どのような部分で例外処理を書くべきか分からずに例外処理を書かないというケースが結構あるのではないかと思います.何らかのライブラリを使う時はどのような例外が発生するかを調べて,入門してすぐの状態でも例外処理を書く癖をつければ,作るアプリケーションの規模が大きくなった時に対応しやすくなると思います.
とはいえ,やはりはじめのうちは例外処理を書く癖がついていないので書き忘れると思います.書く癖がついていないうちはとことん例外処理を書かずにアプリケーションを作り(仕事など以外で責任が問われない場合に限ります),例外処理がどういったところで必要になるのかを身をもって体験するのもアリだと思います.
Javaでマルチスレッドを使ってみる
今回はJavaでマルチスレッドをどのように扱うのかについて説明していきたいと思います.マルチスレッドとは複数あるスレッドを同時に実行する技術のことを言います.マルチスレッドではない処理はプログラムに書かれた処理を上から下まで順番に1つのプロセスとして実行されます.一方マルチスレッドではある処理を1つのスレッドとして同時に複数のそのスレッドを実行します.こうすることで計算機のコストを消費しますが,処理速度はかなり速くなります.また,小規模な処理で少数のスレッド数の同時実行あれば計算機コストをあまり消費せずに効率的に処理を高速化できます.
マルチスレッドの使用方法
Javaでマルチスレッドを使用する一例を次に示したいと思います.
class ThreadA extends Thread { private String threadName; public ThreadA(String tName) { this.threadName = tName; } @Override public void run() { try { System.out.println("Thread: " + this.threadName); Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } } public class MultiThreadSample { public static void main(String[] args) { ThreadA t1 = new ThreadA("thread1"); ThreadA t2 = new ThreadA("thread2"); t1.start(); t2.start(); } }
Javaでマルチスレッドを使用するにはまず,マルチスレッドを使おうとしているクラスでJavaの標準クラスであるThreadを継承します.そしてスレッドに分けたい処理をThreadクラスで定義されているrunメソッドをオーバーライドしてその中に書きます.
スレッドを実行するにはThreadクラスを継承したクラスのインスタンスを生成して,Threadクラスで定義されているstartメソッドを実行することで実行できます.
上の例ではThreadAクラスのインスタンスを2つ生成してそれぞれのインスタンスでstartメソッドを実行しています.実行すると以下のような結果になると思います.
$ java MultiMethodSample Thread: thread1 Thread: thread2
注目すべきなのは実行の際の挙動です.2行の文字列が同時に画面に表示された後1秒経って実行が終了します.t1.start()が終了するのを待たずにt2.start()が実行されて,2つの処理が同時に実行されることがわかります.
for文のなかでスレッドを使う
上記の方法では2つのスレッドしか集まっていませんでしたが,もっと多くのスレッドを同時に実行するにはfor文などの反復文のなかでスレッドのインスタンスを生成して実行することで可能となります.以下に例を示します.
import java.util.Random; class ThreadB extends Thread { private String threadName; private int sleepTime; public ThreadB(String tName, int sTime) { this.threadName = tName; this.sleepTime = sTime; } @Override public void run() { try { System.out.println("Thread: " + this.threadName); sleep(this.sleepTime * 1000); System.out.println(this.threadName + " has finished!"); } catch (InterruptedException e) { e.printStackTrace(); } } } public class MultiThreadSample2 { public static void main(String[] args) { Random r = new Random(); for (int i = 0; i < 10; i++) { int time = r.nextInt(7); ThreadB th = new ThreadB("thread" + i, time); th.start(); } } }
Threadクラスを継承したThreadBクラスのコンストラクタにスレッド名と乱数の整数を入れるようにしていて,runメソッド内で無作為な時間停止するようにしています.実行結果は以下のようになります.
$ java MultiThreadSample2 Thread: thread0 Thread: thread3 Thread: thread2 Thread: thread1 Thread: thread4 thread3 has finished! Thread: thread5 thread1 has finished! thread2 has finished! ...
10個のスレッドが同時に実行され,それぞれのスレッドのインスタンスが生成される際に格納された秒数経過した後「〇〇 has finished!」と表示されるようにしています.上記の例では同時に実行するスレッドの数の制限をしていませんが工夫次第でスレッド数を制限できるようになると思います.
終わりに
今回はJavaでマルチスレッドを使用する方法について説明しました.プログラミングを始めたばかりの状態ではマルチスレッドを使用することはないと思いますが,処理の効率化などにかなり重要になってきます.まだある程度の大きさの規模のアプリケーションを作っていない場合は私もですが,どのような場面でマルチスレッドを使うのか想像できないというのがマルチスレッドの難しさだと思います.また,マルチスレッドのスレッド数が大きくなったり制御が複雑になったりするとどのような挙動でスレッドが処理されているのか非常に困難になります.
何かと難しいマルチスレッドですが,色々なプログラムを書いていくなかで使って慣れていくしかなさそうです.このブログでもマルチスレッドを使ったプログラムを公開してどんな感じで使うのかを書いていけたらと思います.