web-dev-qa-db-ja.com

動的オブジェクト型を返す汎用メソッド

おそらく以前に尋ねられた質問かもしれませんが、いつものように、Wordジェネリックに言及すると、タイプ消去について説明する1000の回答が得られます。私はずっと前にその段階を経て、ジェネリックとその使用について多くを知っていますが、この状況は少し微妙な状況です。

スプレッドシート内のデータのセルを表すコンテナーがあり、実際には2つの形式でデータを保存します。表示用の文字列としてだけでなく、データに依存する別の形式(オブジェクトとして保存)でもあります。セルは、型を変換するトランスフォーマーも保持し、型の有効性チェックも行います(たとえば、IntegerTransformerは、文字列が有効な整数であるかどうか、および格納する整数を返すかどうかを確認します)。

新しいタイプでセルオブジェクトを再構築することなく、フォーマットを変更できるようにするため(たとえば、整数の代わりに浮動小数点数に変更する、または生の文字列に変更する)、セル自体は入力されません。以前の試みではジェネリック型を使用していましたが、一度定義した型を変更することはできませんでした。

問題は、型付きでCellからデータを取得するにはどうすればよいですか?私は実験して、制約が定義されていなくても、メソッドでジェネリック型を使用できることを発見しました

public class Cell {
    private String stringVal;
    private Object valVal;
    private Transformer<?> trans;
    private Class<?> valClass;

    public String getStringVal(){
        return stringVal;
    }

    public boolean setStringVal(){
        //this not only set the value, but checks it with the transformer that it meets constraints and updates valVal too
    }

    public <T> T getValVal(){
        return (T) valVal;
        //This works, but I don't understand why
    }
}

私を先送りにするビットは:それは?何もキャストすることはできません。何にも一致するように制約するタイプTの入力はありません。実際、実際にはどこにも何も言いません。 Objectの戻り値の型を持つことは、どこにでもキャストの複雑さを与えます。

私のテストでは、Double値を設定し、Doubleを(オブジェクトとして)格納し、Doubleを実行したときにtestdou = testCell.getValVal();未確認のキャスト警告なしでも、即座に機能しました。ただし、String teststr = testCell.getValVal()を実行すると、ClassCastExceptionが発生しました。当然のことです。

これには2つのビューがあります。

1つ:未定義のキャストを使用することは、メソッドが戻った後、外部ではなくメソッドの内部にキャストを配置する方法にすぎないようです。ユーザーの観点からは非常にきちんとしていますが、メソッドのユーザーは正しい呼び出しの使用について心配する必要があります。これは、実行時まで複雑な警告とチェックを非表示にするだけですが、動作しているようです。

2番目のビューは次のとおりです。私はこのコードが好きではありません。それはクリーンではなく、通常コード作成の質を誇っています。コードは正しく機能しているだけでなく、正しく機能している必要があります。エラーはキャッチして処理する必要があり、予想されるインターフェイスは、期待するユーザーが自分自身であっても絶対確実である必要があります。また、気まずいものよりも柔軟で汎用的で再利用可能な手法を常に好みます。問題は、これを行う通常の方法はありますか?これは、キャストせずに必要なものを返すArrayListをすべて受け入れる型なしを達成するための卑劣な方法ですか?または、私がここで見逃しているものがあります。このコードを信用してはいけないということを教えてくれます!

おそらく、私が意図したよりも哲学的な質問の方が多いのですが、それが私が求めていることだと思います。

編集:さらなるテスト。

次の2つの興味深いスニペットを試しました。

public <T> T getTypedElem() {
    T output = (T) this.typedElem;
    System.out.println(output.getClass());
    return output;
}

public <T> T getTypedElem() {
    T output = null;
    try {
        output = (T) this.typedElem;
        System.out.println(output.getClass());
    } catch (ClassCastException e) {
        System.out.println("class cast caught");
        return null;
    }
    return output;
}

DoubleをtypedElemに割り当ててStringに入れようとすると、へのキャストではなく戻りで例外が発生し、2番目のスニペットは保護しません。 getClassからの出力はJava.lang.Doubleであり、これはtypedElemから動的に推論されていることを示唆していますが、コンパイラレベルの型チェックは邪魔にならないように強制されているだけです。

