初出 JAVA PRESS Vol.33 (2003.11)

見えてきた「Tiger」の全貌

はじめに

今年は虎が大躍進の年でしたが、Javaでも虎が話題になっています。それはJava 2 Platform, Standard Edition 1.5という虎のことです。

J2SEはバージョンをコードネームで呼ぶことが慣例になっており、1.4がMerlin、1.4.1がHopper、1.4.2がMantisと呼ばれてきました。そして、1.5のコードネームがTigerなのです。

Tigerは今までにない大幅な言語仕様の変更や、多くの新機能が取り入れられる予定です。今年の6月にサンフランシスコで開催された JavaOne 2003でTigerの概要がかなり明らかになりました。Tigerに関するテクニカルセッションやBOFが多くあり、そこで新機能や従来機能の強化点などが発表されています。

この記事ではJavaOneでの発表やJCPの仕様に沿って、Tigerの概要について解説していきたいと思います。ただし、現時点でのドラフト仕様に基づいていますので、最終的な仕様までに仕様が変更される場合もあります。

Tigerのテーマ

JavaOne 2003は、これからのJavaを語る上できわめて重要な位置を占めていると思います。それはJavaの歴史の転換点とでもいえるかもしれません。

Sun Microsystemsの執行副社長であるJonathan SchwartsのメッセージはJava is Everywhere。このコンセプトにあわせて、様々なデバイスでも表示できるようにJavaのロゴまで変更されました。

このコンセプトはどちらかというとエンドユーザ向けのものですが、開発者に向けたコンセプトがEase of Developmentです。Javaの開発者を1,000万人へと増大させるためには開発の容易さが重要に名手来るというわけです。たとえば、Java Server Facesを使用した開発環境Project Raveなどがこのコンセプトに基づいて開発されています。

J2SEのロードマップに関するセッションではEase of Developmentも加えてTigerの5つのテーマが示されました(表3)。

この解説では新機能の話を絞るということから後半の3つEase of Development、Monitoring and Manageability、Desktop Clientを中心に解説したいと思います。

Tigerの仕様

Tigerの仕様はJava Community Process (JCP)において策定されています。JCPで策定されている標準はJava Specific Request (JSR)として管理されており、Tigerの仕様はJSR-176で行われています。

表1に示したメンバ(エキスパートグループ)がJSR-176の策定を行っています。

JSR-176ではどのような機能をTigerに取り入れるかを議論しており、表2に示すJSRがその候補となっています。表2で示したJSRがすべて取り入れられるとは限りませんが、ほとんどは何らかの形で取り入れられると予想されます。

これ以外にもJSR-202 Java Class File Specification UpdateなどTigerに深くかかわっているJSRもあります。

その他、JSRにはなっていませんが、多くの従来機能の強化が図られています。また、バグフィックスやパフォーマンス強化なども行われる予定です。

 

表 1 JSR-176 エキスパートグループ

Apache Hewlett Packard SAP AG
Apple IBM SAS Institute
Borland Macromedia SavaJe
Cisco Systems Nokia Osvaldo Doederlein
富士通 Oracle Juergen Kreileder

 

表 2 Tiger に関連する JSR

JSR-003 Java Management Extensions (JMX) Specification
JSR-013 Decimal Arithmetic Enhancement
JSR-014 Add Generic Types to the Java Programming Language
JSR-028 Java SASL Specfication
JSR-114 JDBC Rowset Implementations
JSR-133 Java Memory Model and Thread Specification Revision
JSR-163 Java Platform Profiling Architecture
JSR-166 Concurrency Utilities
JSR-174 Monitoring and Management Specification for the Java Virtual Machine
JSR-175 A Metadata Facility for the Java Programming Language
JSR-199 Java Compiler API
JSR-200 Network Transfer Format for Java Archives
JSR-201 Extending the Java Programming Language with Enumerations, Autoboxing, Enhanced for loops and Static Import
JSR-204 Unicode Supplementary Character Support
JSR-206 Java API for XML Processing (JAXP) 1.3

表 3 Tiger の主要なテーマ

  • Quality - Compatibility, Compatiblity, Compatibility!
  • Performance and Scalability
  • Ease of Development
  • Monitoring and Manageability
  • Desktop Client

 

Ease of Developement

TigerでもEase of Developmentというコンセプトが大きく影響しています。Ease of Developmentと関連するJSRは次の3つです。

いずれも言語仕様の変更をともなったものです。また、C/C++のprintfのような簡易フォーマット入出力も導入されます。それぞれについて簡単に紹介しましょう。

JSR-014 Generics

コレクションを使用するとき、いちいちキャストをしなくてはならないことを煩雑だと思われる方も多いと思います。

また、キャストすることによりClassCastException例外が発生するという問題もあります。キャストのチェックはコンパイル時にチェックすることができないため、アプリケーションの保守性が低下してしまいます。

Genericsを使用すると型をパラメータ化することができるため、キャストが必要なくなります。リスト1は4文字の文字列を連結する例ですが、これをGenericsを使用して書き直したものがリスト2です。

CollectionインタフェースとIteratorインタフェースをStringクラスでパラメータ化することにより、キャストが必要なくなります。

