広告

■□■□■□■□■□■□■□■□■□■□■□■□■□■□■
                      2011年11月28日

   Java総合講座 - 初心者から達人へのパスポート
                 2009年11月開講コース 046号

                                セルゲイ・ランダウ
 バックナンバー: http://www.flsi.co.jp/Java_text/
■□■□■□■□■□■□■□■□■□■□■□■□■□■□■


-------------------------------------------------------
・現在、このメールマガジンは以下の2部構成になっています。
[1] 当初からのコース:vol.xxx(xxxは番号)が振られています。
   これは現在、中級レベルになっています。
[2] 2009年11月開講コース:xxx号(xxxは番号)が振られています。
   これは現在、初心者向けのレベルになっています。
・このメールマガジンは、画面を最大化して見てください。
小さな画面で見ていると、不適切な位置で行が切れてしまう
など、問題を起すことがあります。
・このメールマガジンに掲載されているソース・コード及び
文章は特に断らない限り、すべて筆者が著作権を所有してい
ます。また、これらのソース・コードは学習用のためだけに
提供しているものです。
-------------------------------------------------------


========================================================
◆ 01.データベースを使用するアプリケーションの開発(続き)
========================================================


前回貼り付けた「検索」ボタンをクリックしたら、各テキスト・
フィールドに入力されている値を使ってEMPLOYEEテーブルを検索し、
該当するデータを取り出して表形式で画面上に表示するようにプロ
グラミングすることにします。
このとき、表形式でデータを表示するために使うGUI部品にJTableと
いうのがあります。

036号でSwingのUIオブジェクトとモデル・オブジェクトの分離に
ついてお話し、「JComboBoxに対応したモデル・オブジェクトは
ComboBoxModelというインターフェイスを実装するというルールに
なっている」というお話をしましたが、同様にJTableに対するモデル・
オブジェクトはTableModelというインターフェイスを実装するという
ルールになっています。

つまり、JTableに表示するデータを入れるクラスはTableModelという
インターフェイスを実装する必要があります。
これをすべて自分で実装するのはちょっと面倒ですが、さいわいにも
このTableModelをある程度実装したAbstractTableModelというクラス
がありますので、通常はこれを利用します。

AbstractTableModelは抽象クラスであり、そのままではインスタンス
を生成することができません。
また、このクラスではTableModelのメソッドのうち

 public int getRowCount()
 public int getColumnCount()
 public Object getValueAt(int row, int column)

という3つのメソッドが実装されていないため、サブクラスを作って
その中で実装してやる必要があります。

上記の3つのメソッドのうち、getRowCount()メソッドは行数を返す
メソッドで、getColumnCount()は列数を返すメソッド、また
getValueAt()は引数に指定した行(row)の番号と列(column)の番号
に対するデータを返すメソッドです。

したがって、「検索」ボタンがクリックされたら、データベースの
EMPLOYEEテーブルからSELECTのSQL文でデータを取り出し、その行数
や列数や各データを上記のメソッドで返すようにプログラミングして
やれば、JTableにデータを表示できるようになります。

また、必須ではありませんが、実装しておいたほうがいいメソッドとして

public String getColumnName(int column)

があります。これは、column番目の列(カラム)の名前を返すメソッド
で、この名前はJTableの表の上部に表示されます。



ところで、検索するときのキーワードには年齢も含めましたが、年齢に
関する記述はEmployeeクラスにはありませんでした。
そこで、これからそれをEmployeeクラスに追加することにしましょう。

Eclipseでjp.co.flsi.lecture.entityパッケージのEmployeeのエディ
ターを開いて、次のメソッドを追加してください。

--------------------------------------------------------
public int getAge() {
   GregorianCalendar today = new GregorianCalendar();
   if (today.get(Calendar.MONTH) > dateOfBirth.get(Calendar.MONTH))
          return today.get(Calendar.YEAR) - dateOfBirth.get(Calendar.YEAR);
   if (today.get(Calendar.MONTH) == dateOfBirth.get(Calendar.MONTH)) {
      if (today.get(Calendar.DAY_OF_MONTH) >= dateOfBirth.get(Calendar.DAY_OF_MONTH))
             return today.get(Calendar.YEAR) - dateOfBirth.get(Calendar.YEAR);
   }
   return today.get(Calendar.YEAR) - dateOfBirth.get(Calendar.YEAR) - 1;
}
--------------------------------------------------------
(注: Calendarはjava.utilパッケージのクラスです。)

