web-dev-qa-db-ja.com

Javaで不変オブジェクトを識別する方法

私のコードでは、オブジェクトが不変である場合にのみ安全な方法でさまざまなスレッドによってアクセスされるオブジェクトのコレクションを作成しています。コレクションに新しいオブジェクトを挿入しようとすると、それが不変かどうかをテストしたいと思います(そうでない場合は、例外をスローします)。

私ができることの1つは、いくつかのよく知られた不変タイプをチェックすることです。

private static final Set<Class> knownImmutables = new HashSet<Class>(Arrays.asList(
        String.class, Byte.class, Short.class, Integer.class, Long.class,
        Float.class, Double.class, Boolean.class, BigInteger.class, BigDecimal.class
));

...

public static boolean isImmutable(Object o) {
    return knownImmutables.contains(o.getClass());
}

これにより実際には90%の方法が得られますが、ユーザーは独自の不変タイプを作成したい場合があります。

public class ImmutableRectangle {
    private final int width;
    private final int height;
    public ImmutableRectangle(int width, int height) {
        this.width = width;
        this.height = height;
    }
    public int getWidth() { return width; }
    public int getHeight() { return height; }
}

クラスが不変かどうかを確実に検出できる方法(おそらくリフレクションを使用)はありますか?偽陽性(そうでない場合は不変であると考える)は受け入れられませんが、偽陰性(そうでない場合は変更可能であると考える)は受け入れられます。

編集して追加しました:洞察に満ちた役立つ回答をありがとうございます。いくつかの回答が指摘したように、私はセキュリティ対策方針の定義を怠っていました。ここでの脅威は無知な開発者です-これは、スレッド化についてほとんど知らず、ドキュメントを読んでいない多数の人々によって使用されるフレームワークコードの一部です。私は悪意のある開発者から守る必要はありません- 文字列を変更する または他の悪意のある行為を実行するのに十分賢い人は、この場合安全でないことを知るのに十分賢くなります。コードベースの静的分析IS自動化されている限りオプションですが、すべてのレビューにスレッド対応のレビュー担当者がいる保証がないため、コードレビューは期待できません。

42
mcherm

クラスが不変であるかどうかを検出する信頼できる方法はありません。これは、クラスのプロパティが変更される可能性のある方法が非常に多くあり、リフレクションではすべてを検出できないためです。

これに近づく唯一の方法は:

  • 不変の型の最終的なプロパティのみを許可します(既知のプリミティブ型とクラスは不変です)。
  • クラス自体がfinalであることを要求する
  • あなたが提供する基本クラスから継承することを要求する(これは不変であることが保証されている)

次に、所有しているオブジェクトが不変かどうかを次のコードで確認できます。

static boolean isImmutable(Object obj) {
    Class<?> objClass = obj.getClass();

    // Class of the object must be a direct child class of the required class
    Class<?> superClass = objClass.getSuperclass();
    if (!Immutable.class.equals(superClass)) {
        return false;
    }

    // Class must be final
    if (!Modifier.isFinal(objClass.getModifiers())) {
        return false;
    }

    // Check all fields defined in the class for type and if they are final
    Field[] objFields = objClass.getDeclaredFields();
    for (int i = 0; i < objFields.length; i++) {
        if (!Modifier.isFinal(objFields[i].getModifiers())
                || !isValidFieldType(objFields[i].getType())) {
            return false;
        }
    }

    // Lets hope we didn't forget something
    return true;
}

static boolean isValidFieldType(Class<?> type) {
    // Check for all allowed property types...
    return type.isPrimitive() || String.class.equals(type);
}

pdate:コメントで提案されているように、特定のクラスをチェックする代わりに、スーパークラスで再帰するように拡張できます。 isValidFieldTypeメソッドでisImmutableを再帰的に使用することも提案されました。これはおそらく機能し、私もいくつかのテストを行いました。しかし、これは簡単なことではありません。 isImmutableを呼び出してすべてのフィールドタイプをチェックすることはできません。これは、Stringがすでにこのテストに失敗しているためです(そのフィールドhashは最終ではありません)。また、無限再帰が発生しやすくなり、StackOverflowErrors;)ジェネリックが原因で他の問題が発生する可能性があります。

いくつかの作業で、これらの潜在的な問題は何とか解決されるかもしれません。しかし、その場合、まずそれが本当に価値があるかどうか(また、パフォーマンスについても)を自問する必要があります。