型の指定にワイルドカードを使用することもできます。リスト3はTigerでのCollectionsクラスのcopyメソッドの定義部分です。

引数のdestはTの親クラスであるクラスのオブジェクトのリスト、srcはTの派生クラスのオブジェクトのリストであれば、何でも使用できます。

Genericsを使用したコードはコンパイル時に、型チェックを行い、キャストを使用した従来のバイトコードに変換します。このため言語仕様は変更されましたが、Java VMの仕様は変更せずにGenericsを実現しています。

Genericsの詳細は本誌Vol 28の柴田氏による解説(1)を参照してください。また、リファレンスインプリメンテーションのEarly Access版が以下のURLで提供されています。ダウンロードにはJava Developer Networkの登録 (無料) が必要です。

http://developer.java.sun.com/developer/earlyAccess/adding_generics/index.html

 

import java.util.*;
 
public class ConcatSample1 {
    public static void main(String[] args) {
        List argList = Arrays.asList(args);
        StringBuffer buffer = new StringBuffer();
 
        Iterator it = argList.iterator();
        while (it.hasNext()) {
            String tmp = (String)it.next(); // キャストが必要
            buffer.append(tmp);
        }
        System.out.println(buffer.toString());
    }
}
リスト 1 文字列の連結

 

import java.util.*;
 
public class ConcatSample2 {
    public static void main(String[] args) {
        List<String> argList = Arrays.asList(args);
        StringBuffer buffer = new StringBuffer();
 
        Iterator<String> it = argList.iterator();
        while (it.hasNext()) {
            String tmp = it.next(); // キャストは不必要
            buffer.append(tmp);
        }
        System.out.println(buffer.toString());
    }
}

リスト 2 Generics を使用した文字列の連結

 

public class Collections {
    public static <T> void copy(List<? super T> dest, List<? extends T> src) {
        ...
    }
    ...
}

リスト 3 Tiger での Collections#copy

 

JSR-175 Metadata

RMIを使って通信することを考えてみましょう。RMIを使用するときにはいくつかのお約束があります。Remoteインタフェースを派生させたインタフェースを作成し、そこで定義するメソッドはすべてRemoteException例外をスローするようします。そして、そのインタフェースをインプリメントしたクラスを作成しなくてはいけません。

RMIを使用するときはいつもこのような同じ手順をふまなくてはいけません。同じようなことはEJBやJAX-RPCなどにもあります。

また、BeanInfoクラスなどのように、アプリケーションの本筋とは関係のないファイルを作成しなくてはならないこともあります。

これらを自動的に作成してくれれば、無駄な労力を咲かなくてもすみ、開発効率を上げることが可能になります。

それを実現するための機能がメタデータです。.NETを知っている方であれば、カスタム属性をご存知だと思います。メタデータはJava版カスタム属性というべきものです。

メタデータはJavaOen 2003でBOFセッションはありましたが、仕様はまだ公開されていません。ここでの解説やコード例はBOFでの発表を基に行いますが、最終的な仕様はかなり変更される可能性があります。

メタデータを記述するためにはアノテーションを使用します。しかし、アノテーションが記述されたとしてもプログラム自体の動作にはなんら影響がありません。通常は、ツールやライブラリがアノテーションを解釈して、ファイルの自動生成などを行います。

アノテーションは@の後に記述し、クラスやメソッド、プロパティ、ローカル変数などに付加することができます。

なにはともあれ、実際にどのように記述するか見てみましょう。リスト4はJAX-RPCでサービスを記述した例です。

リスト4のOLDが従来の記述法です。Remoteインタフェースを派生させたPingIFインタフェースを作成し、それをインプリメントするPingクラスで処理を記述しています。

NEW 1から3がメタデータを使用して書き直したものです。

NEW 1では@Webmethodがアノテーションです。これはfooというメソッドがWebサービスとして公開するメソッドであることを注釈しています。プログラムとしてはアノテーションがあったとしても変化はありません。しかし、JAX-RPCのwsdeployのようなツールがアノテーションを解釈することで、PingIFに相当するインタフェースを自動的に作成することができます。

また、import文がOLDとNEWで異なっていますが、これは@Webmethodというアノテーションがjavax.xml.rpcパッケージで定義されていることを示しています。

アノテーションをクラスに使用したものがNEW 2です。@WebServiceというアノテーションはこのクラスがWebサービスとして公開するもので、そのネームスペースはhttp://acme.com/pingとなることを示しています。

複数のアノテーションをつけた例がNEW 3です。fooメソッドがWebサービスで公開されるという付加情報だけでなく、SOAPのオペレーションであることを示しています。

アノテーションは特殊なインタフェースとして定義することができます。アノテーション型の定義の例をリスト5に示します。

アノテーション型の定義には@interfaceを使用します。一般のインタフェースと違って、この型の定義にはいろいろと制約があります。たとえば、派生を使用することはできない、メソッドは引数なし、戻り値に使用できる型もプリミティブ型や文字列に制限される、例外は投げられない、などです。派生を使用できないのはアノテーション型が実際にはjava.lang.annotation.Annotationインタフェースの派生インタフェースとなるためです。

アノテーション型のメソッドはメソッドの顔はしていますが、使用されるときはプロパティのように使用されます。アノテーションの使用例をリスト6に示しました。

