オブジェクトのシリアライズとJTable
データが複数の型のオブジェクトで構成されている場合(たとえば数値とテキストと時刻など)、それぞれをString型に変換して、テキストファイルとして保存するといった方法もありますが、面倒です。シリアライズを使えば、非常に簡単に複数の型で構成された複雑なデータでも、ファイルへの保存や読み出しが簡単に行えます。
ここでは、お金の入出金の記録データを想定したデータをシリアライズで保存してみます。また、そうした複数の型のデータをJTableに一覧表示するためのレンダラの使用方法のサンプルとしても作ってみます。
画面構成は次のようになります。初期データとして5件分がソースコード中に記述してあります。追加する場合は、画面下部のテキストボックスとコンボボックスで指定し、[追加]ボタンをクリックします。
[データ保存]と[データ読込]ボタンで、現在のデータをSerializeによってファイルに保存し、また保存されているファイルを読み込むことができます。なお、日付入力補助にはjava33.phpの日付入力用カレンダダイアログを使っていますので、必要に応じてライブラリとしてjarファイルをリンクするか、ソースコードを追加するなりしてください。
動作画面例

Calendar Input Dialog
Kom., 2014
プログラムはいつものようにNetBeansのGUIデザイナを使用していますので、ソースコードは手動入力する部分のみ示します。
使用しているコンポーネントは、
private javax.swing.JButton jButtonAdd;
private javax.swing.JButton jButtonLoad;
private javax.swing.JButton jButtonSave;
private javax.swing.JComboBox jComboBunrui;
private javax.swing.JComboBox jComboInOut;
private javax.swing.JLabel jLabel1;
private javax.swing.JLabel jLabel10;
private javax.swing.JLabel jLabel2;
private javax.swing.JLabel jLabel3;
private javax.swing.JLabel jLabel4;
private javax.swing.JLabel jLabel5;
private javax.swing.JLabel jLabel6;
private javax.swing.JScrollPane jScrollPane1;
private javax.swing.JTable jTable1;
private javax.swing.JTextField jTextContent;
private javax.swing.JTextField jTextDate;
private javax.swing.JTextField jTextPrice;
です。
上の図と比べ合わせてみれば、どのコンポーネントがどの名称かはすぐにわかると思います。
Netbeansを使用している場合は、各コンポーネントを配置後にデザイナ画面で名称を変更します。
日付入力用カレンダー表示ダイアログのソースを以下に示します。カレンダ計算用クラスを含んでいます。
import javax.swing.table.*;
import javax.swing.table.TableColumn;
import java.util.*;
import java.io.*;
import javax.swing.*;
import java.awt.*;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.text.*;
public class ObjSerialize1 extends javax.swing.JFrame {
//JComboBoxのjComboBox1と、JTableのjTable1を使用。
//NetBeansデザイナ画面のjTable1のプロパティのmodelに、ユーザコード→カスタムコードで、
//tableModelを設定することもできる。( jTable1.setModel(tableModel); )
//カスタムコードの実行は、コンストラクタから呼び出されるinitComponents();内で行われる。
//DefaultTableModel tableModel =
// new DefaultTableModel({"日付", "項目種別", "入/出","金額","内容"}, 0);
//のようにここでクラスフィールドとして定義することもできる。
DefaultTableModel tableModel = new DefaultTableModel(InOutData.columnNameArray, 0);
//以下のtabledataに初期表示用のデータを記述。
//データ本体は、InOutData[]型のdataArray[]を使用する。
String[][] tableData = {
//初期表示用データ。型変換した後、InOutDataクラスオブジェクトに格納して使用。
{"2013/06/12", "食費", "支出","1095", "弁当など"},
{"2013/06/3", "住居費", "支出","36000", "家賃"},
{"2013/06/16", "教養費", "支出","1200", "書籍「Java文法入門」"},
{"2012/11/2", "食費", "支出","580", "学食"},
{"2010/5/2", "アルバイト", "収入","2800", "コンビニ"}
};
//ArrayList型にInOutDataクラス(データ本体)の配列を作成する。
ArrayList < InOutData> aryListInOut= new ArrayList < InOutData>();
public ObjSerialize1() {
initComponents();
DefaultTableColumnModel cmodel=(DefaultTableColumnModel)jTable1.getColumnModel();
//コラム幅の調整。はじめの4つは幅を指定し、5つ目はテーブルの残り幅全部を使う。
int tableWidth,wkWidth=0;
int[] widthArray={0,0,0,0,0};
widthArray[0]=80;
widthArray[1]=100;
widthArray[2]=50;
widthArray[3]=70;
jTable1.setAutoResizeMode(JTable.AUTO_RESIZE_OFF);
for(int n=0; n < 4; n++){
cmodel.getColumn(n).setPreferredWidth(widthArray[n]);//列幅を指定
wkWidth += widthArray[n];
}
tableWidth=jTable1.getWidth();
widthArray[4]=tableWidth-wkWidth;
cmodel.getColumn(4).setPreferredWidth(widthArray[4]);
//Rendererの設定
TableColumn columnwk=cmodel.getColumn(0);//日付列
columnwk.setCellRenderer(new DateRenderer());
columnwk=cmodel.getColumn(2);//支出収入列
columnwk.setCellRenderer(new InOutRenderer());
columnwk=cmodel.getColumn(3);//金額列
columnwk.setCellRenderer(new MoneyRenderer());
int lnh= InOutData.himokuTable.length;
String[] tableColName = new String[lnh+1];
tableColName[0]="全項目";
for(int i=0;i < lnh;i++){
tableColName[i+1]=InOutData.himokuTable[i];
}
jComboBunrui.setModel(new javax.swing.DefaultComboBoxModel(
InOutData.himokuTable));
//ArrayList aryListInOutにString配列 tabledataのデータをコピー
for(int i = 0 ; i < tableData.length ; i++){
InOutData wkInOut= new InOutData();
wkInOut.putAryStr(tableData[i]);
aryListInOut.add(wkInOut);
}
dispList(); //Table表示更新
}
//Tableに表示するCalendarクラスデータのためのRenderer。選択状態のときは
//デフォルトの選択状態時の色にする。
class DateRenderer extends DefaultTableCellRenderer{
public Component getTableCellRendererComponent(
JTable table, Object value, boolean isSelected, boolean hasFocus,int row, int column
)
{
Calendar cal;
cal=(Calendar)value;
SimpleDateFormat sdfm = new SimpleDateFormat("yyyy/MM/dd");
if (isSelected) {
setForeground(table.getSelectionForeground());
super.setBackground(table.getSelectionBackground());
} else {
setForeground(table.getForeground());
setBackground(table.getBackground());
}
setText(sdfm.format(cal.getTime()));
return (this);
}
}
//金額表示用Renderer。選択状態のときはデフォルトの選択状態時の色にする。
class MoneyRenderer extends DefaultTableCellRenderer{
public Component getTableCellRendererComponent(
JTable table, Object value, boolean isSelected, boolean hasFocus,int row, int column
)
{
if (isSelected) {
setForeground(table.getSelectionForeground());
super.setBackground(table.getSelectionBackground());
} else {
setForeground(table.getForeground());
setBackground(table.getBackground());
}
setText(String.format("¥%1$,3d", (int)value));
setHorizontalAlignment(JLabel.RIGHT);
return (this);
}
}
// 収入、支出をテーブルに表示するためのRenderer。データの値が-1であれば、「支出」と表示、
//そうでなければ「収入」と表示。また、背景色もピンクかシアンに設定。選択状態のときは
//デフォルトの選択状態時の色にする。
class InOutRenderer extends DefaultTableCellRenderer{
public Component getTableCellRendererComponent(
JTable table, Object value, boolean isSelected, boolean hasFocus,
int row, int column)
{
if (isSelected) {
setForeground(table.getSelectionForeground());
super.setBackground(table.getSelectionBackground());
} else {
setBackground((int)value == -1 ? Color.pink: Color.cyan );
setForeground(table.getForeground());
}
setText((int)value == -1 ? "支出": "収入");
setHorizontalAlignment(JLabel.CENTER);
return (this);
}
}
// 並べ替えを行うために必要なComparatorクラス。InOutDataクラスのArrayListを並べ替えるときに
//使用する。
private class ArrayComparator implements java.util.Comparator {
//ArrayListを日付順に並べ替えるのためのArrayComparator。
public int compare( Object a, Object b ) {
//第1引数の方が大きければ1を返し、小さければ-1を返す
int retval=0;
InOutData d1 = (InOutData) a;
InOutData d2 = (InOutData) b;
//カレンダーの日付で比較。
retval=d1.comparewith(d2);
return retval;
}
}
private void dispList(){
//JTableの表示更新処理
//aryListInOutのソートを行っておく
ArrayComparator aComp = new ArrayComparator();
Collections.sort(aryListInOut, aComp );//aryListInOutの並べ替えを実行
//次にtableModelをクリアすることで、Tableの表示データをいったん全て削除。
tableModel.setRowCount(0);
for(int i = 0 ; i < aryListInOut.size() ; i++){
tableModel.addRow(aryListInOut.get(i).getArray());
}
}
private void jButtonSaveActionPerformed(java.awt.event.ActionEvent evt) { String FS = File.separator;
File file=null;
JFileChooser filechooser = new JFileChooser("c:"+FS);//デフォルトはC:\
int selected = filechooser.showSaveDialog(this);
if (selected == JFileChooser.APPROVE_OPTION){
file = filechooser.getSelectedFile();
System.out.println(file.getAbsolutePath());
saveArrayList(file.getAbsolutePath());
}else if (selected == JFileChooser.CANCEL_OPTION){
//Cansel
}else if (selected == JFileChooser.ERROR_OPTION){
//Error
}
}
private void jButtonLoadActionPerformed(java.awt.event.ActionEvent evt) {
//String fpath="";
File file=null;
String FS = File.separator;
JFileChooser filechooser = new JFileChooser("c:"+FS);
int selected = filechooser.showOpenDialog(this);
if (selected == JFileChooser.APPROVE_OPTION){
file = filechooser.getSelectedFile();
loadArrayList(file.getAbsolutePath());
}else if (selected == JFileChooser.CANCEL_OPTION){
//Cansel
}else if (selected == JFileChooser.ERROR_OPTION){
//Error
}
}
private void jButtonAddActionPerformed(java.awt.event.ActionEvent evt) {
//データ追加。日付、分類、支出/収入、金額、内容をすべてString型でInOutData型の
//putAryStr()に渡し、型変換を行ってaryListInOutに追加。その後、Tableへの表示更新。
InOutData wkInOut= new InOutData();
wkInOut.putAryStr(new String[]{
jTextDate.getText(),
(String)jComboBunrui.getSelectedItem(),
(String)jComboInOut.getSelectedItem(),
jTextPrice.getText(),
jTextContent.getText(),
} );
aryListInOut.add(wkInOut);
dispList();
jTextDate.setText("");
jTextPrice.setText("");
jTextContent.setText("");
}
private void saveArrayList(String fpath) {
try {
//オブジェクトデータ(クラスオブジェクト配列のデータなど)の保存は簡単
//まずObjectOutputStreamを用意し、
//(ObjectOutputStream).writeObject(オブジェクト配列名)でよい。
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(fpath));
oos.writeObject(aryListInOut);
oos.close();
} catch (Exception e) {
System.out.println("例外"+ e );
}
}
private void loadArrayList(String fpath) {
try {
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(fpath));
aryListInOut = (ArrayList < InOutData>)ois.readObject();
ois.close();
} catch (Exception e) {
System.out.println("例外:"+ e);
}
}
private javax.swing.JButton jButtonAdd;
private javax.swing.JButton jButtonLoad;
private javax.swing.JButton jButtonSave;
private javax.swing.JComboBox jComboBunrui;
private javax.swing.JComboBox jComboInOut;
private javax.swing.JLabel jLabel1;
private javax.swing.JLabel jLabel10;
private javax.swing.JLabel jLabel2;
private javax.swing.JLabel jLabel3;
private javax.swing.JLabel jLabel4;
private javax.swing.JLabel jLabel5;
private javax.swing.JLabel jLabel6;
private javax.swing.JScrollPane jScrollPane1;
private javax.swing.JTable jTable1;
private javax.swing.JTextField jTextContent;
private javax.swing.JTextField jTextDate;
private javax.swing.JTextField jTextPrice;
}
//以下はInOutDataクラスの定義。フィールドとして、Calendarクラス、int、int、int、Stringを
//データ格納用に持っている。上記のプログラム中では、ArrayListにこのInOutDataクラスの
//オブジェクトを格納し、シリアライズによるファイルへの保存、読み出し、Tableへの表示などを
//行うために用いている。
class InOutData implements Serializable{
//データ一件分を格納するクラス
//serializableインターフェイスを実装。内部フィールドの各クラスもserializableであれば、
//全体をserializeできる。ここで使っているCalendarやStringはserializableである。
public Calendar cal;//発生した日付はCalendarクラスで格納
public int inout;//支出=-1、収入=1
public int himokuID;//費目番号
public int kingaku;//金額
public String naiyou;//内容
public static String[] columnNameArray = {"日付", "項目種別", "入/出", "金額","内容"};
public static String[] himokuTable={"食費","光熱費","住居費","娯楽費",
"教養費","アルバイト","仕送り","その他"};
InOutData(){//コンストラクタ
cal= Calendar.getInstance();
inout=1;
himokuID=0;
naiyou="";
}
public String convHimoku(){
//このインスタンスが保持している費目番号から費目名称文字列を返す
return himokuTable[himokuID];
}
public void setDate(int yr, int mn, int dy){
//日付をセットする
cal.set(yr, mn, dy);
}
public int comparewith(InOutData another){
//日付の比較 -, 0, +
//ArrayComparatorで呼び出して使うために、日付を比較するメソッドを用意しておく。
return cal.compareTo(another.cal);
}
public Object[] getArray(){
//フィールドオブジェクトを配列にしたものを返す.Table表示に使用。
return new Object[]{cal, himokuTable[himokuID],inout,kingaku,naiyou};
}
public void putStr(String strDate, String strShubetu, String strIO,
String strAmount, String strNaiyou){
//引数の文字列データをメンバフィールドに変換入力
try {
//java.text.DateFormat df = new SimpleDateFormat("yyyy/MM/dd");
Date date = DateFormat.getDateInstance().parse(strDate);
cal.setTime(date);
} catch (ParseException e) {
e.printStackTrace();
}
for (int i=0; i < himokuTable.length; i++){
if(strShubetu.equals(himokuTable[i])){
himokuID=i;
}
}
if(strIO.equals("支出")){
inout=-1;
}else{
inout=1;
}
kingaku=Integer.parseInt(strAmount);
naiyou=strNaiyou;
}
public void putAryStr(String[] strAry){
//String配列として与えられた引数から、データ変換して自身に入力。
try {
java.text.DateFormat dfmt = new SimpleDateFormat("yyyy/MM/dd");
Date date = dfmt.getDateInstance().parse(strAry[0]);
cal.setTime(date);
} catch (ParseException e) {
e.printStackTrace();
}
for (int i=0; i < himokuTable.length; i++){
if(strAry[1].equals(himokuTable[i])){
himokuID=i;
}
}
if(strAry[2].equals("支出")){
inout=-1;
}else{
inout=1;
}
kingaku=Integer.parseInt(strAry[3]);
naiyou=strAry[4];
}
}