議論のメモとして:valClassを取得するための関数もあります。つまり、実行時に割り当て可能性チェックを行うことができます。

Edit2:結果

オプションについて考えた後、2つのソリューションを試しました。1つは軽量ソリューションですが、関数に@depreciatedと注釈を付け、2つ目はキャストしようとするクラスを渡すソリューションです。このように、状況に応じて選択できます。

25
K.Barad

タイプトークンを試すことができます:

public <T> T getValue(Class<T> cls) {
    if (valVal == null) return null;
    else {
        if (cls.isInstance(valVal)) return cls.cast(valVal);
        return null;
    }
}

これは変換を行わないことに注意してください(つまり、DoublevalValまたはFloatのインスタンスである場合、このメソッドを使用してIntegerを抽出することはできません)。

ところで、getValValの定義に関するコンパイラ警告が表示されるはずです。これは、キャストを実行時にチェックできないためです(Javaジェネリックは「消去」によって機能します。つまり、コンパイル後にジェネリック型パラメーターは忘れられます)。したがって、生成されるコードは次のようになります。

public Object getValVal() {
    return valVal;
}
20
Dirk

発見しているように、Javaの型システムを使用して表現できるものには、ジェネリックであっても制限があります。型宣言を使用してアサートしたい特定の値の型の間に関係がある場合がありますが、できない場合があります(または、場合によっては、余分な複雑さと長い冗長コードを犠牲にしてできます)。この投稿のサンプルコード(質問と回答)はその良い例だと思います。

この場合、Javaコンパイラーは、「トランスフォーマー」内にオブジェクト/文字列表現を格納している場合、より多くの型チェックを実行できます。単なる「トランスフォーマー」。ベースTransformerクラスにジェネリックバインドを設定し、その同じバインドを「オブジェクト」のタイプにします。

セルの値outを取得する限り、値が異なる型になる可能性があるため、コンパイラの型チェックが役立つ方法はありません(そして、コンパイル時にどの型かわからないオブジェクトの特定のセルに保存されます)。

次のようなこともできると思います。

public <T> void setObject(Transformer<T> transformer, T object) {}

トランスフォーマーとオブジェクトを設定する唯一の方法がそのメソッドを使用する場合、コンパイラーは引数の型チェックにより、互換性のないトランスフォーマー/オブジェクトのペアがセルに入るのを防ぎます。

私があなたがしていることを理解している場合、使用するTransformerのタイプは、セルが保持しているオブジェクトのタイプによってのみ決定されます、そうですか?その場合、トランスフォーマー/オブジェクトを一緒に設定するのではなく、オブジェクトのみにセッターを提供し、ハッシュルックアップを実行して適切なトランスフォーマーを見つけます(キーとしてオブジェクトタイプを使用)。ハッシュルックアップは、値が設定されるたびに、または文字列に変換されるたびに実行できます。どちらの方法でも機能します。

これにより、間違ったタイプのTransformerが渡されることは当然不可能になります。

3
Alex D

あなたは静的型の男だと思いますが、レムは試してください:その部分にグルーヴィーのような動的言語を使用することを考えましたか?

あなたの説明から、タイプは何かを助けるよりも邪魔になるように思えます。

Groovyでは、_Cell.valVal_を動的に型指定して、簡単に変換できます:

_class Cell {
  String val
  def valVal
}

def cell = new Cell(val:"10.0")
cell.valVal = cell.val as BigDecimal
BigDecimal valVal = cell.valVal
assert valVal.class == BigDecimal
assert valVal == 10.0

cell.val = "20"
cell.valVal = cell.val as Integer
Integer valVal2 = cell.valVal
assert valVal2.class == Integer
assert valVal2 == 20
_

asは、最も一般的な変換に必要なすべてのものです。あなたも追加できます。

コードの他のブロックを変換する必要がある場合、Javaの構文はdo { ... } while()ブロックを除いて有効なgroovy構文であることに注意してください

1
Will