アノテーションのカッコの中で、メソッド名 = 値 のように代入を行います。

アノテーションを読み込むにはアプリケーションの中で行う方法とツールで行う方法があります。

アプリケーションの中から利用するには、リフレクションを使用して行います。ロードしたクラスに付加されているアノテーションを読み込むために getAnnotationメソッドなどを定義したjava.lang.reflect.AnnotatedElementインタフェースが導入されました。Classクラスがこのインタフェースをインプリメントするように変更されています。

ツールで読み込むために、javadocのようなAPIが提供される予定です。このAPIではクラスロードをする必要はなく、ソースファイルやクラスファイルからアノテーションだけを読み込むことができます。

メタデータは自分で定義して使用することはあまりないと思います。しかし、定義済みのアノテーションを活用することで、定型処理などを自動化することができるなど、プログラミングのハードルを下げることができるはずです。

OLD:
 
import java.rmi.*;
 
public interface PingIF extends Remote {
    public void foo() throws RemoteException;
}
 
public class Ping implements PingIF {
    public void foo() { ... }
}
 
NEW 1:
 
import javax.xml.rpc.*;
 
public class Ping {
    public @Webmethod void foo() { ... }
}
 
NEW 2:
 
import javax.xml.rpc.*;
 
public @WebService(namespace="http://acme.com/ping") 
class Ping {
    public @Webmethod void foo() { ... }
}
  
NEW 3:
 
import javax.xml.rpc.*;
 
public @WebService(namespace="http://acme.com/ping") 
class Ping {
    public @Webmethod
           @SoapOperation(style=RPC,
                          use=ENCODE,
                          encodingStyle=SOAP_1_1)
    void foo() { ... }
}
リスト 4 メタデータで記述した JAX-RPCサービス

 

public @interface RequestForEnhancement {
    int id();
    String synopsis();
    String engineer();
    String date();
}
リスト 5 アノテーション型の定義例

 

@RequestForEnhancement(
    id = 2868724;
    synopsis = "Enable time-travel";
    engineer = "Poindexter";
    date = "4/1/2004";
) public static void travelThroughTime(Date destination) { ... }
リスト 6 アノテーションの使用例

JSR-201 その他の言語仕様の変更

GenricsとMetadata以外の言語仕様の変更をまとめたものがJSR-201です。すでに本誌Vol.31にて柴田氏が解説(2)されているので、詳細はそちらをご覧ください。

また、JSR-201の実装はGenericsのリファレンスインプリメンテーションに含まれているので、今すぐ試すことが可能です。

for文の拡張

for文が拡張されC#のforeach文、VBのFor ... Each文と同様のことを行うことが可能になりました。新しいfor文の記述方を次に示します。

   for ( FormalParameter : Expression )
        Statement

Expression部で使用できるのは新たに導入されたjava.lang.Iterableインタフェースをインプリメントしたクラスか配列です。このインタフェースはコンパイラが拡張したfor文のために使用するため、通常はユーザが使用することはありません。

java.lang.Collectionインタフェースは拡張for文が使用できるように、Iterableインタフェースの派生インタフェースと定義が変更されます。

リスト1の文字列の連結は、拡張for文を使用するとリスト7のように書きかえることができます。また、Genericsと併用すればより簡潔に記述することができます(リスト8)。

 

import java.util.*;
 
public class ConcatSample3 {
    public static void main(String[] args) {
        List argList = Arrays.asList(args);
        StringBuffer buffer = new StringBuffer();
 
        for(Object o : argList) {
            buffer.append((String)o);
        }
        System.out.println(buffer.toString());
    }
}
リスト 7 拡張 for 文を使用したリスト1の書き換え

 

import java.util.*;
 
public class ConcatSample4 {
    public static void main(String[] args) {
        List argList = Arrays.asList(args);
        StringBuffer buffer = new StringBuffer();

        for(Object o : argList) {
            buffer.append(o);
        }
        System.out.println(buffer.toString());
    }
}
リスト 8 拡張 for 文と Generics を使用したリスト1の書き換え

 

タイプセーフEnum

C/C++言語で使用できるenum型(列挙型)をJavaでは使うことができませんでした。

そこで、通常はリスト9のようにstatic finalな定数を使用することが多いと思います。しかし、この方法には問題がいくつかあります。たとえば、次のコードは文法的にはすべて正しいのですが、1行目と2行目が同じものを表していることが分かりにくくなってしまいます。また、3行目は明らかに範囲外にありますが、コンパイラではチェックできません。

  int suit = SPADES; // 正しい
  suit = 3;          // 正しいが意味が分かりにくい
  suit = 10;         // 間違いだが、
                     // コンパイラはチェックできない

そこで、Joshua Bloch氏はこの問題に対してタイプセーフEnumを提案されています(3)。TigerではこのタイプセーフEnumが導入されました。

タイプセーフEnumを使用するとリスト9は次のように書くことができます。行の最後のセミコロンは必要ありません。

    public enum Suit {clubs, diamonds, hearts, spades}

タイプセーフEnumは次のよう使用することができます。

    Suit suit = Suit.clubs;

