連載
» 2002年08月24日 12時00分 公開

Javaオブジェクトモデリング(4):静的モデル:クラスにおけるUMLとJavaのマッピング(2) (4/5)

[浅海智晴,@IT]

7.サンプルプロファイル

 前節までで、UML“クラス”とJava“クラス”のマッピングを考えるための情報を整理しました。

 簡単なクラス図を書くことが目的であれば、UML“クラス”とJava“クラス”はほとんどそのままマッピングできます。しかし、CASEツールを用いて、UML“クラス”とJava “クラス”を完全に相互変換するようなことを最終目標と考えると、いろいろな要因が絡み合っていて、なかなか完全なマッピングができません。

 この問題を解決するためには、開発の目的に応じて、プロファイルという形でまとめておく必要が出てきます。プロファイルを決めることによって、UML“クラス”とJava“クラス”のマッピングにおけるあいまいな点をなくすことができるため、完全なマッピングを行うことが可能になるのです。

  本節では、このような目的から、通常の開発に利用するためのサンプルプロファイルをまとめてみました。

7.1 “クラス”のUML上の表現

 前述したようにUMLの“クラス”は、以下の種類に分けることができます。

  • (ステレオタイプのない)普通のクラス
  • ステレオタイプmetaClassのクラス
  • ステレオタイプthreadのクラス
  • ステレオタイプutilityのクラス
  • インターフェイス
  • 例外
  • シグナル

  この中で、普通のクラス、ステレオタイプutilityのクラス、インターフェイス、例外については、Javaとのマッピングははっきりしています。

 ステレオタイプmetaClassのクラスに対応するJavaのクラスはjava.lang.Classのみです。ただし、JavaではメタクラスをベースとしたいわゆるMOP(Meta Object Protocol)のテクニックを使うことはできないので、ステレオタイプmetaClassのクラスを主要なモデル要素として考える必要はありません。もちろん、ステレオタイプmetaClassのクラスを補助的に使うのは有力です。

  ステレオタイプthreadのクラスが対応するJavaクラスとしては、java.lang.Threadが本命です。しかし、java.lang.Runnableというインターフェイスもあり、単純にステレオタイプthreadのクラスを使うだけでは表現しきれません。実際にJava向けの設計モデルを作成する場合には、ステレオタイプthreadのクラスを使うよりも、java.lang.Threadやjava.lang.Runnableを直接指定する方が明快です。このため、本プロファイルでは、特にステレオタイプthreadのクラスについては直接のマッピングの対象にはしません。もちろん、ステレオタイプthreadのクラスを補助的に使うのは有力です。

 シグナルは、Java側で対応するクラスはjava.lang.Throwableとなりますが、Javaプログラムではサブクラスのjava.lang.Errorや java.lang.Exceptionを使うのが一般的なので、特に意識しておく必要はないでしょう。

 以上の点から、本プロファイルで使用する“クラス”は以下の5つとなります。これらの“クラス”のUML上の表現は図11となります。

  • 具象クラス
  • 抽象クラス
  • インターフェイス
  • ユーティリティ
  • 例外
図11 “クラス”のUML上の表現 図11 “クラス”のUML上の表現

 実際の開発においては、以上の5つのクラスを部品として用いていけばよいでしょう。

7.1.1 具象クラス

 具象クラスは、インスタンスを生成することができるクラスです。具象クラスのUMLでの実現例は図11の左上となります。四角形のクラスアイコン内に、クラス名がボールド体で記述されます。

 このUMLクラスに対するJavaの実装はリスト1のようになります。

リスト1 ConcreteClass.java
public class ConcreteClass {
}

7.1.2 抽象クラス

 抽象クラスは、インスタンスを生成することができないクラスです。汎化を用いて利用されることを想定しています。

 抽象クラスのUMLでの実現例は図11の上中央となります。

 四角形のクラスアイコン内に、クラス名がイタリック体で記述されます。なお、クラス名をイタリック体で表示する代わりにプロパティabstractを明記する方法も有効です。

 このUMLクラスに対するJavaの実装はリスト2のようになります。修飾子abstractを用いることで、抽象クラスを実現しています。

リスト2 AbstractClass.java
public abstract class AbstractClass {
} 