29
Simon Lehmann

Immutable アノテーションを Java Concurrency in Practice から使用します。ツール FindBugs は、変更可能であるはずのないクラスを検出するのに役立ちます。

29
Benno Richters

私の会社では、@Immutableという属性を定義しています。それをクラスにアタッチすることを選択した場合、それは不変であることを約束することを意味します。

これはドキュメンテーションで機能し、あなたの場合はフィルターとして機能します。

もちろん、あなたは依然として作者が彼のWordを不変であることを維持することに依存していますが、作者が注釈を明示的に追加したので、それは合理的な仮定です。

9
Jason Cohen

基本的にはありません。

受け入れられたクラスの巨大なホワイトリストを作成することもできますが、コレクションのドキュメントを書くだけで、このコレクションがすべてこのコレクションになる必要がある不変。

編集:他の人々は不変のアノテーションを持つことを提案しました。これは問題ありませんが、ドキュメントも必要です。それ以外の場合、人々は「このアノテーションをクラスに置いた場合、コレクションに格納できる」と考え、不変クラスと可変クラスのすべてにそれをチャックします。実際、人々がその注釈がクラスを不変にすると考える場合に備えて、私は不変の注釈を持つことに警戒しています。

8
SCdF

これは別のヒントになるかもしれません:

クラスにセッターがない場合、クラスを変更することはできません。クラスの作成に使用されたパラメーターが「プリミティブ」タイプであるか、またはそれ自体が変更可能でない場合。

また、メソッドをオーバーライドすることはできません。すべてのフィールドはfinalおよびprivateです。

明日は何かをコーディングするつもりですが、リフレクションを使用したSimonのコードはかなりよさそうです。

とりあえず、Josh Blockの "Effective Java"の本を入手してみてください。このトピックに関連する項目があります。 isは確実に不変クラスを検出する方法を述べていませんが、良いクラスを作成する方法を示しています。

アイテムは「不変の好意」と呼ばれます

リンク: http://Java.Sun.com/docs/books/effective/

4
OscarRyz

私のコードでは、オブジェクトが不変である場合にのみ安全な方法でさまざまなスレッドによってアクセスされるオブジェクトのコレクションを作成しています。

質問に対する直接の回答ではありませんが、不変のオブジェクトは、スレッドセーフであることが自動的に保証されるわけではないことに注意してください(悲しいことに)。コードは、スレッドセーフになるために副作用のないものである必要があり、それはかなり困難です。

次のクラスがあるとします。

_class Foo {
  final String x;
  final Integer y;
  ...

  public bar() {
    Singleton.getInstance().foolAround();
  }
}
_

次に、foolAround()メソッドに、スレッドセーフではない操作が含まれている場合があり、これによりアプリが爆発します。また、リフレクションを使用してこれをテストすることはできません。実際の参照はメソッド本体にのみあり、フィールドや公開されたインターフェースにはありません。

それ以外は正しいです。クラスの宣言されたすべてのフィールドをスキャンし、それらすべてがfinalで不変のクラスであるかどうかを確認すれば完了です。メソッドがfinalである必要はないと思います。

また、依存フィールドの不変性を再帰的にチェックする場合は注意が必要です。最終的に次のように円が表示される場合があります。

_class A {
  final B b; // might be immutable...
}

class B {
  final A a; // same so here.
}
_

クラスAとBは完全に不変です(場合によっては、リフレクションハックによっても使用可能です)、単純な再帰コードは、A、B、Aの順にチェックする無限ループに入り、B、...

サイクルを許可しない「目に見える」マップを使用して、または依存先がすべて自分自身にのみ不変である場合にクラスが不変であると判断するいくつかの本当に賢いコードを使用して、これを修正できますが、それは本当に複雑になります...

4
Martin Probst

AOPと @Immutablejcabi-aspects からの注釈:

@Immutable
public class Foo {
  private String data;
}
// this line will throw a runtime exception since class Foo
// is actually mutable, despite the annotation
Object object = new Foo();
3
yegor256

なぜすべての推奨事項でクラスが最終である必要があるのですか?リフレクションを使用して各オブジェクトのクラスをチェックし、プログラムでそのクラスが不変(不変の最終フィールド)であると判断できる場合は、クラス自体が最終であることを要求する必要はありません。

3
james

次のように、クライアントにメタデータ(注釈)を追加し、実行時にそれらをリフレクションでチェックするように要求できます。