swintch 文でも使用可能です。

    Suit suit;
    ...
    switch (suit) {
      case Suit.clubs:
        ...
        break;
      case Suit.diamonds:
        ...
        break;
      ...
    }

タイプセーフEnumはコンパイラによってjava.lang.Enumクラスの派生クラスに変換されます。Enumクラスにはいくつかの定数とメソッドが定義されています。その1つに定数VALUESがあります。VALUESはタイプセーフEnumの値を要素にもつListオブジェクトと定義されています。

また、クラスとして扱われるので、コンストラクタやメソッドも定義することができます。

これらを使用したのがリスト10です。VALUESはListオブジェクトなので、拡張for文がそのまま使用できます。また、toStringメソッドを定義しているので、printlnメソッドで出力することができます。リスト10の結果を図1に示しました。

 

    public static final int CLUBS = 0;
    public static final int DIAMONDS = 1;
    public static final int HEARTS = 2;
    public static final int SPADES = 3;
リスト 9 static final を使用した定数の例

 

public class EnumSample {
    private enum Yen {SOUSEKI(1000), SHIKIBU(2000), INAZOU(5000), YUKICHI(10000);
          Yen(int value) { this.value = value; }
 
          private final int value;
 
          public String toString() {
              return value + " 円";
          }
    }
 
    public static void main(String[] args) {
        for(Yen y: Yen.VALUES) {
            System.out.println(y);
        }
    }
}
リスト 10 タイプセーフ Enum と拡張 for 文の使用例

 

リスト10の実行結果
図 1 リスト 10の実行結果

 

staticインポート

Javaではいろいろな定数が使われていますが、クラス名とペアにする必要があります。たとえば、三角関数を使うときに次のように書きます。

    double x = Math.sin(Math.PI / 180.0 * theta);

staticインポートを使用すると、これを

    double x = sin(PI / 180.0 * theta);

のようにクラス名なしでも記述することが可能になります。

staticインポートの記述法は次の2種類です。

    import static TypeName.Identifier;
    import static TypeName*.*;

上記の三角関数の例でいえば

    import static TypeName.sin;
    import static TypeName.PI;

と記述するか

    import static TypeName.*;

と記述します。

オートボクシング

コレクションはオブジェクトしか保持できないため、プリミティブ型を保持させる場合そのラッパクラスを使用せざるをえません。これはかなり煩雑です。

オートボクシングとアンボクシングはプリミティブ型とラッパオブジェクトの自動変換を行います。たとえば次のようなコードは

    Integer ten = new Integer(10);
    int nine = ten.intValue() - 1;

オートボクシング/アンボクシングを使用すると次のようになります。

    Integer ten = 10;
    int nine = ten - 1;

コレクションで使用した例を次に示します。

    List<Integer> list = new ArrayList<Integer>();
    list.add(10);
    int x = list.get(0);

オートボクシング/アンボクシングを使うことで、見かけ上プリミティブ型とラッパクラスを区別なく使えることが可能になります。実際はコンパイラがプリミティブ型とラッパクラスの変換のコードを挿入します。

可変長引数

本誌Vol.31の解説では可変長引数は触れられていなかったので、少し詳しく説明しましょう。

C/C++のprintfをJavaでも使用したいと思ったことはないでしょうか。Javaでprintfと同じようにフォーマットするにはjava.text.MessageFormat クラスなどを使用する必要がありました。

MessageFormatクラスのformatメソッドを使用したフォーマットはフォーマットしたいオブジェクトを配列にする必要があります。

    Object[] args 
        = new Object[]{"Neal", new Integer(10), "Josh"};
    MessageFormat.format(
         "Args are {0} {1, number, integer} {2}",
         args);

これではprintfに比べると煩雑になってしまいます。

詳細は後述しますが、Javaでもやっとprintfのような簡易フォーマット入出力ができるようになりました。そして、簡易フォーマット入出力に必要不可欠なのが可変長引数です。

TigerではMessageFormat#formatは可変長引数を使用するように変更されています。上記の例は次のように書くことができます。

    MessageFormat.format(
          "Args are {0} {1, number, integer} {2}",
          "Neal", 10, "Josh");

ここではオートボクシングを使用しているので、10をIntegerオブジェクトにする必要はありません。

可変長引数を使用する場合は、第2引数以降でなければなりません。可変にするには可変にする引数の型の後に "..." とピリオドを 3 つつなげて記述します。先ほどのMessageFormat#formatのシグネチャは次のようになります。

    public static String format(String pattern,
                                Object... arguments)

可変長引数を使用したメソッドの中では、引数名がargsだとするとargs[0], args[1]のように配列として扱うことができます。配列として扱えるのは、コンパイラが自動的に可変長引数を配列に変換しているためです。

その他に可変長引数が使われているものとしてリフレクションがあります。リスト11はリフレクションを利用してメソッドをコールしている例です。

ここで気になるのが引数の評価がどのように行われているかです。リスト12では、シグネチャが異なるメソッドを定義しておき、実行時にどのメソッドがコールされるかを試すものです。

リスト12を実行してみた結果を図2に示します。実行にはGenericsの リファレンスインプリメンテーションのEarly Access版を使用しています。図2からは、可変長引数を使用しないメソッドが可変長引数を使用しているものより優先的に評価されていることが分かります。

 

