akios @ ウィキ

4.8. 未加工型

最終更新:

akios

- view
管理者のみ編集可

4. 型と値と変数

4.1. 型と変数の種類

4.2. プリミティブ型と値

4.3. 参照型と値

4.4. 型変数

4.5. 引数付き型

4.6. 型の抹消

4.7. 具象可能型

4.8. 未加工型

非ジェネリックのレガシーコードとのインタフェースを容易にするため、引数付き型抹消や構成要素の引数付き型を抹消した配列型を型として利用できます。このような型を未加工型(raw type)と呼びます。

もう少し正確には、未加工型とは次の1つとして定義されます。:
  • 型実引数リストを持たないジェネリック型宣言の名前という形式の参照型
  • 構成要素の型が未加工型である配列型
  • 未加工型Rの非静的メンバー型、Rのスーパークラスやスーパーインタフェースから継承されていないこと

非ジェネリッククラス型やインタフェース型は未加工型ではありません。

未加工型の非静的型メンバーがなぜ未加工とみなせるのか、以下の例を考えます。:

class Outer<T>{
    T t;
    class Inner {
        T setOuterT(T t1) { t = t1; return t; }
    }
}

Innerのメンバーの型はOuterの型引数に依存しています。もしOuterが未加工であるなら、InnerTに対する有効な束縛がないため同様に未加工として扱わなければなりません。

このルールは継承していない型メンバーの場合のみ適用可能です。型変数に依存する継承された型メンバーは未加工型のスーパータイプを抹消するというルールにより生まれた未加工型として継承されます。これはこの説の後で記述します。

上記のルールともう1つ密接に関係するのが未加工型のジェネリック内部クラスはそれ自身未加工型としてのみ使用できるということです。:

class Outer<T>{
    class Inner<S> {
        S s;
    }
}

部分的な未加工型(生焼け型(rare type))であるInnerにはアクセスできません。:

Outer.Inner<Double> x = null;  // illegal
Double d = x.s;

なぜならば、Outer自身は未加工のため、Innerを含むその全ての内部クラスも未加工となるので、Innerへ型実引数を渡すことができなくなるためです。

未加工型のスーパークラス(とスーパーインタフェース)はその引数付き呼び出しにおけるスーパークラス(スーパーインタエース)の抹消です。

スーパークラスやスーパーインタフェースから継承されない未加工型Cのコンストラクター、インスタンスメソッド(8.4.9.4.)もしくは非静的フィールドMの型は未加工型です。その未加工型はCと対応したジェネリック宣言内のその型の抹消に該当するものです。

未加工型Cの静的メソッドや静的フィールドの型はCと対応したジェネリック宣言内のその型と同じです。

型実引数を未加工型のスーパークラスやスーパーインタフェースから継承されない非静的型メンバーに渡そうとするとコンパイル時にエラーとなります。

引数付き型の型メンバーを未加工型として使用しようとするとコンパイル時にエラーとなります。

これは"生焼け(rare)"型の追放を限定型が引数付きであるケースにも拡張していることを意味していますが、内部クラスを未加工型として使用しようとすると以下のようになります。:

Outer<Integer>.Inner x = null; // illegal

これは上で述べたことの逆です。この半分焼けた(half-baked)型の実践的正当化はありません。レガシーコード内では、型実引数は使われません。非レガシーコード内では、ジェネリック型を正しく使い必要とされる全ての型実引数を与えるべきです。

クラスのスーパータイプは未加工型であっても構いません。そのクラスへのメンバーアクセスは通常と同じに扱われ、スーパータイプへのメンバーアクセスは未加工型に対するように扱われます。クラスのコンストラクター内では、superの呼び出しを未加工型のメソッド呼び出しとして扱います。

未加工型の使用はレガシーコードとの互換性を得るための譲歩でしかありません。Javaプログラミング言語にジェネリクスが導入されて以降に書かれたコード内での未加工型の使用は強く反対します。Javaプログラミング言語の将来のバージョンでは未加工型の使用は禁止されるでしょう。

型ルールの潜在的違反を常に廃絶することを確かにするために、未加工型のメンバーへアクセスがあれば、コンパイル時に未検査警告が出力されます。未加工型のコンストラクターのメソッドをアクセスする際のコンパイル時の未検査警告のルールは以下の通りです。:
  • フィールドへの代入の時点: 左オペランドの型が未加工型なら、抹消によりフィールドの型が変更されるとして未検査警告をコンパイル時に出力する。
  • メソッドやコンストラクターの呼び出しの時点: 検索するクラスやインタフェースの型が未加工型なら、抹消によりメソッドやコンストラクターの仮引数の型が変更されるとして未検査警告をコンパイル時に出力する。
  • 抹消があっても仮引数の型に変更がないメソッド呼び出し(戻り型やスロー節に変更があるとしても)について、フィールドの読出しについて、もしくは未加工型のクラスインスタンスの作成については、コンパイル時に未検査警告を出力しない。