このメソッドは生年月日と現在(today)の日付を比較して現在の年齢を
計算するものですね。
このメソッドは、以前Humanというクラスを作ったときに年齢を答えるメ
ソッドとして用意したもの(007号参照)とまったく同じものですから、
それをそのままコピーしてくれば済みます。

なお、生年月日が現在の日付よりも未来になっている場合(普通はあり
得ないことだが)はどうするか、といった細かい話は省略していますが、
こだわりたい人は自分で考えてコーディングして下さい。


次に、検索結果を画面に表示するときに各社員の所属部署の名前(部門名)
も表示したいので、Employeeクラスに部門名を表す属性も追加することに
しましょう。
(通常、所属部署の部門名や所属部署コード(部門コード)はDepartmentク
ラスが持つ属性であり、本来ならばEmployeeクラスの属性にすべきでは
ありません。EmployeeオブジェクトはDepartmentオブジェクトとの「関連」
を持っているだけにすべきなのです。
でも、ここではプログラミングを簡単にするためにEmployeeクラスにも
所属部署の名前(部門名)を持たせておくことにします。
「関連」についてはずっと後でお話します。)

では、Employeeのエディターで

--------------------------------------------------------
private int departmentNumber;
--------------------------------------------------------

の行の下に下記のフィールド(変数)定義を追加しましょう。

--------------------------------------------------------
private String departmentName;
--------------------------------------------------------

このフィールドに対するsetメソッド(setter)、getメソッド(getter)を
追加しましょう。
007号のように「リファクタリング」の「フィールドのカプセル化」の
機能を使えば簡単に追加できます。
追加されるsetメソッド、getメソッドを下記に提示しておきます。

--------------------------------------------------------
public void setDepartmentName(String departmentName) {
   this.departmentName = departmentName;
}
public String getDepartmentName() {
   return departmentName;
}
--------------------------------------------------------



ではEmployeeクラスはこのまま保管し、続いて、EMPLOYEEテーブルか
らデータを取り出す部分のプログラミングを始めましょう。


まず、EMPLOYEEテーブルからデータを取り出すSQL文を組み立てて
おきましょう。

まず、EMPLOYEEテーブルから全行の全カラムのデータを取り出し、
同時にDEPARTMENTテーブルから(各社員が所属している部署の)
部門名も取り出すことにします。

--------------------------------------------------------
SELECT EMP_NUM, NAME, EMPLOYEE.DEPART_NUM, DATE_OF_BIRTH, DEPART_NAME
 FROM EMPLOYEE, DEPARTMENT WHERE EMPLOYEE.DEPART_NUM = DEPARTMENT.DEPART_NUM
--------------------------------------------------------

このSQL文とほとんど同様のものは027号で学習しましたので、説明
は不要ですね。

これを、例えば、氏名に「安部」という文字列を含む社員のデータを
取り出すようにSQL文を少し修正してみましょう。
020号で説明したように
NAME LIKE '%安部%'
という条件を追加すればいいことはわかりますね。たとえば、

--------------------------------------------------------
SELECT EMP_NUM, NAME, EMPLOYEE.DEPART_NUM, DATE_OF_BIRTH, DEPART_NAME
 FROM EMPLOYEE, DEPARTMENT WHERE NAME LIKE '%安部%' AND EMPLOYEE.DEPART_NUM = DEPARTMENT.DEPART_NUM
--------------------------------------------------------

というSQL文にすればいいですね。

では、これらのSQL文を利用し、(jp.co.flsi.lecture.dbパッケージの)
EmployeeDbManagerクラスに以下のようなフィールドとメソッドを追加しま
しょう。
EmployeeDbManagerのエディターを開いて入力してください。

--------------------------------------------------------
private String selectAllSql = "SELECT EMP_NUM, NAME, EMPLOYEE.DEPART_NUM," +
   " DATE_OF_BIRTH, DEPART_NAME FROM EMPLOYEE, DEPARTMENT" +
   " WHERE EMPLOYEE.DEPART_NUM = DEPARTMENT.DEPART_NUM";
private String selectNameSql = "SELECT EMP_NUM, NAME, EMPLOYEE.DEPART_NUM," +
   " DATE_OF_BIRTH, DEPART_NAME FROM EMPLOYEE, DEPARTMENT" +
   " WHERE NAME LIKE ? AND EMPLOYEE.DEPART_NUM = DEPARTMENT.DEPART_NUM";