メタデータ:

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.CLASS)
public @interface Immutable{ }

クライアントコード:

@Immutable
public class ImmutableRectangle {
    private final int width;
    private final int height;
    public ImmutableRectangle(int width, int height) {
        this.width = width;
        this.height = height;
    }
    public int getWidth() { return width; }
    public int getHeight() { return height; }
}

次に、クラスでリフレクションを使用して、アノテーションがあるかどうかを確認します(コードを貼り付けますが、ボイラープレートであり、オンラインで簡単に見つけることができます)。

3
Pablo Fernandez

他の回答者がすでに述べたように、IMHOはオブジェクトが本当に不変かどうかを確認する信頼できる方法はありません。

追加するときにチェックするためのインターフェイス「Immutable」を紹介します。これは、何らかの理由で不変オブジェクトのみを挿入する必要があるというヒントとして機能します。

interface Immutable {}

class MyImmutable implements Immutable{...}

public void add(Object o) {
  if (!(o instanceof Immutable) && !checkIsImmutableBasePrimitive(o))
    throw new IllegalArgumentException("o is not immutable!");
  ...
}
2
Daniel Hiller

私の知る限りでは、100%正しい不変オブジェクトを識別する方法はありません。しかし、私はあなたをより近づけるためにライブラリを書きました。クラスのバイトコードの分析を実行して、それが不変であるかどうかを判断し、実行時に実行できます。これは厳密な側面にあるため、既知の不変クラスをホワイトリストに登録することもできます。

あなたはそれをチェックアウトすることができます: www.mutabilitydetector.org

アプリケーションで次のようなコードを記述できます。

/*
* Request an analysis of the runtime class, to discover if this
* instance will be immutable or not.
*/
AnalysisResult result = analysisSession.resultFor(dottedClassName);

if (result.isImmutable.equals(IMMUTABLE)) {
    /*
    * rest safe in the knowledge the class is
    * immutable, share across threads with joyful abandon
    */
} else if (result.isImmutable.equals(NOT_IMMUTABLE)) {
    /*
    * be careful here: make defensive copies,
    * don't publish the reference,
    * read Java Concurrency In Practice right away!
    */
}

これは、Apache 2.0ライセンスの下で無料でオープンソースです。

1
Grundlefleck

これを試して:

public static boolean isImmutable(Object object){
    if (object instanceof Number) { // Numbers are immutable
        if (object instanceof AtomicInteger) {
            // AtomicIntegers are mutable
        } else if (object instanceof AtomicLong) {
            // AtomLongs are mutable
        } else {
            return true;
        }
    } else if (object instanceof String) {  // Strings are immutable
        return true;
    } else if (object instanceof Character) {   // Characters are immutable
        return true;
    } else if (object instanceof Class) { // Classes are immutable
        return true;
    }

    Class<?> objClass = object.getClass();

    // Class must be final
    if (!Modifier.isFinal(objClass.getModifiers())) {
            return false;
    }

    // Check all fields defined in the class for type and if they are final
    Field[] objFields = objClass.getDeclaredFields();
    for (int i = 0; i < objFields.length; i++) {
            if (!Modifier.isFinal(objFields[i].getModifiers())
                            || !isImmutable(objFields[i].getType())) {
                    return false;
            }
    }

    // Lets hope we didn't forget something
    return true;
}
1
M. Tempesta

Javaの機能の実装である joe-e を確認してください。

0
spoerri

Grundlefleckが彼の可変性検出器に費やした仕事の量に感謝し、賞賛しますが、それは少しやり過ぎだと思います。次のように、単純ですが実際には非常に適切な(つまり、pragmatic)検出器を記述できます。

