27

おそらく以前に尋ねられた質問ですが、いつものように、一般的な単語に言及すると、型消去を説明する千の答えが得られます。私はずっと前にその段階を経験し、ジェネリックとその使用について多くのことを知っていますが、この状況はもう少し微妙なものです。

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

セルオブジェクトを新しいタイプで再構築せずにフォーマットを変更できるようにしたいので(たとえば、セカンダリフォーマットを整数の代わりにfloatに変更したり、生の文字列に変更したり)、セル自体はタイプされません。以前の試みではジェネリック型を使用していましたが、一度定義すると型を変更できず、コーディングが非常に大きくなり、多くの反射が発生しました。

問題は、タイプされた方法でセルからデータを取得するにはどうすればよいですか?実験してみたところ、制約が定義されていなくても、ジェネリック型の使用はメソッドで実行できることがわかりました。

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のreturnタイプを持つことは、どこでもキャストの複雑さを与えるだけです。

私のテストでは、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;
}

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

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

Edit2:結果

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

4

3 に答える 3

22

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

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

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

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

public Object getValVal() {
    return valVal;
}
于 2012-09-24T17:37:34.667 に答える
3

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

この場合、オブジェクト/文字列表現を「トランスフォーマー」内に格納すると、Javaコンパイラーはより多くの型チェックを実行できます。(おそらく、それが何であるかを再考する必要があります。おそらく、それは単なる「トランスフォーマー」ではありません。)基本Transformerクラスにジェネリック境界を設定し、同じ境界を「オブジェクト」の型にします。

セルから値を取得する限り、値はさまざまなタイプである可能性があるため、コンパイラのタイプチェックが役立つ方法はありません(コンパイル時にどのタイプのオブジェクトがに格納されるかはわかりません)。与えられたセル)。

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

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

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

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

Transformerこれは当然、間違ったタイプを渡すことを不可能にします。

于 2012-09-24T17:44:00.423 に答える
1

あなたは静的型の人だと思いますが、lemme try:その部分にgroovyのような動的言語を使用することを考えましたか?

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

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一般的な変換に必要なすべてのものです。あなたも追加することができます。

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

于 2012-09-24T17:39:13.363 に答える