上記の未検査警告は未検査変換キャスト、メソッド宣言(8.4.1.8.4.8.3.8.4.8.4.9.4.1.2.)、可変項数メソッド呼び出しで発生する未検査警告とは異なります。

ここでの警告はレガシーコード開発者がジェネリックライブラリを使用した場合を想定したものです。例えば、ライブラリはVector<T>型のフィールドfを持つジェネリッククラスFoo<T extends String>を宣言します。しかし開発者はFooの未加工型であるeに対し&(){e.fで整数のベクターを代入します。これはジェネリックス使用ライブラリのジェネリック使用プログラムがヒープ汚染を発生させるということでレガシーコード開発者は警告を受け取ります。

(レガシーコード開発者はライブラリからVector<String>を自身のVector変数に警告を受けることなく代入できます。つまり、Javaプログラミン言語のサブタイプ化ルールでは未加工型の変数に型の引数付けされたどのようなインスタンスに対する値も代入することができます。)

未検査変換による警告はレガシーライブラリーを使用するジェネリクス開発者が遭遇する2つのケースをカバーします。例えば、ライブラリのメソッドはVector型の未加工型を返し、使用者側はメソッド呼び出しの結果をVector<String>型の変数に代入します。これは、未加工ベクターはString以外の要素の型を持っているかもしれないので安全ではありません。しかし、レガシーコードとのインタフェースを保つため未検査変換はまだ使用できるようになっています。未検査変換による警告はジェネリクス開発者がプログラムの違う箇所でヒープ汚染を起こすという問題が発生するかもしれないということを喚起しています。

例4.8-1. 未加工型

class Cell<E> {
    E value;

    Cell(E v)     { value = v; }
    E get()       { return value; }
    void set(E v) { value = v; }

    public static void main(String[] args) {
        Cell x = new Cell<String>("abc");
        System.out.println(x.value);  // OK, has type Object
        System.out.println(x.get());  // OK, has type Object
        x.set("def");                 // unchecked warning
    }
}

例4.8-2. 未加工型と継承

import java.util.*;
class NonGeneric {
    Collection<Number> myNumbers() { return null; }
}

abstract class RawMembers<T> extends NonGeneric
                             implements Collection<String> {
    static Collection<NonGeneric> cng =
        new ArrayList<NonGeneric>();

    public static void main(String[] args) {
        RawMembers rw = null;
        Collection<Number> cn = rw.myNumbers();
                              // OK
        Iterator<String> is = rw.iterator();
                              // Unchecked warning
        Collection<NonGeneric> cnn = rw.cng;
                              // OK, static member
    }
}

このプログラムは、RawMembers<T>は以下のメソッドを:

Iterator<String> iterator()

スーパーインターフェースであるCollection<String>から継承しています。しかし、RawMembers型はCollection<String>の抹消からiterator()を継承しています。これはiterator()の戻り型はIterator<String>の抹消であるIteratorであることを意味します。

結果として、rw.iterator()からの代入はIteratorからIterator<String>への未検査変換を要求し、未検査警告が発生します。

逆に、静的メンバーcngは未加工型のオブジェクトへのアクセスが完全な引数付き型で表されています。(インスタンスへの静的メンバーを通したアクセスは悪いスタイルと考えられており推奨されません。)メンバーmyNumbersNonGenericクラス(抹消もNonGeneric)より継承されていますので完全な引数付き型です。

未加工型はワイルドカードと密接に関係しています。両方とも実在型に基づいています。未加工型はレガシーコードとの相互接続を達成するために型規則を故意に不安定にしたワイルドカードとみなせます。歴史的には、未加工型はワイルドカードより先に出現しました。最初に紹介したのはGJで、論文Making the future safe for the past: dding Genericity to the Java Programming Languageで著者はGilad Bracha、Martin Odersky、David Stoutamire、Philip WadlerでObject-Oriented Programming, Systems, Languages and Applications (OOPSLA 98), 1998年10月のACM会議会議録の中で述べられています。

4.9. 交差型

4.10. サブタイプ化

4.11. 型の使用箇所

4.12. 変数

目安箱バナー