JAVA プログラミングTips

JavaのTipsいろいろ

このページでは、Javaプログラミングをしながら気づいた小さなTipsを書き留めています。

思いつきベースなので、内容的にも分散していますが、役に立つ内容もあると思います。ご一読ください。



Singleton

プログラミングにおいて、データの一元化を行い、各イベント発生時(ボタンクリックなど)に同一のデータにアクセスしたい場合があります。

例えば、ArrayListに対して、Add, Edit, Save, Openなどのボタンから操作したいが、Listenerを内部クラスにしないと親クラスにあるデータにアクセスできないからといって、全てのListenerを親クラス内に記述するのは、個人的に、スタイルとして格好悪い気がします。

デザイン・パターン Singletonは、インスタンスの数を保障できるパターンとして上記のような場合に適応できそうです。

Singleton

class SingleArrayList extends ArrayList{ 
        // 唯一の自分自身のインスタンスを保持 
        private static SingleArrayList _sal;
        // 外部から生成できないことを保障するためprivateコンストラクタを宣言 
        private SingeArrayList(){}
        // インスタンスがなければ生成し、インスタンスの参照をreturn
        public static getSingleArray(){
                if(_sal == null){
                        _sal = new SingleArrayList();
                }
                return _sal; 
        }
}

委譲型Singleton

前回のSingletonの例では、各クラスが一つのインスタンスへの参照を共有しますが、この場合、Fileに出力されたSingletonデータを読み込む場合に問題がおきます。

例えば、Singletonである目録データ(SingleMokuroku)のインスタンスを、User用のGUIとOperator用のGUIが保持していたとします。

そこで、Operator用GUIのSaveボタンのListenerがOperator用GUIで保持しているSingleMokurokuのインスタンスをwriteObjectで書き出した場合を考えます。

この場合、書き出しまでは正常にできますが、これをOpenのListenerがOperator用GUIで保持しているSingleArrayListのインスタンスにreadObjectしてしまうと、OpenのSingleArrayListの参照先がreadObjectで生成されたインスタンスに変更されるだけで、User用GUIの参照は、依然として今までのSingleMokurokuのインスタンスを見ているという状態になってしまいます。

つまり、一つしか存在しないはずのSingleArrayListが2つ出来上がってしまうのです。(下記SingletonTest.java参照(長い。。))

SingletonTest.java

import java.util.*;
import java.io.*;
import java.awt.event.*;
import javax.swing.*;

public class Library{
	private UserGUI userGUI;
	private OperatorGUI operatorGUI;

	public Library(){
		userGUI=new UserGUI();
		operatorGUI=new OperatorGUI();
		userGUI.show();
		operatorGUI.show();
	}

	public static void main(String args[]){
		Library library;
		library=new Library();
	}
}

class UserGUI extends JFrame{
	private JButton buttonView;
	private SingleMokuroku singleMokuroku=SingleMokuroku.getObj();

	public UserGUI(){
		setTitle("User GUI ");
		addWindowListener(new java.awt.event.WindowAdapter(){
			public void windowClosing(java.awt.event.WindowEvent e){
				System.exit(0);
			}
		});
		buttonView = new JButton();
		buttonView.setText("View");
		buttonView.addMouseListener(new java.awt.event.MouseAdapter(){
			public void mouseClicked(MouseEvent e){
				System.out.println("View Pressed");
				System.out.println("View data...");
				singleMokuroku.printAll();
				System.out.println("...");
			}
		});
		getContentPane().add(buttonView,"West");
		pack();
	}
}

class OperatorGUI extends JFrame{
	private JButton buttonSave,buttonOpen;
	private JTextField textAdd;
	private SingleMokuroku singleMokuroku=SingleMokuroku.getObj();

	public OperatorGUI(){
		setTitle("Operator GUI ");
		addWindowListener(new java.awt.event.WindowAdapter(){
			public void windowClosing(java.awt.event.WindowEvent e){
				System.exit(0);
			}
		});
		buttonSave = new JButton();
		buttonSave.setText("Save");
		buttonSave.addMouseListener(new java.awt.event.MouseAdapter(){
			public void mouseClicked(MouseEvent e){
				System.out.println("Save Pressed");
				try{
					FileOutputStream fos = new FileOutputStream("mydata.ser");
					ObjectOutputStream oos = new ObjectOutputStream(fos);
		
					oos.writeObject(singleMokuroku);
					oos.close();
				}catch(IOException ex){
					ex.printStackTrace();
				}finally{
					System.out.println("Saved data...");
					singleMokuroku.printAll();
					System.out.println("...");
				}
			}
		});
		getContentPane().add(buttonSave,"Center");
		buttonOpen = new JButton();
		buttonOpen.setText("Open");
		buttonOpen.addMouseListener(new java.awt.event.MouseAdapter(){
			public void mouseClicked(MouseEvent e){
				System.out.println("Open Pressed");
				try{
					FileInputStream fis = new FileInputStream("mydata.ser");
					ObjectInputStream ois = new ObjectInputStream(fis);
					singleMokuroku=(SingleMokuroku)ois.readObject();
					ois.close();
				}catch(IOException ex){
					ex.printStackTrace();
				}catch(ClassNotFoundException ex){
						ex.printStackTrace();
				}finally{
					System.out.println("Opened data...");
					singleMokuroku.printAll();
					System.out.println("...");
				}
			}
		});
		getContentPane().add(buttonOpen,"West");
		textAdd = new JTextField();
		textAdd.setColumns(10);
		textAdd.addActionListener(new ActionListener(){
			public void actionPerformed(ActionEvent e){
				System.out.println("Add Pressed");
				singleMokuroku.add(e.getActionCommand());
				System.out.println("Added data...");
				singleMokuroku.printAll();
				System.out.println("...");
			}
		});
		getContentPane().add(textAdd,"East");
		pack();
	}
}

class Mokuroku extends ArrayList{
	void printAll(){
		ListIterator itr=this.listIterator(); 
		while(itr.hasNext()){
			System.out.println("Library data field " + itr.next());
		}
	}
}
	
// Singleton Data
class SingleMokuroku extends Mokuroku{
	private static SingleMokuroku _singleMokuroku;

	private SingleMokuroku(){}
	public static SingleMokuroku getObj(){
		if(_singleMokuroku == null){
			_singleMokuroku=new SingleMokuroku();
		}
		return _singleMokuroku;
	}
}

上記を実行し、下記の手順を行って見ます。
1. Textフィールドに文字を入れ、リターン
2. Saveをクリック
3. Openをクリック
4. Textフィールドに文字をいれ、リターン

出力されるデータから、UserGUIが保持しているSingletonのインスタンスとOperatorGUIの保持しているSingletonインスタンスが別のものをさしていることがわかります。

これは、Openでオブジェクトを読み込んだ場合に、readObjectが新しいインスタンスを生成するためです。

では、Openして読み込んだデータを他にも反映させるには、どうすればいいでしょうか?

この場合、2つの方式があります。