private Vector<Employee> employeeList;

public Vector<Employee> getEmployeeList() {
   return employeeList;
}

public void getData(String name, int ageLowerLimit, int ageUpperLimit) {
   try {
      connect();
      Statement selectSql = conn.createStatement();
      PreparedStatement selectPs = conn.prepareStatement(selectNameSql);
      ResultSet rs;
      if (name == null) {
         rs = selectSql.executeQuery(selectAllSql);
      }
      else {
         selectPs.setString(1, '%' + name + '%' );
         rs = selectPs.executeQuery();
      }
      employeeList = new Vector<Employee>();
      while (rs.next()) {
         Employee anEmployee = new Employee();
         anEmployee.setEmployeeNumber(rs.getInt("EMP_NUM"));
         anEmployee.setName(rs.getString("NAME"));
         anEmployee.setDepartmentNumber(rs.getInt("DEPART_NUM"));
         anEmployee.setDepartmentName(rs.getString("DEPART_NAME"));
         GregorianCalendar gcDate = new GregorianCalendar();
         gcDate.setTimeInMillis(rs.getDate("DATE_OF_BIRTH").getTime());
         anEmployee.setDateOfBirth(gcDate);
         int empAge = anEmployee.getAge();
         if (ageLowerLimit < 0 || ageLowerLimit <= empAge) {
            if (ageUpperLimit < 0 || empAge <= ageUpperLimit) {
               getEmployeeList().addElement(anEmployee);
            }
         }
      }
      selectPs.close();
      selectSql.close();
      disconnect();
   }
   catch (SQLException exception) {
      exception.printStackTrace();
   }
}
--------------------------------------------------------
(注: Statementはjava.sqlパッケージのインターフェースです。)

ここで、変数selectAllSqlとselectNameSqlの定義が複数行にわたっている
のは、1行におさめようとするとあまりにも長くなり過ぎるから複数行に
分けただけで、特に意味はありません。

また、employeeListというVector型の変数を用意したのは、
DepartmentDbManagerにおいてdepartmentListというVector型の変数
を用意したのと同じ役割です。

なお、今回は(038号の補足欄で解説したように)総称型として<Employee>
を指定したことに注意して下さい。これによってJavaコンパイラーがVector
に入れるオブジェクトの型チェックを行ってくれます。
これに関して、DepartmentDbManagerとの違いを確認してみて下さい。
DepartmentDbManagerでは、raw型のVectorを使っているために、コンパイラー
が警告(黄色いマークが付いている)の表示をしています。
つまり、(コンパイラーがチェックできるように)<クラス名>(ここでは
<Department> )を指定すべきだ、と注意を促しているのです。


さて、このgetData(String name, int ageLowerLimit, int ageUpperLimit)
というメソッドは、引数に指定したnameがnullのときは、NAMEカラムの値
には関係なくすべての行をEMPLOYEEテーブルから取り出し、nameがnullで
ないときはnameの値がNAMEカラムに含まれるような行だけをEMPLOYEEテー
ブルから取り出します。
また、引数に指定したageLowerLimitが負の値でないときは年齢が
ageLowerLimit以上の社員のデータだけに絞込み、ageUpperLimitが
負の値でないときは年齢がageUpperLimit以下の社員のデータだけに
絞込むものとします。

上記のソース・コードの中の
--------------------------------------------------------
gcDate.setTimeInMillis(rs.getDate("DATE_OF_BIRTH").getTime());
--------------------------------------------------------
という行の中のgetDate("DATE_OF_BIRTH")は、ResultSetオブジェクトの
中から引数に指定されたカラムの日付データを取り出すメソッドで
java.sql.Date型のオブジェクトを返します。
これをEmployeeオブジェクトの生年月日の属性にセットするためには
GregorianCalendar型に変換する必要がありますので、いったんgetTime()
というメソッドでlong型のデータに変換してからsetTimeInMillis()メソッ
ドを使ってgcDateオブジェクト(GregorianCalendar型)にセットしてい
ます。
そしてその次の行でgcDateをEmployeeオブジェクトの生年月日の属性に
セットしています。


残りの部分はもう説明しなくても理解できるでしょう。

EmployeeDbManagerの編集が終わったらそのまま保管してください。


では、いよいよAbstractTableModelのサブクラスを作ることにしま
しょう。
その名もずばりTableModelOfEmployeeにしましょう。


┌───────────────────────────┐
     TableModelOfEmployeeクラスの作成