7.1.3 インターフェイス

 インターフェイスは、オペレーションの集合を示すモデル要素です。インターフェイスのUMLでの実現例は図11の右上となります。ステレオタイプinterfaceを用いてインターフェイスであることを示します。

 このUMLインターフェイスに対するJavaの実装はリスト3のようになります。Javaのインターフェイスをそのまま用いることができます。

リスト3 Interface.java
public interface Interface {
} 

7.1.4 ユーティリティ

 ユーティリティは、グローバルなスコープを持つ変数や手続きをクラスの形にまとめたモデル要素です。 ユーティリティのUMLでの実現例は図11の左下となります。ステレオタイプutilityのクラスを用いてユーティリティであることを示します。

 このUMLユーティリティに対するJavaの実装はリスト4のようになります。Javaでは、クラスメソッドのみを定義したクラスとして実現します。

リスト4 Utility.java
public class Utility {
  public static Object doSomething(Object lhs, Object rhs) {
...
  }
} 

7.1.5 例外

 例外は、例外事象を示すシグナルです。プログラムの正規の制御フローとは異なった形で送受信されます。例外のUMLでの実現例は図11の下中央となります。ステレオタイプexceptionを用いて例外であることを示します。

 このUML例外に対するJavaの実装はリスト5のようになります。図11では直接示されていませんが、ConcreteExceptionは、java.lang.Exceptionの子孫クラスである必要があります。

リスト5 ConcreteException.java
public class ConcreteException extends 
java.lang.Exception {
}

 またjava.lang.RuntimeExceptionの子孫クラスである場合には、プロパティruntimeを使って表現することになります。

7.2 属性

 属性のマッピングについて検討します。

7.2.1 種類のマッピング

 UML属性に対応するJavaの部品として以下のものが挙げられます。

  • インスタンス変数
  • クラス変数
  • 定数

  インスタンススコープを持ったUML属性はインスタンス変数、クラススコープを持ったUML属性はクラス変数にマップできます。UML属性とJavaのインスタンス変数/クラス変数の関係は、一般的なオブジェクト指向の守備範囲中の話なので大きな問題はありません。

 少し議論が必要になるのはJavaの定数の扱いです。UMLでは直接定数に対応する概念がないため、簡単にマッピングすることができないのです。Javaの定数をUMLで表現する方法として以下のものが考えられます。

  • 属性を拡張して表現
  • オプションの並び区画で定義

 「属性を拡張して表現」は、専用のプロパティconstなどを導入し、定数であることを表現する方法です。この方法は、一番自然な解決策に見えますが、JavaのインターフェイスをUMLのインターフェイスにマッピングするときに問題となります。UMLのインターフェイスはオペレーションの集まりを表現するモデル要素であり、属性を持つことができないからです。

 無難な方法として考えられるのは、オプションの並び区画を使う方法です。すなわち、定数宣言のための並び区画を新規に導入し、この中に定数の宣言を書いてしまいます。UMLの標準仕様外なのでJavaの文法のまま記述しても構いません。

 本プロファイルでは、最後に挙げた「オプションの並び区画で定義」を採用することにします。

7.2.2 部品のマッピング

カテゴリ UML Java
対応 属性名 属性名
可視性 可視性
初期値 初期値
多重度 配列またはコレクションライブラリ
順序性 配列またはコレクションライブラリ
UML拡張プロパティ プロパティfinal final
プロパティtransient transient
プロパティvolatile volatile
補助情報 changeability -
persistence -

 上の表は属性のプロファイルです。共通の部品である属性名、型、可視性、初期値はUMLの部品とJavaの部品がそのまま対応します。

 UMLのスコープはJavaのstaticの有無に対応します。Javaではstaticで修飾された属性のスコープはクラススコープ、staticで修飾されていない属性のスコープはインスタンススコープになりますが、これがそのままUMLのスコープの定義に対応します。

 多重度と順序性については、今回の検討範囲外とします。次回以降「アソシエーション」の回に検討する予定です。

 UML側に拡張するプロパティは以下のものです。

  • final
  • transient
  • volatile

  Javaのtransientとvolatileに相当するUMLの機能はないので、この2つのプロパティを拡張します。

 finalについては、UMLの標準プロパティであるchangeabilityと一部機能が重なっていますが、前述したようにまったく同じ機能を持つわけではありません。意味的な混乱を避けるのと、Javaプログラマにはfinalというプロパティがあれば意味が一目瞭然なので、本プロパティでは拡張プロパティfinalを導入することにしました。

 UMLが標準で定義している以下のプロパティは補助情報とします。

  • changeability
  • persistence

 changeablityは属性の変更可能性に対するプロパティ、persistenceはオブジェクトの永続性に対するプロパティです。いずれもJavaには完全に対応する機能がないため、補助情報という扱いにします。