import java.lang.reflect.*;
import javax.swing.*;
 
public class InvokerSample {
    public static void main(String[] args) {
	JFrame frame = new JFrame();
        try {
            Class cls = Class.forName("javax.swing.JOptionPane");
 
            // 可変長引数を用いたメソッドの取得
            Method method = cls.getDeclaredMethod("showMessageDialog",
                                       java.awt.Component.class, Object.class);
            // 可変長引数を用いたメソッドの実行
            method.invoke(null, frame, "OK?");
        } catch (Exception e) {
            e.printStackTrace();
        }
	System.exit(0);
    }
}
リスト 11 可変長引数を用いたメソッド例

 

public class VarargsEvalTest {
    public static void foo(int a, int b, int c){
        System.out.println("Call foo(int a, int b, int c)");
    }

    public static void foo(int a, Integer... args){
        System.out.println("Call foo(int a, Integer... args)");
    }

    public static void foo(int a, Number... args){
        System.out.println("Call foo(int a, Number... args)");
    }

    public static void foo(int a, Object... args){
        System.out.println("Call foo(int a, Object... args)");
    }

    public static void main(String[] args) {
        foo(0);
        foo(0, 1);
        foo(0, 1, 2);
        foo(0, new Integer(1), 2);
        foo(0, 1, 2, 3);
        foo(0, 1.5);
        foo(0, "abc");
    }
}
リスト 12 可変長引数の引数評価の例

 

リスト12の実行結果
図 2 リスト 12の実行結果

 

簡易フォーマット入出力

簡易フォーマット入出力はMerlineで残された宿題です。もともとJSR-51 New I/O APIs for the Java Platformの項目の 1 つに入っていましたが、Merlineの時点では保留事項になっていたものです。

New I/Oに関しては新たにJSR-203 More New I/O APIs for the Java Platform ("NIO.2")で標準化されていますが、この項目の中には簡易フォーマット入出力は入っていないようです。

Tigerでは簡易フォーマット出力のためにFormatterクラスとFormattableインタフェースが導入されました。出力はFormatterクラスのformatメソッドで行われます。

formatメソッドではまずフォーマットを読み込んでフォーマット文字列に対応した引数を探します。その引数がFormattableインタフェースをインプリメントしているクラスであればformatメソッドをコールします。その他のクラスであればtoStringメソッドをコールします。

formatメソッドのフォーマットはprintfのフォーマットとほとんど同一です。printfと異なる点として、%zがboolean、%pがhashCode()に割り当てられているなどがあります。

まだ仕様が公開されていないのですが、JavaOneのBOFで示されたサンプルをリスト13に示しておきます。

Formatterクラスがどのようなクラスか、どのようなクラスがFormattableインタフェースをインプリメントしているか、また入力はどのように行うかなどの詳細は仕様が公開されるまでのお楽しみです。

 

public class TrigTable {
    public static void main(String[] args) {
        double rad, incr = 15;
        Formatter f = System.out();
 
        f.format("%3s %12s %12s %12s%\n",
                 "deg", "sin", "cos", "tan");
 
        for (int i = 0; i <= 360; i += incr) {
            rad = i / 180.0 * M_PI;
            f.format("%3d %12.4f %12.4f %12.4g%\n",
                     i, sin(rad), cos(rad), tan(rad));
        }

    }
}
リスト 13 簡易フォーマット出力の例

 

Monitoring and Manageability

近年のソフトウェアはどんどんと多機能化、複雑化しており、特にサーバ系のソフトウェアでは一度動き出したら長時間止まらないものが多くあります。

そこで重要となるのはいわゆるRAS (Reliability, Availability and Serviceability)と呼ばれるものです。それに加えて、CPUの専有率やメモリ使用量、レスポンスタイムなどパフォーマンスも非常に重要なテーマとなります。

そこで、ソフトウェアを管理するための機構が必要となります。Tigerでは、ソフトウェアの管理に関して次の3つのJSRを採用しています。

これ以外にもスタックトレースに関するAPIなどが強化されています。

ここではJMXはすでに標準として使われているので、JSR-163とJSR-174について解説します。しかし、JSR-174は仕様がまだ公開されていませんので、JavaOneセッションでの情報とJCPから得られる情報を基に解説します。

JSR-163 Java Platform Profiling Architecture

プロファイリングというのはシステムがどのように動作しているかを調べることで、プロファイルを行うアプリケーションをプロファイラといいます。プロファイラを使用することでアプリケーションのボトルネックやメモリリーク、デッドロックなどを発見することができます。また、システムのメモリ使用量などをモニタすることなども可能です。

Tiger以前でもプロファイルを行うためにJava Virtual Machine Profiler Interface (JVMPI)が提供されていました。これらを使用した市販のProfilerとしてBorlandのOptimizeit、Quest SoftwareのJProbe(注)などがあります。

JVMPIでもJVMのかなりの部分まで調べることができたのですが、それをより強化したのがJSR-163で策定されているJava Virtual Machine Tool Interface (JVMTI)です。

プロファイルには次の3種類の方法が考えられます。

  1. In-Processで動作し、Javaで記述する
  2. In-Processで動作し、Native Codeで記述する
  3. Out-Processで行う