└───────────────────────────┘

(1) パッケージ・エクスプローラーにおいてjp.co.flsi.lecture.ui.model
(パッケージ)を右クリックし、「新規」→「クラス」を選択します。

(2) 「新規Javaクラス」ウインドウで「ソース・フォルダー」の欄に
「JStudy1/src」がはいっていることを確認し、「パッケージ」の欄に
「jp.co.flsi.lecture.ui.model」がはいっていることを確認し、
「名前」の欄に

TableModelOfEmployee

と入力し、
「スーパークラス」の右側の「参照」ボタンをクリックします。

(3) 「スーパークラスの選択」ウインドウの入力欄に

AbstractTableModel

を入力するとその下の「一致する項目」の欄に「AbstractTableModel - javax.swing.table」
というのが表示されるのでそれを選択(クリック)し、「OK」ボタンを
クリックします。
これで、「スーパークラス」の欄に「javax.swing.table.AbstractTableModel」
が入力されたことを確認してください。

(5) 「作成するメソッド・スタブの選択」の欄は「継承された
抽象メソッド」だけにチェックマークがはいっている状態にして
「完了」ボタンをクリックします。

そうすると、TableModelOfEmployeeのエディターが開いて
下記のようなソース・コードが表示されますね。

--------------------------------------------------------
package jp.co.flsi.lecture.ui.model;

import javax.swing.table.AbstractTableModel;

public class TableModelOfEmployee extends AbstractTableModel {

   @Override
   public int getColumnCount() {
      // TODO 自動生成されたメソッド・スタブ
      return 0;
   }

   @Override
   public int getRowCount() {
      // TODO 自動生成されたメソッド・スタブ
      return 0;
   }

   @Override
   public Object getValueAt(int rowIndex, int columnIndex) {
      // TODO 自動生成されたメソッド・スタブ
      return null;
   }

}
--------------------------------------------------------

これを下記のように編集しましょう。

--------------------------------------------------------
package jp.co.flsi.lecture.ui.model;

import javax.swing.table.AbstractTableModel;
import java.util.Vector;
import jp.co.flsi.lecture.entity.Employee;

public class TableModelOfEmployee extends AbstractTableModel {

   private Vector<Employee> employeeList = new Vector<Employee>();

   public void setEmployeeList(Vector<Employee> aEmployeeList) {
      employeeList = aEmployeeList;
   }

   public Vector<Employee> getEmployeeList() {
      return employeeList;
   }


   public int getColumnCount() {
      return 6;
   }

   public int getRowCount() {
      return getEmployeeList().size();
   }

   public Object getValueAt(int rowIndex, int columnIndex) {
      Employee employee = (Employee)getEmployeeList().elementAt(rowIndex);
      switch (columnIndex) {
      case 0:
         return Integer.toString(employee.getEmployeeNumber());
      case 1:
         return employee.getName();
      case 2:
         return Integer.toString(employee.getAge());
      case 3:
         return employee.getDateOfBirth().toString();
      case 4:
         return Integer.toString(employee.getDepartmentNumber());
      case 5:
         return employee.getDepartmentName();
      }
      return null;
   }

   public String getColumnName(int columnIndex) {
      switch (columnIndex) {
      case 0:
         return "社員番号";
      case 1:
         return "氏名";
      case 2:
         return "年齢";
      case 3:
         return "生年月日";
      case 4:
         return "部門コード";
      case 5:
         return "部門名";
      }
      return "";
   }

}
--------------------------------------------------------

編集が終わったら保管しておいてください。

このソース・コードも説明しなくても理解できることと思います。


もし、わからないところがありましたら、質問をお寄せください。

では、今回はここまで。


(続く)


では、また来週。



========================================================
◆ 02.文法解説 [演算子]
========================================================

[論理演算子& | ^]
論理演算子の & はAND(かつ)を表し、| はOR(または)を表し
ます。
これらは以前説明した && と || によく似ていますが、少し違い
ます。

どこが違うかというと、&&や||の場合は左辺のオペランドの判定
だけで済むときは右辺は処理しないということです。
つまり&&の左辺の値が偽のときは右辺は処理されず、||の左辺の
値が真のときは右辺は処理されません。

具体的には、たとえば

((l = 10) == 100) && ((m = 100) == 100)