7.3 属性のマッピング例

7.3.1 プリミティブ型属性

 一般的なプリミティブ型の属性のマッピングは以下のようになります。

Java private int balance;
UML balance:int

 Javaの可視性は、privateやpublicといった修飾子で記述されますが、UMLでは「-」や「+」といった記号が使用されます(UMLでもpublic

などの文字列を用いることはできますが、あまり一般的ではないので本連載では省略します)。この例では、JavaのprivateをUMLでは「-」で表現しています。

 属性の型は、Javaでは変数名の左側に、UMLでは属性名の右側、「:」の後に記述します。この例ではJavaのプリミティブ型を指定していますが、これはUMLではデータ型にマップされます。

7.3.2 オブジェクト属性

 一般的なオブジェクトの属性のマッピングは以下のようになります。

Java private String name;
UML name:String

 基本的にはプリミティブ型と同じ構成になります。型としてクラス名が記述されます。

7.3.3 初期化

Java private int balance = 0;
UML balance:int = 0

 Java、UMLいずれの場合も、「=」の右側に初期値を記述します。UMLでは初期値の文法は特に定めておらず、ターゲットとなるプログラミング言語の文法をそのまま利用することになっており、この例でもそのようになっています。

7.3.4 クラス変数

 クラス変数のマッピングは以下のようになります。

Java private static int numberOfItems;
UML numberOfItems:int

 Javaのクラス変数は修飾子staticを用いて表現します。

 Javaのクラス変数に対応するUMLのモデル要素はクラススコープの属性となります。クラススコープであることを、属性に下線を引くことで表現します。

7.4 オペレーション

 オペレーションのマッピングについて検討します。

7.4.1 種類のマッピング

 UMLのオペレーションに対応するJavaの部品は以下の3つです。

  • インスタンスメソッド
  • クラスメソッド
  • コンストラクタ

 インスタンススコープのオペレーションがインスタンスメソッドに、クラススコープのオペレーションがクラスメソッドに対応します。ステレオタイプcreateのオペレーションがコンストラクタに相当します。

7.4.2 部品のマッピング

 オペレーションの部品のマッピングを以下の表に示します。

カテゴリ UML Java
対応 オペレーション名 クラス名
返却値型 復帰型
可視性 可視性
パラメタ パラメタ
例外 例外
プロパティabstract abstract
UML拡張プロパティ プロパティfinal final
プロパティsynchronized synchronized
プロパティnative native
プロパティstrictfp strictfp
補助情報 プロパティquery -
プロパティroot -
プロパティleaf -
プロパティconcurrency -

 UMLとJavaの共通の部品である、オペレーション名/メソッド名、返却値型/復帰型、可視性、パラメタ、例外はそのままマッピングされています。また、UMLのプロパティabstractとJavaの修飾子abstractも同じ意味です。いずれも抽象メソッドを表します。

 UML側に拡張するプロパティは以下のものです。

  • final
  • synchronized
  • native
  • strictfp

  Javaのnativeとstrictfpに相当するUMLの機能はないので、この2つのプロパティを拡張するのは自然です。

 finalについては、UMLの標準プロパティとして、同じ意味であるleafが定義されています。しかし、プロパティfinalはクラスや属性のところですでに導入しています。用途の一貫性を保つためには、オペレーションにおいてもfinalを用いた方が混乱が少ないということで、本プロファイルではプロパティfinalを用いることにします。プロパティsynchronizedは、標準プロパティconcurrencyと一部意味が重なりますが、前述したように完全に対応するわけではありません。意味的な混乱を避けるのと、Javaプログラマにはsynchronizedというプロパティがあれば意味が一目瞭然なので、本プロパティでは拡張プロパティsynchronizedを導入することにしました。

 UMLが標準で定義している以下のプロパティは補助情報とします。

  • query
  • root
  • leaf
  • concurrency

 プロパティqueryとプロパティrootは、対応する機能がJavaにありません。

 プロパティleafは、Javaのfinal修飾子に対応しますが、拡張プロパティfinalを導入する以上、Javaのfinal修飾子にはプロパティfinalをマップするのが整合性の意味から望ましいと考えられます。

 プロパティconcurrencyは、Javaのsynchronized修飾子と微妙に意味が重なっていますが、完全に同じではないため、実用上は新規にsynchronized修飾子を導入した方が分かりやすいと考えられます。