(注:これは私のコメントのコピーです https://stackoverflow.com/a/28111150/77311

まず、クラスが不変かどうかを判断するメソッドを記述するだけではありません。代わりに、不変状態検出クラスを記述する必要があります。これは、状態を維持する必要があるためです。検出器の状態は、これまでに調べたすべてのクラスの検出された不変性です。これはパフォーマンスに役立つだけでなく、クラスに循環参照が含まれている可能性があるため実際に必要です。これにより、単純化不変性検出器が無限再帰に陥ります。

クラスの不変性には、UnknownMutableImmutableCalculatingの4つの値があります。おそらく、これまでに遭遇した各クラスを不変性の値に関連付けるマップが必要になるでしょう。もちろん、Unknownは実際には実装する必要はありません。これは、まだマップにないクラスの暗黙の状態になるためです。

したがって、クラスの調査を開始するときは、クラスをマップ内のCalculating値に関連付け、完了したら、CalculatingImmutableまたはMutableのいずれかに置き換えます。

クラスごとに、コードではなくフィールドメンバーのみをチェックする必要があります。バイトコードをチェックするという考えはかなり見当違いです。

まず、クラスが最終かどうかをしないことを確認する必要があります。クラスの最終性は、その不変性に影響を与えません。代わりに、不変パラメーターを期待するメソッドは、最初に不変性検出器を呼び出して、渡された実際のオブジェクトのクラスの不変性をアサートする必要があります。パラメータのタイプが最終クラスである場合、このテストは省略できます。そのため、ファイナリティはパフォーマンスに優れていますが、厳密には必要ありません。また、後で見ていくように、型が非最終クラスであるフィールドにより、宣言クラスは変更可能と見なされますが、それでも、それは非最終の問題ではなく、宣言クラスの問題です不変のメンバークラス。不変クラスの高い階層を持つことは完全に問題ありません。その場合、すべての非リーフノードはもちろん非最終ノードでなければなりません。

フィールドが非公開であるかどうかをしないでください。クラスがパブリックフィールドを持つことは完全に問題なく、フィールドの可視性は、宣言クラスの不変性に何らかの形、形、または形式で影響を与えることはありません。フィールドがfinalであり、その型が不変であるかどうかを確認するだけで済みます。

クラスを調べるとき、最初にやりたいことは、再帰してsuperクラスの不変性を判別することです。スーパーが変更可能である場合、子孫も定義により変更可能です。

次に、allフィールドではなく、クラスのdeclaredフィールドを確認するだけで済みます。

フィールドがfinalではない場合、クラスは変更可能です。

フィールドがfinalであるが、フィールドのタイプが変更可能な場合、クラスは変更可能です。 (配列は定義により変更可能です。)

フィールドがfinalで、フィールドのタイプがCalculatingの場合、それを無視して次のフィールドに進みます。すべてのフィールドが不変またはCalculatingの場合、クラスは不変です。

フィールドのタイプがインターフェース、抽象クラス、または非最終クラスである場合、実際の実装が何を行うかを完全に制御できないため、変更可能と見なされます。これは、UnmodifiableCollection内の変更可能なコレクションをラップしても不変性テストに失敗することを意味するため、乗り越えられない問題のように見えるかもしれませんが、実際には問題なく、次の回避策で処理できます。

一部のクラスには、final以外のフィールドが含まれていても、事実上不変である場合があります。この例は、Stringクラスです。このカテゴリに分類されるその他のクラスは、純粋にパフォーマンスモニタリングの目的で最終メンバー以外を含むクラス(呼び出しカウンターなど)、popsicle不変性を実装するクラス(ルックアップ) 、および副作用を引き起こさないことがわかっているインターフェースであるメンバーを含むクラス。また、クラスに本当の可変フィールドが含まれているが、hashCode()とequals()を計算するときにそれらを考慮に入れないと約束した場合、クラスはマルチスレッドに関してはもちろん安全ではありませんが、マップのキーとして使用するための不変。したがって、これらのケースはすべて、次の2つの方法のいずれかで処理できます。

  1. 手動でクラス(およびインターフェース)を不変性検出器に追加します。特定のクラスの不変性テストが失敗したにもかかわらず、特定のクラスが事実上不変であることがわかっている場合は、クラスをImmutableに関連付けるエントリを手動で検出器に追加できます。このように、検出器はそれが不変であるかどうかをチェックしようとすることは決してなく、常に「はい、そうです」と言うだけです。

  2. @ImmutabilityOverrideアノテーションの紹介。不変性検出器は、フィールドにこの注釈が存在するかどうかを確認できます。存在する場合、フィールドが非最終であるか、その型が変更可能であるにもかかわらず、フィールドを不変として扱う場合があります。検出器は、このアノテーションがクラスに存在するかどうかをチェックすることもできるため、フィールドをチェックする手間をかけることなく、クラスを不変として扱います。

これが将来の世代に役立つことを願っています。

0
Mike Nakis

組み込みクラスの高い割合で機能するものは、instanceof Comparableのテストです。 Dateのように不変ではないクラスの場合、ほとんどの場合、それらは不変として扱われます。

0
Peter Lawrey