という演算をした場合、左辺の((l = 10) == 100)の値は偽(false)
なので、&&で演算した結果も、右辺の((m = 100) == 100)の値の
いかんによらず偽に決まっています。
この場合は右辺の((m = 100) == 100)は処理されませんので、mに
100が代入されることはありません。

また、たとえば

((l = 10) == 10) || ((m = 100) == 100)

という演算をした場合、左辺の((l = 10) == 10)の値は真(true)
なので、||で演算した結果も、右辺の((m = 100) == 100)の値の
いかんによらず真に決まっています。
この場合も右辺の((m = 100) == 100)は処理されませんので、mに
100が代入されることはありません。

一方、&や|の演算子では、いかなる場合にも右辺も必ず処理され
ます。

通常は、&&や||を使ったほうが(余計な処理をしなくて済みます
ので)省力になり、好まれます。
一方、左辺と右辺の両方ともを必ず処理して欲しい場合には&や|
を使います。


論理演算子には他に^があり、これは排他的OR(Exclusive OR)を
表します。

排他的ORというのは左辺と右辺のいずれか一方のみが真のときに
は演算結果も真で、左辺と右辺の両方ともに偽のときや両方とも
に真のときは演算結果が偽になるというものです。
ORに似ていますが左辺と右辺の両方ともに真のときにも偽になっ
てしまうという点で異なっています。

^の場合ももちろん、いかなる場合にも左辺と右辺の両方が必ず
処理されます。


なお、&や|や^はビット演算の演算子としても使われますので、
また次回説明いたします。


(続く)



================================================
◆ 03.演習問題
================================================

上記の演算子の振る舞いを確認するために、下記のようなプログ
ラムを作って実行してみてください。

--------------------------------------------------------
public class BooleanOperationTest {

   public static void main(String[] args) {
      System.out.println("\n[Example 1]");
      int l = 0;
      int m = 0;
      System.out.println("int l = 0;");
      System.out.println("int m = 0;");
      boolean n = (l = 10) == 100 && (m = 100) == 100;
      System.out.println("n = (l = 10) == 100 && (m = 100) == 100;");
      System.out.println("Result:  l = " + l);
      System.out.println("Result:  m = " + m);
      System.out.println("Result:  n = " + n);
      l = 0;
      m = 0;
      System.out.println("int l = 0;");
      System.out.println("int m = 0;");
      n = (l = 10) == 100 & (m = 100) == 100;
      System.out.println("n = (l = 10) == 100 & (m = 100) == 100;");
      System.out.println("Result:  l = " + l);
      System.out.println("Result:  m = " + m);
      System.out.println("Result:  n = " + n);
      l = 0;
      m = 0;
      System.out.println("int l = 0;");
      System.out.println("int m = 0;");
      n = (l = 10) == 10 || (m = 100) == 100;
      System.out.println("n = (l = 10) == 10 || (m = 100) == 100;");
      System.out.println("Result:  l = " + l);
      System.out.println("Result:  m = " + m);
      System.out.println("Result:  n = " + n);
      l = 0;
      m = 0;
      System.out.println("int l = 0;");
      System.out.println("int m = 0;");
      n = (l = 10) == 10 | (m = 100) == 100;
      System.out.println("n = (l = 10) == 10 | (m = 100) == 100;");
      System.out.println("Result:  l = " + l);
      System.out.println("Result:  m = " + m);
      System.out.println("Result:  n = " + n);
      l = 0;
      m = 0;
      System.out.println("int l = 0;");
      System.out.println("int m = 0;");
      n = (l = 10) == 10 ^ (m = 100) == 100;
      System.out.println("n = (l = 10) == 10 ^ (m = 100) == 100;");
      System.out.println("Result:  l = " + l);
      System.out.println("Result:  m = " + m);
      System.out.println("Result:  n = " + n);
   }
}
--------------------------------------------------------



(次回に続く。)


では、また来週。



┏━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
★ホームページ:
      http://www.flsi.co.jp/Java_text/
★このメールマガジンは
     「まぐまぐ(http://www.mag2.com)」
 を利用して発行しています。
★バックナンバーは
      http://www.flsi.co.jp/Java_text/
 にあります。
★このメールマガジンの登録/解除は下記Webページでできます。
      http://www.mag2.com/m/0000193915.html
★このメールマガジンへの質問は下記Webページにて受け付けて
 います。わからない所がありましたら、どしどしと質問をお寄
 せください。
      http://www.flsi.co.jp/Java_text/
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━┛

Copyright (C) 2011 Future Lifestyle Inc. 不許無断複製