45

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

私にできることの 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; }
}

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

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

4

15 に答える 15

30

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

これに近づく唯一の方法は次のとおりです。

  • 不変の型 (不変であることがわかっているプリミティブ型とクラス) の最終プロパティのみを許可し、
  • クラス自体が 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);
}

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

いくつかの作業を行うことで、これらの潜在的な問題が何らかの形で解決される可能性があると思います. ただし、最初に、それが本当に価値があるかどうかを自問する必要があります (パフォーマンスに関しても)。

于 2008-10-15T02:52:21.130 に答える
30

Java Concurrency in PracticeのImmutableアノテーションを使用します。ツールFindBugsは、ミュータブルであるがミュータブルであってはならないクラスを検出するのに役立ちます。

于 2008-10-15T08:24:14.203 に答える
9

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

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

もちろん、作成者が不変であるという彼の言葉を守っていることに依然依存していますが、作成者が明示的に注釈を追加したため、それは妥当な仮定です。

于 2008-10-15T02:14:18.087 に答える
8

基本的にいいえ。

受け入れられたクラスの巨大なホワイトリストを作成することもできますが、コレクションのドキュメントに、このコレクションは不変でなければならないということを書くのが、あまりクレイジーではない方法だと思います。

編集:他の人は、不変の注釈を持つことを提案しています。これは問題ありませんが、ドキュメントも必要です。そうしないと、人々は「このアノテーションを自分のクラスに付ければ、コレクションに保存できる」と考えて、不変クラスと可変クラスを問わず、何でもそれをチャックします。実際、注釈によってクラスが不変になると人々が考える場合に備えて、不変の注釈を持つことには慎重です。

于 2008-10-15T02:12:03.893 に答える
4

これは別のヒントになる可能性があります。

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

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

明日は何かコードを書いてみますが、リフレクションを使ったサイモンのコードはかなり良さそうです。

それまでの間、Josh Block による「Effective Java」の本を入手してみてください。このトピックに関連する項目があります。不変のクラスを検出する方法を確実に述べているわけではありませんが、適切なクラスを作成する方法を示しています。

アイテムの名前: 「Favor immutability」

リンク: http://java.sun.com/docs/books/effective/

于 2008-10-15T03:34:50.043 に答える
4

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

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

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

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

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

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

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

また、依存フィールドの不変性を再帰的にチェックすることに注意してください。円になってしまう可能性があります。

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

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

クラス A と B は完全に不変です (そして、いくつかのリフレクション ハックで使用できる可能性さえあります) が、単純な再帰コードは、A、B、A、B の順にチェックする無限ループに入ります...

サイクルを許可しない「表示された」マップ、またはすべての依存先が自分自身にのみ依存して不変である場合にクラスが不変であると判断する非常に巧妙なコードを使用して、これを修正できますが、それは非常に複雑になります...

于 2008-10-15T07:43:18.843 に答える
3

jcabi-aspectsからAOP と@Immutable注釈を使用できます。

@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();
于 2013-02-18T07:02:28.763 に答える
3

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

メタデータ:

@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; }
}

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

于 2008-10-15T02:15:22.840 に答える
3

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

于 2008-10-15T16:31:38.650 に答える
2

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

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

interface Immutable {}

class MyImmutable implements Immutable{...}

public void add(Object o) {
  if (!(o instanceof Immutable) && !checkIsImmutableBasePrimitive(o))
    throw new IllegalArgumentException("o is not immutable!");
  ...
}
于 2008-10-15T05:25:43.473 に答える
1

これを試して:

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;
}
于 2011-07-27T11:08:57.347 に答える
1

私の知る限り、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 ライセンスの下で無料のオープン ソースです。

于 2014-05-30T13:01:14.237 に答える
0

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

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

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

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

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

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

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

フィールドがプライベートかどうかをチェックするべきではありません。クラスがパブリック フィールドを持つことはまったく問題ありません。フィールドの可視性は、宣言するクラスの不変性に何らかの形、形状、または形式で影響を与えません。フィールドが final であり、その型が不変であるかどうかを確認するだけで済みます。

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

次に、すべてのフィールドではなく、クラスの宣言されたフィールドのみを確​​認する必要があります。

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

フィールドが final であるが、フィールドの型が変更可能である場合、クラスは変更可能です。(配列は定義上、変更可能です。)

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

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

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

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

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

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

于 2015-01-23T14:35:23.327 に答える
0

組み込みクラスの高い割合で機能するのは、instanceof Comparable のテストです。Date のようにイミュータブルではないクラスについては、ほとんどの場合、イミュータブルとして扱われることがよくあります。

于 2009-03-04T19:21:59.337 に答える