JSR-163でターゲットとしているのは2と3の手法です。

既存のJVMPIは2の手法で使用されます。OptimizeitやJProbeでは VMPIを使用してJVMの情報を取得し、独自のプロトコルを使用して他のプロセスで動作するプロファイラ本体に情報を送信しています。

TigerではJVMPI の代わりにJVMTIが使用されます。

3はデバッガで使用されている手法です。現在ではJVMとのやり取りにJava Virtural Machine Debugger Interface (JVMDI)、ワイヤプロトコルにJava Debug Wire Protocol (JDWP)、リモートプロセスで使用するインタフェースとしてJava Debug Interface (JDI)が定義されています。

JVMTIはJDWP、JDIとともに使用されることも考慮されています。このため、Out-ProcessでJavaだけでプロファイラを記述することができます。

JVMTIは基本的にはJVMPIと同様にイベントドリブンなインタフェースです。主なイベントを表4に示しました。イベントの種類は多くありますが、自分の興味のあるイベントを選択することが可能です。

その他の機能として、スレッドやヒープ、クラスやオブジェクトなどにアクセスすることや情報を取得することなど多岐にわたっています。表5に主な機能を示しましたが、JVMPIではできなかったブレークポイントの設定やスレッドのインターラプトなども行えるようになりました。

 

表 4 JVMTI の主要なイベント

Single Step Breakpoint Class Load GC Start/Finsh
Thread Start/End Object Allocation/Free Method Entry/Exit Field Access/Modification
Exception      

 

表 5 JVMTI の主要な機能

分類 機能
Memory Management
Allocate/Deallocate
Thread
Get Status
Suspend/Resume
Stop/Interrupt
Stack Frame
Get Stack Trace
Heap
Force GC
分類 機能
Breakpoint
Set/Clear
Class
Get Loaded Class
Get Class Methods/Fields
Object
Get Object Size
Timer
Get Thread CPU Timer
System Properties
Get/Set System Property

 

(注) 日本での取り扱いはグレープシティもしくはタンジェントコンピューティングです。

JSR-174 Monitoring and Management Specification for the JVM

JSR-174もJVMから情報を取得するという点ではJVMTIと同様です。しかしJSR-174ではモニタリングに対象を絞ることで、JVMTIよりオーバヘッドが少なく、メモリの使用量も少ない規格になる予定です。得られる情報はJVMTIよりは少ないのですが、常にシステムと一緒に動作させることが可能です。

ルータやスイッチなどの機器ではSNMP (Simple Network Monitoring Protocol)を使用した機器管理が行われていますが、JSR-174でもSNMPをサポートしています。JVMの情報はMIBで表わされます。このため、現在使用されている機器管理システムでJavaのアプリケーションのモニタができるようになります。

JSR-174で得られるのはスレッドの一覧やステータス、メモリの使用状況、GCの情報などです。

現状のドラフト仕様では、まだどのようなインタフェースになるか決まっていないようですが、今後どうなっていくはサーバ系のアプリケーションを作成/保守されている方は要注目です。

Desktop Client

JavaOne 2003ではクライアントに再びスポットライトが当たりました。HP、DELLのPCにJ2REがプリインストールされることが発表され、現在はこれにひきつづきAcer、Gateway、Samsung、東芝などのPCにJ2REがプリインストールされる予定です。

また、クライアントでの大きな需要としてゲームが取り上げられ、JavaOne会期中にJava Game Summitも行われました。また、ゲームに欠かすことのできない低レベルグラフィックライブラリとして、OpenGLを直接サポートされることが決まっています。

Tigerでもクライアントサイドの強化が図られています。

たとえば、Java Plug-inとJava Web Startの強化や、起動時間の短縮、フットプリントの削減などが行われています。また、X Window向けにAWTをスクラッチから再構築したXAWT、GnomeのLook-and-feelなどGUIも強化されています。

また、TigerではUnicode 3.1がサポートされます。ところが、Unicode 3.1は16 bitに収まりきらない文字があるため、charなどの扱いが変更される可能性があります。この問題に対応しているのがJSR-204です。

ここでは次の3つのJSRについて説明したいと思います。

JSR-206 JAXP

TigerでサポートされるJAXPのバージョンは1.3になります(JSR-206)。JAXP 1.3がサポートしているのは次の標準です。

残念ながらWebサービス関連のJAXM、SAAJ、JAX-RPC、JAXRなどはJ2SEのコアには入らないことになったようです。

JSR-114 JDBC Rowset Implementations

JSR-114ではJDBC 3.0で導入されたRowSetインタフェースのインプリメンテーションが提供されます。すでにリファレンスインプリメンテーションのEarly Access版が以下のURLでダウンロードできます。

http://developer.java.sun.com/developer/earlyAccess/jdbc/jdbc-rowset.html

RowSetのインターフェースとして以下のようなインターフェースが定義されています。

また、リファレンスインプリメンテーションではそれぞれのコンクリートクラスが提供されています。これらのコンクリートクラスはパッケージがcom.sun.rowsetでインタフェース名にImplをつけたクラス名になっています。たとえば、JdbcRowSetインタフェースのコンクリートクラスはcom.sun.rowset.JdbcRowSetImplクラスになります。