7.5 オペレーションのマッピング例

 それでは、本プロファイルでのオペレーションのマッピング例について見ていきましょう。

7.5.1 インスタンスメソッド

 インスタンスメソッドのマッピングは以下のようになります。

Java public int getBalance()
UML +getBalance():int

 属性の場合と同様に、可視性として、Javaではpublicなどの修飾子、UMLでは「+」のなどの記号が用いられます(UMLでもpublicなどの文字列を用いることはできますがあまり一般的ではないので本連載では省略します)。Javaのメソッド名はそのままUMLのオペレーション名にマップされます。Javaのメソッドの復帰値にはプリミティブ型かオブジェクト型名が、メソッド名の左側に指定されますが、UMLではオペレーションの右側に「:」に続いて指定されます。

7.5.2 引数のあるインスタンスメソッド

 引数のあるインスタンスメソッドのマッピングは以下のようになります。

Java public int plus(int lhs, int rhs)
UML +plus(lhs:int, rhs:int):int

 Javaではメソッドの引数を「型」「パラメタ名」の組みを「,」で区切って並べます。それに対してUMLではオペレーションの引数は、「パラメタ名」「:」「型」の組みを「,」で区切って並べます。文法が若干異なりますが、記述している内容はほとんど同じですね。

7.5.3 復帰値がvoid型のインスタンスメソッド

 復帰値がvoid型のインスタンスメソッドのマッピングは以下のようになります。

Java public void doSomething(int order)
UML +doSomething(order:int)

 Javaでは復帰値を持たないメソッドは復帰値の型としてvoidを指定します。それに対してUMLでは復帰値の型としては何も指定しないという記述方法を取ります。

7.5.4 例外のあるインスタンスメソッド

 例外のあるインスタンスメソッドのマッピングは図12のようになります。

図12 例外のあるインスタンスメソッドのマッピング 図12 例外のあるインスタンスメソッドのマッピング

 Javaではメソッドが送出する可能性のある例外はthrows句によって宣言されます。それに対してUMLではオペレーションからステレオタイプsendのディペンデンシィとして記述されます(ディペンデンシィは分類子間の関係を表現するリレーションシップの一種です。詳細な内容については次回以降「ディペンデンシィ」 の回に取り上げる予定です。

7.5.5 抽象メソッド

 抽象メソッドのマッピングは以下のようになります。

Java public abstract int getBalance()
UML +getBalance():int

 Javaでは抽象メソッドは修飾子abstractによって宣言されます。それに対してUMLではオペレーション名をイタリック体で記述することで表現します。またUMLでは以下のようにプロパティabstractを指定することでも抽象メソッドを表現できます。この記法はプレーンテキストなどイタリック体が使えない場合に有用です。

  • +getBalance():int {abstract}

7.5.6 クラスメソッド

 クラスメソッドのマッピングは以下のようになります。

Java public static int getNumberOfItems()
UML +getNumberOfItems():int

 Javaではクラスメソッドは修飾子staticによって宣言されます。それに対してUMLではオペレーション名に下線を引くことで表現します。

7.5.7 final

 修飾子finalを持つメソッドのマッピングは以下のようになります。

Java public final int getBalance()
UML +getBalance():int {final}

 この例ではJavaにおける修飾子finalを、UMLの拡張プロパティfinalとして表現しています。なおオペレーションに対する標準プロパティleafは、Javaのfinalと同じ機能を持っているので、UMLの標準文法だけで表現するのであれば、以下のようになります。

  • +getBalance():int {leaf}

7.5.8 native

 修飾子nativeを持つメソッドのマッピングは以下のようになります。

Java public native int getBalance()
UML +getBalance():int {native}

 Javaの修飾子nativeを、UMLの拡張プロパティnativeで表現しています。

Copyright © ITmedia, Inc. All Rights Reserved.

注目のテーマ