COBOLerの上司のためにJava入門講座を開いてみた
18/08/13 16:26:54 19/01/13 12:36:10
Java入門。クラスって何?
COBOL等の他言語の経験は十分あるが、Javaの経験がないという経歴の人を対象にJavaを教えることになりました。その体験を元にJava入門者に説明するにはどうすればいいか考えてみました。
Java入門ということで、変数の定義や演算子、条件分岐や繰返しなどから始めましたが、このあたりは他の言語でも対応するものがあるのですんなりと理解が進みました。
非オブジェクト言語の経験者には、「クラス」を始めとするオブジェクト指向の考え方が分かりにくいようです。
なぜ「クラス」を理解するのが難しいのか?それは「クラス」がJavaの基本的な概念でありながら、データと処理という2つの側面を持っているからです。
図1 クラスの構成
上の図で示した様にクラスの構成要素として、データを保持ずる「メンバ変数」と処理を記述した「メソッド」があります。このような構成をもつことからクラスはデータでもあり、処理でもあるという二面性を持っています。Java入門者はクラスのこの二面性に戸惑ってしまいます。以下に例を挙げてその二重人格ぶりを見ていきましょう。
次のコード例では文字列を「text」という変数に設定して、その文字列の空白を除去しています。変数とは書き換えができるデータのことです。「String」というのは文字列を表すクラスです。Javaでは変数を定義するときにデータ型を定義する必要があります。データ型とは変数に入るデータがどういうものかをあらかじめ宣言するものです。文字列のデータ型であるString型はStringクラスで定義されています。
「trim()」というのはStringクラスが持っている処理で、自分が持っている文字列データの空白を除きます。
String text = "Sample ";
text.trim();
このソースをみてよく言われるのが「trim(text)」じゃないのか?ということですね。
これは後のインスタンス化のところで説明しましょう。
また、データを保持・受け渡しする目的のクラスとしてBeanクラスというものがあります。ソースコード例を以下に示します。
public class UserBean {
private String id;
// constructor
public UserBean(){
this.id = "";
}
// setter
public void setId(String id){
this.id = id;
}
// getter
public String getId(){
return this.id;
}
...
}
クラス「UserBean」の変数「id」はメソッド(処理)「setId」「getId」を経由してアクセスされます。「setId」は「id」に値を設定する処理、「getId」は「id」から値を取得する処理です。データを扱うために必ず決められた処理を経由することになります。
値が変更できる変数に対して、あらかじめ決められた値を記憶しておくものが定数です。同じ目的・種類の定数を分類して定数クラスとしてするのが一般的です。
Webアプリケーション開発では、役割に応じて様々なクラスを設計します。サーバー側でアプリケーションの機能を担うのがサービスクラスとよばれるものです。
機能を実現するための処理とその機能に関連するデータを定義しています。
プログラムを作成していると同じような処理が出てくる場合があります。文字列編集や日付計算などのよく使う処理を関数としてまとめてクラスに格納したものが共通関数クラスです。大きな開発プロジェクトでは共通関数を作成する専門チームが組織されていることがあります。
このようにデータと処理を1つのものとして扱えるところ、さらには処理だけ集めたものやデータだけの定義ができるところがクラスの便利なところですが初心者には混乱の原因にもなります。ということで表に整理しておきましょう。
クラスの種類 | 説明 | データ | 処理 |
データ型 | Stringなどデータの種類を表す | ○ | ○ |
Beanクラス | データの保持・受け渡し | ○ | ○ |
定数クラス | 同じ種類の定数をまとめる | ○ | − |
サービスクラス | アプリケーションの機能を担う | ○ | ○ |
共通関数クラス | よく使う処理をまとめる | − | ○ |
インスタンス化はなぜ必要?
それでは、クラスを実際に使う場面を考えてみましょう。
実際のプログラムはメモリ上で動作するので、クラスをメモリ上に読み込むことが必要です。Javaではこのメモリ上に展開されたクラスの実体をインスタンスと呼び、インスタンスを作成することをインスタンス化といいます。インスタンス化は「初期化」とも言い換えることができます。
クラスをインスタンス化するには、クラス名の前に「new」と記載します。
次のソースコードを見てください。
class mainClass {
public static void main(String args[]) {
// sampleAクラスをインスタンス化する
sampleA aaa = new sampleA();
// sampleAクラスのmethodを呼び出す
aaa.method();
System.out.println("呼び出し元です。");
}
}
class sampleA {
void method(){
System.out.println("sampleAです。");
}
}
このソースを実行してコンパイルすると、実行結果としてまず「sampleAです。」が出力され、後に「呼び出し元です。」が出力されます。
「sampleA aaa = new sampleA()」と記載することで、クラス「sanpleA」をインスタンス化して使用可能にします。「new」は新しくインスタンスを作成することを意味します。次に、「aaa.method()」と記述することで「sampleB」クラスの「method」を呼び出します。
「new」を記載しなくてもインスタンスが作成されるクラスがあります。それは文字列を表すStringクラスです。
String text = "Sample ";
text.trim();
1行目の「String text = “Sample “」でインスタンスが作成され、変数「text」に代入されています。2行目の「text.trim()」でStringクラスの「trim()」メソッドを呼び出しているのです。
インスタンスは複数作成することができます。同じクラスをインスタンス化する度に新しいインスタンスが生成されます。
なぜ、複数のインスタンスが必要なのでしょうか? 理由の一つとして同じプログラムが同時に動くことを考慮するためです。例えばWebシステムの場合、複数のユーザーが同時にアプリケーションにアクセスする可能性があります。下の図の様に顧客の情報を検索して返すクラスがあったとします。
複数のユーザーの処理で1つのインスタンスを共有していると、同時にアクセスが行われた場合、他のユーザーからのアクセスでインスタンス内のデータが書き換えられる可能性があります。その場合は正しくない結果が返されることになります。
それぞれ別のインスタンスが割り当てられるようにすれば、互いに影響を及ぼすことがなく安全なシステムが構築できます。
また、複数の同じクラスのデータを同時に扱う必要がある場合に複数インスタンスを使います。
例えば、2つのファイルを同時に読み込んでそれぞれ1文字ずつ比較していくプログラムの場合、ファイルにアクセスするクラスのインスタンスを2つ作成します。
また、データベースから複数のレコードを取得する場合、レコード数分のインスタンスを作成します。もしインスタンスを1つしか作成されないとしたら全てのレコードが同じ値を参照してしまいます。
継承ってどんな時に使うの?
Javaを始めとするオブジェクト指向プログラミングには「継承」というしくみがあります。「継承」とは汎用的なクラスから派生したクラスを作成することです。Javaの入門書などでは「新しい機能を作るとき、以前作った機能と共通する部分を引き継ぐこと」と説明されています。実際には機能の設計の段階で複数の機能に共通する処理を抽出して汎用的なクラスを設計する場合が多いです。
それでは、継承のメリットは何でしょうか?
1つ目のメリットは汎用的なクラスにメソッドが共通化されるため、同じコードを書く必要がなくなることです。システムやアプリケーションの開発には大量のプログラムを作成することが必要になり、多くのクラスを作ることが必要ですが継承によって同じ処理を複数のクラスに書くという無駄を省くことができるので生産性を向上させることができます。
2つ目のメリットは継承により、クラスのコードが整理されプログラムが読みやすくなることです。システムの開発期間よりも運用・保守を行う期間の方がずっと長いので、保守のことを考えると読みやすいプログラムであるに越したことはありません。
Javaではクラスの継承によって、開発者は既存のクラスで指定されている変数やメソッドといった要素を取り込み、同じ記述を繰り返すことなく新しいクラスを作成することができます。
Javaでの継承の書式は次のようになります。
class サブクラス名 extends スーパークラス名 {}
クラスの継承をする場合、継承元(継承されるクラス)をスーパークラスと呼び、継承先(継承するクラス)をサブクラスと呼んでいます。継承後、サブクラスではスーパークラスのメソッドや変数を使えるようになります。
以下はDB接続を行うサービスクラスです。「AService」も「BService」もDB接続情報の設定や取得を行いますが、「BaseService」クラスを継承しているため「getDm」メソッドや「setDm」メソッドを記載する必要がありません。
class BaseService{
DBManager dm; //DB接続情報
public DBManager getDm(){
return dm; //DB接続情報を返す
}
public void setDm(DBManager dm){
this.dm = dm; //DB接続情報を設定します。
}
}
class AService extends BaseService {
public void doService(){
//処理は省略
}
}
class BService extends BaseService {
public void invoke(){
//処理は省略
}
}
もし、継承を使わないと次のようになります。コードが重複しているのが分かるでしょうか。
class AService {
public DBManager getDm(){
return dm; //DB接続情報を返す
}
public void setDm(DBManager dm){
this.dm = dm; //DB接続情報を返す
}
public void doService(){
//処理は省略
}
}
class BService {
public DBManager getDm(){
return dm; //DB接続情報を返す
}
public void setDm(DBManager dm){
this.dm = dm; //DB接続情報を返す
}
public void invoke(){
//処理は省略
}
}
継承はチームで開発をする場合に重要になってきます。
上の例で「BaseService」クラスを共通部品チームが担当し、各機能の担当チームでサブクラスを開発する等の様に分担して作業することがあります。
インターフェースって何がうれしいの?
継承と並んでJavaが持つ特徴的な機能が「インターフェース」です。
「インターフェース」は抽象的なクラスの仕様をあらわします。クラスが満たすべき制約を定義したものです。インターフェースにはメソッドを定義しますが、その処理内容は記載しません。インターフェースとは、形だけ作った中身のないクラスのようなものです。こんなものがどうして必要なのか疑問に思われる方も多いかもしれません。
インターフェースの使い方の一つにメインクラスからの呼び出しの共通化があります。
次のコードを見てください。指定されたサービス名によって使うサービスクラスを切り替えています。このコーディングだと新たにサービスクラスが増えた場合、呼び出しもとのクラスを変更することになります。
class Yobidasimoto{
//コマンドメソッド
//引数:サービス名
public void command(String serviceName){
if("A".equals(serviceName)){
AService service = new AService();
service.doSerice();
}else if("B".equals(serviceName)){
BService service = new BService();
service.doSerice();
}
}
}
Class AService {
Public void doService(){ }
}
Class BService {
Public void doService(){ }
}
インターフェースを使用した下のコードではどうでしょうか。
インスタンスの生成については「ServiceFactry」に任せています。どのサービスのインスタンスでも「doService」メソッドを呼び出すだけです。新たにサービスクラスが追加された場合でも、呼び出し元のクラスは影響を受けません。その場合「ServiceFactry」に新しいサービスについての記載が追加されることになります。
インターフェース「IFService」をサービスクラスの型として使用していることに注目してください。前の例ではそれぞれ個別のクラスを型として使用していましたが、ここではインターフェースが各サービスクラス共通の型として使用されています。
class Yobidasimoto{
public void command(String serviceName){
IFService service = CreateService.CreateService(serviceName);
service.doService();
}
}
//「インターフェース」のJavaソースコード
interface IFService
{
void doService();
}
Class AService implements IFService {
Public void doService(){ }
}
Class BService implements IFService {
Public void doService(){ }
}
//ファクトリクラス
class ServiceFactry{
public IFService CreateService(String serviceName){
IFService service ;
if("A".equals(serviceName)){
service = new AService();
}else if("B".equals(serviceName)){
service = new AService();
}
return service;
}
}
このようにインターフェースを使用すると呼出方法を統一して共通化することができます。アプリケーションのフレームワークなどでこのようなやり方が良く使用されています。
また、具体的な処理の詳細が未定な場合や変更が予測される場合に先にインターフェースだけ定義しておいて、実装は後から行うという方法を採ることができます。インターフェースが決まっているので、あとで呼出先クラスの実装に変更があっても呼出元には影響がありません。
チームで作業している場合に、インターフェースを境界として、プログラミングの作業を分担することができます。インターフェースとはプログラム部品を組み合わせてシステムを作る際の「接合面」なのです。接合面の仕様さえ決めておけば組み合わせる部品の中身を知らなくても自分の担当の開発に専念することができます。これを有効的に使うことで大規模システムは整理され、分担して作りやすくなりますし、開発後のメンテナンスも容易になります。
まとめ
いかがでしたでしょうか。プログラム初心者でも他の言語の経験者でも、Javaオブジェクト指向プログラミングの考え方は難しいと感じるようです。この記事では基本的なキーワードである「クラス」、「継承」、「インターフェース」について解説してみました。なんとなくイメージだけでも掴んでもらえたら幸いです。
人気記事