JdbcRowSetインタフェースが基本となるインタフェースで、通常のResultSetと同様の機能を持っています。JdbcRowSetを使用した例をリスト14に示します。setterメソッドでSQL文やDBのURL、ユーザ名などを設定し、executeメソッドをコールすることでクエリが行われます。setStringメソッドで設定しているのはSQL文の?の部分です。

RowSetインタフェースはResultSetインタフェースの派生インタフェースなので、クエリ結果を参照するのは、ResultSetインタフェースを使用した場合と同様に行うことができます。

JSR-114でメインになるのがCachedRowSetインタフェースとWebRowSetインターフェースです。

CachedRowSetインタフェースはJdbcRowSetインタフェースの派生インタフェースで、その名の通りDBとのアクセスをキャッシュしてくれます。基本的な使用法はJdbcRowSetと同じです。

値を挿入したり、更新したりしても、キャッシュに対して更新するだけでDBへのアクセスは行われません。最終的にacceptChangesメソッドをコールすることでDBに反映されます。

WebRowSetインタフェースはChcedRowSetインタフェースの派生インタフェースで、RowSetインタフェースをインプリメントしたオブジェクトをXMLドキュメントとして読み込み/書き込みを行うことができます。

この際、使用されるXMLドキュメントはXML Schemaで定義されています。この定義は以下のURLで参照することができます。

http://java.sun.com/xml/ns/jdbc/webrowset.xsd

WebRowSetインタフェースで定義されているのはreadXML/writeXMLメソッドです。ストリーム、reader/writerから読み込み/書き込みを行うことができます。

実際にどのようなXMLドキュメントがやり取りされるかはJSR-114の仕様に記載されています。

http://jcp.org/en/jsr/detail?id=114

 

    JdbcRowSet jrs = new JdbcRowSetImpl();
    jrs.setCommand("SELECT * FROM TITLES WHERE TYPE = ?");
    jrs.setURL("jdbc:myDriver:myAttribute");
    jrs.setUsername("cervantes");
    jrs.setPassword("sancho");
    jrs.setString(1, "BIOGRAPHY");
    jrs.execute();
リスト 14 JdbcRowSetインタフェースの使用例

 

JSR-166 Concurrency Utilities Library

ここまで説明してきた新機能のJSR はほとんどはSun主導で行われているものですが、JSR-166はニューヨーク州立大学のDoug Leaがまとめ役になっています。Doug Leaといえば「Javaスレッドプログラミング」(3)の著者であり、並列プログラミングでは著名人です。JSR-166は彼が作成した並列プログラミング用ライブラリがベースとなっており、以下のURLでPreliminary Test版がダウンロードできます。

http://gee.cs.oswego.edu/dl/concurrency-interest/jsr166/

JSR-166では以下のような機能が提供されています。

このほかにもAtomicやスレッドセーフなコレクションなども提供されています。

非同期処理、スレッドプール

非同期に処理を行う場合、Threadクラスを派生させるかRunnableインタフェースをインプリメントしたクラスを作成する必要がありました。JSR-166ではもっと簡単に非同期処理を行うためにjava.util.concurrent.Executorインタフェースとjava.util.concurrent.Executorsクラスが導入されています。また、Runnableと同等のインタフェースとしてjava.util.concurrent.Callableが新たに作られています。

Executorクラスはタスクを実行するためのインタフェースです。もっとも単純なインプリメントはリスト15に示したDirectExecutorクラスのようになります。DirectExecutorクラスは単に引数で与えられたRunnableオブジェクトのrunメソッドをコールするだけです。

JSR-166ではExecutorインタフェースをインプリメントしたクラスとしてjava.util.concurrent.ThreadPoolExecutorクラスが提供されています。このクラスを使えばその名のとおりスレッドプールを実現することができます。

リスト16がExecutorの使用例です。

Executorインタフェースを使用して、実際に同期/非同期処理を行うのがExecutorsクラスです。Executorsクラスのinvokeメソッドを使用すれば同期、executeメソッドを使用すれば非同期に実行してくれます。

invokeメソッド、executeメソッドとも第1引数はExecutorインタフェースをインプリメントしたオブジェクトです。第2引数が実際に行う処理を表わすRunnableインタフェースもしくはCallableインタフェースをインプリメントしたオブジェクトです。リスト16ではCallableインタフェースを使用しています。

RunnableインタフェースとCallableインタフェースは非常に似ています。両者の違いは、Runnableインタフェースのrunメソッドが戻り値がないのに対し、Callableインタフェースのcallメソッドが戻り値を設定することが可能という点です。

Callableインタフェースを使用して非同期実行を行った結果は、executeメソッドの戻り値のFutureTaskオブジェクトのgetメソッドで得ることができます。

 

public class DirectExecutor implements Executor {
    public void execute(Runnable r) {
        r.run();
    }
}
リスト 15 Executorインタフェースのインプリメント例

 

import java.util.concurrent.*;
 
public class ExecutorSample {
    public void executeCallable() throws ExecutionException, InterruptedException {
        Executor e = new DirectExecutor();
        Future future = Executors.execute(e, new Task());
        String result = future.get();
        System.out.println("Execution Result: " + result);
    }
 
    public void invokeCallable () throws ExecutionException, InterruptedException {
        Executor e = new DirectExecutor();
        String result = Executors.invoke(e, new Task());
        System.out.println("Invocation Result: " + result);
    }
 
    private static class Task implements Callable {
        public String call() {
            return "Good";
        }
    }
 
    public static void main(String[] args) {
        try {
            ExecutorSample sample = new ExecutorSample();
            sample.executeCallable();
            sample.invokeCallable();
        } catch (Exception ex) {
            ex.printStackTrace();
        }
    }
}
リスト 16 Executorの使用例

 

キュー、ブロッキングキュー

キューはFIFO (first-in-first-out)という特徴を持つコレクションの一種です。キューを実現するためにjava.util.Queueインタフェースが導入されています。QueueインタフェースはCollectionインターフェースの派生クラスとして定義されています。

キューに要素を追加するときにはaddメソッドを使用しますが、要素を取得する場合はpeekメソッド、もしくはpollメソッドを使用します。peekメソッドを使用した場合要素はキューに残りますが、pollメソッドでは取り除かれるという違いがあります。

Queueインタフェースをインプリメントしたクラスとしてjava.util.LinkedListクラス、優先度をつけられるPriorityQueueクラス、またスレッドセーフなjava.util.concurrent.LinkedQueueクラスが提供されています。

Queueインタフェースの一種としてBlockingQueueインタフェースも提供されています。BlockingQueueインタフェースでは要素の追加や取得がブロッキングされます。

たとえば、ArrayBlockingQueueクラスは容量を持っており、要素の追加時に領域がなければ、領域があくまでブロッキングします。

これを使用すればこれまでwait-notifyを使用してブロッキングしていた処理をとても簡単に書くことができます。

時間表現

非同期処理を行う場合、頻繁に使われるのがタイムアウトまでの時間です。JSR-166ではこの時間を指定するのにjava.util.concurrent.TimeUnitクラスを使用します。TimeUnitクラスで表現できるのは秒、ミリ秒だけでなく、マイクロ秒、ナノ秒も表わすことができます。

TimeUnitは時間を表わすクラスですが、java.util.DateクラスやSystem.currentTimeMillisメソッドとの依存関係はありません。

ロック

リソースへのロックを行うのがjava.util.concurrent.locks.Lockインタフェースです。synchronizedで行っていたロックより柔軟にロックを行うことができます。基本的には次のように使用します。

    lock.lock();
    try {
        ....
    } finally {
        lock.unlock();
    }

LockインタフェースをインプリメントしたクラスとしてReentrantLockクラスが提供されています。

通常のlockメソッドではロックがかかるまで処理がブロックしますが、ブロックしないtryLockメソッドもあります。また、ロックを割り込み可能にする場合lockInterruptiblyメソッドを使用します。

ロックの一種としてReadWriteLockインタフェースとReentrantReadWriteLockクラスも提供されています。これらはロックの対象となるリソースに対して、複数のスレッドで読み込みが可能であるが、書き込みは単一のスレッドでしか許さないようなロックを提供します。

同期機構

いままで、Javaで同期を取るためにはsynchronizedしか使用できませんでしたが、JSR-166ではセマフォなど同期を行うためのクラスが提供されています。提供されているクラスは次の 4 種類です。

ここではセマフォについて説明します。

セマフォは内部にカウンタを持ち、決められた上限までリソースの共有を可能にします。

Semaphoreクラスではacquireメソッドでセマフォを得ることができます。acquireメソッドをコールするとカウンタが1だけインクリメントします。カウンタが上限にあるときは、誰かがセマフォを開放するまで処理がブロックされます。セマフォが不要になった場合はreleaseメソッドで開放することができます。

上限が1のセマフォはロックと同様とみなすことができます。また、SemaphoreクラスはLockインタフェースやsynchronizedと一緒に使用することが可能です。

おわりに

これほど変更点が多岐にわたったバージョンアップは過去例がないのではないでしょうか。今回紹介した機能以外にも多くの機能が盛り込まれる予定です。それだけすべてを把握するのは難しいかもしれません。

それでも、Genericsをはじめとする言語仕様の変更とConcurrency Utilitiesだけはぜひとも抑えておきたいところです。今までプログラムを書くのが面倒であった部分が、とても簡単に書くことができます。ぜひEase of Developmentを味わってみてください。

これらの変更点は他言語、特にC#などに由来するものが多いように思います。Javaのいいところを取り入れてC#が作られたように、Javaも他言語のいいところがあればどんどん取り入れてしまってもかまわないのではないかと筆者は思います。

 

参考文献

  1. 柴田 芳樹 「Java Generics」 JAVA PRESS Vol.28, 技術評論社
    JAVA PRESS Vol.30 の付属 CD-ROM にも PDF 版が収録されています
  2. 柴田 芳樹 「JSR201 Java言語仕様拡張の概要」 JAVA PRESS Vol.31, 技術評論社
  3. Joshua Block 「Effective Java プログラミング言語ガイド」ピアソン・エデュケーション ISBN 4-89471-436-1
  4. Doug Lea 「Javaスレッドプログラミング―並列オブジェクト指向プログラミングの設計原理」翔泳社 ISBN 4-88135-918-5