web-dev-qa-db-ja.com

java:ある型から別の型への変数の動的なキャストを行うにはどうすればよいですか?

Java変数に対して動的キャストを行いたい場合、キャストタイプは別の変数に格納されます。

これは通常のキャストです:

 String a = (String) 5;

これは私が欲しいものです:

 String theType = 'String';
 String a = (theType) 5;

出来ますか?もしそうならどのように?ありがとう!

更新

受け取ったhashMapをクラスに追加しようとしています。

これはコンストラクタです:

public ConnectParams(HashMap<String,Object> obj) {

    for (Map.Entry<String, Object> entry : obj.entrySet()) {
        try {
            Field f =  this.getClass().getField(entry.getKey());                
            f.set(this, entry.getValue()); /* <= CASTING PROBLEM */
        } catch (NoSuchFieldException ex) {
            log.error("did not find field '" + entry.getKey() + '"');
        } catch (IllegalAccessException ex) {
            log.error(ex.getMessage());         
        }
    }

}

ここでの問題は、クラス変数の一部がDouble型であり、数値3を受け取った場合、整数として認識され、型の問題があることです。

70
ufk

更新に関して、Javaでこれを解決する唯一の方法は、多くのifおよびelseおよびinstanceof式を持つすべてのケースをカバーするコードを書くことです。あなたがやろうとしていることは、まるで動的言語でプログラムするのに使われているように見えます。静的言語では、あなたがしようとすることはほとんど不可能であり、おそらくあなたがしようとすることに対して全く異なるアプローチを選ぶでしょう。静的言語は動的言語ほど柔軟ではありません:)

Javaベストプラクティスの良い例は、 BalusCによる回答 (つまりObjectConverter)と Andreas_Dによる回答 (つまりAdapter) 未満。


意味がありません

String a = (theType) 5;

aの型はStringに静的にバインドされているため、この静的型に動的キャストすることは意味がありません。

PS:例の最初の行はClass<String> stringClass = String.class;と書くことができますが、それでもstringClass変数をキャストします。

13
akuhn

はい Reflection を使用して可能です

Object something = "something";
String theType = "Java.lang.String";
Class<?> theClass = Class.forName(theType);
Object obj = theClass.cast(something);

ただし、結果のオブジェクトはObject型の変数に保存する必要があるため、これはあまり意味がありません。変数を特定のクラスにする必要がある場合は、そのクラスにキャストするだけです。

与えられたクラスを取得したい場合、例えばNumber:

Object something = new Integer(123);
String theType = "Java.lang.Number";
Class<? extends Number> theClass = Class.forName(theType).asSubclass(Number.class);
Number obj = theClass.cast(something);

しかし、それを行う意味はまだありません。Numberにキャストできます。

オブジェクトをキャストしても何も変わりません。それはただ方法コンパイラがそれを扱います。
そのようなことを行う唯一の理由は、オブジェクトが特定のクラスまたはそのサブクラスのインスタンスであるかどうかを確認することですが、それはinstanceofまたはClass.isInstance()を使用して行う方が適切です。

更新

最後のupdateによると、実際の問題は、Doubleに割り当てる必要がある整数がHashMapにあることです。この場合にできることは、フィールドのタイプを確認し、NumberのxxxValue()メソッドを使用することです

...
Field f =  this.getClass().getField(entry.getKey());
Object value = entry.getValue();
if (Integer.class.isAssignableFrom(f.getType())) {
    value = Integer.valueOf(((Number) entry.getValue()).intValue());
} else if (Double.class.isAssignableFrom(f.getType())) {
    value = Double.valueOf(((Number) entry.getValue()).doubleValue());
} // other cases as needed (Long, Float, ...)
f.set(this, value);
...

(マップに間違ったタイプを持つというアイデアが好きかどうかわからない)

100

このためには、ObjectConverterのようなものを書く必要があります。これは、変換するオブジェクトがあり、変換先のターゲットクラスがわかっている場合に実行できます。この特定のケースでは、 Field#getDeclaringClass() でターゲットクラスを取得できます。

here のようなObjectConverterの例を見つけることができます。基本的なアイデアが得られるはずです。より多くの変換の可能性が必要な場合は、目的の引数と戻り値の型でメソッドを追加するだけです。

21
BalusC

これを行うには Class.cast() メソッドを使用します。このメソッドは、指定されたパラメーターを動的にクラスインスタンスの型にキャストします。特定のフィールドのクラスインスタンスを取得するには、問題のフィールドで getType() メソッドを使用します。以下に例を示しましたが、すべてのエラー処理が省略されており、変更せずに使用しないでください。

public class Test {

    public String var1;
    public Integer var2;
}

public class Main {

    public static void main(String[] args) throws Exception {
        Map<String, Object> map = new HashMap<String, Object>();
        map.put("var1", "test");
        map.put("var2", 1);

        Test t = new Test();

        for (Map.Entry<String, Object> entry : map.entrySet()) {
            Field f = Test.class.getField(entry.getKey());

            f.set(t, f.getType().cast(entry.getValue()));
        }

        System.out.println(t.var1);
        System.out.println(t.var2);
    }
}
11
Jared Russell

以下のような単純なcastMethodを作成できます。

private <T> T castObject(Class<T> clazz, Object object) {
  return (T) object;
}

あなたの方法では、次のように使用する必要があります

public ConnectParams(HashMap<String,Object> object) {

for (Map.Entry<String, Object> entry : object.entrySet()) {
    try {
        Field f =  this.getClass().getField(entry.getKey());                
        f.set(this, castObject(entry.getValue().getClass(), entry.getValue()); /* <= CASTING PROBLEM */
    } catch (NoSuchFieldException ex) {
        log.error("did not find field '" + entry.getKey() + '"');
    } catch (IllegalAccessException ex) {    
        log.error(ex.getMessage());          
    }    
}

}
5
Hoji

それは機能し、あなたのアプローチに共通のパターンさえあります: Adapter pattern 。しかし、もちろん、(1)Javaプリミティブをオブジェクトにキャストするためには機能せず、(2)クラスは適応可能でなければなりません(通常、カスタムインターフェイスを実装することによって)。

このパターンを使用すると、次のようなことができます。

Wolf bigBadWolf = new Wolf();
Sheep sheep = (Sheep) bigBadWolf.getAdapter(Sheep.class);

そしてWolfクラスのgetAdapterメソッド:

public Object getAdapter(Class clazz) {
  if (clazz.equals(Sheep.class)) {
    // return a Sheep implementation
    return getWolfDressedAsSheep(this);
  }

  if (clazz.equals(String.class)) {
    // return a String
    return this.getName();
  }

  return null; // not adaptable
}

あなたにとって特別なアイデア-それは不可能です。キャストに文字列値を使用することはできません。

4
Andreas_D

あなたの問題は「動的なキャスティング」の不足ではありません。 IntegerDoubleにキャストすることはまったくできません。 Javaに1つのタイプのオブジェクト、互換性のないタイプのフィールドを与え、何らかの方法でタイプ間の変換方法を自動的に把握させたいようです。

この種のことは、Javaのような強く型付けされた言語、および非常に正当な理由でのIMOに対する嫌悪感です。

実際に何をしようとしていますか?反射のその使用はすべてかなり怪しげに見えます。

2

ですから、これは古い投稿ですが、何か貢献できると思います。

いつでも次のようなことができます。

package com.dyna.test;  

import Java.io.File;  
import Java.lang.reflect.Constructor;  

public class DynamicClass{  

  @SuppressWarnings("unchecked")  
  public Object castDynamicClass(String className, String value){  
    Class<?> dynamicClass;  

    try  
    {  
      //We get the actual .class object associated with the specified name  
      dynamicClass = Class.forName(className);  



    /* We get the constructor that received only 
     a String as a parameter, since the value to be used is a String, but we could
easily change this to be "dynamic" as well, getting the Constructor signature from
the same datasource we get the values from */ 


      Constructor<?> cons =  
        (Constructor<?>) dynamicClass.getConstructor(new Class<?>[]{String.class});  

      /*We generate our object, without knowing until runtime 
 what type it will be, and we place it in an Object as 
 any Java object extends the Object class) */  
      Object object = (Object) cons.newInstance(new Object[]{value});  

      return object;  
    }  
    catch (Exception e)  
    {  
      e.printStackTrace();  
    }  
    return null;  
  }  

  public static void main(String[] args)  
  {   
    DynamicClass dynaClass = new DynamicClass();  

    /* 
 We specify the type of class that should be used to represent 
 the value "3.0", in this case a Double. Both these parameters 
 you can get from a file, or a network stream for example. */  
    System.out.println(dynaClass.castDynamicClass("Java.lang.Double", "3.0"));  

    /* 
We specify a different value and type, and it will work as 
 expected, printing 3.0 in the above case and the test path in the one below, as the Double.toString() and 
 File.toString() would do. */  
    System.out.println(dynaClass.castDynamicClass("Java.io.File", "C:\\testpath"));  
  }  

もちろん、これは他の言語(Pythonなど)のように実際に動的なキャストではありません。なぜなら、Javaは静的に型付けされたlangだからです。ただし、これにより、一部の識別子に応じて、実際にはいくつかのデータをさまざまな方法でロードする必要のあるいくつかの例外的なケースを解決できます。また、Stringパラメーターを使用してコンストラクターを取得する部分は、同じデータソースからパラメーターを渡すことにより、おそらくより柔軟にすることができます。つまりファイルから、使用するコンストラクタシグネチャと使用する値のリストを取得します。たとえば、最初のパラメーターは文字列で、最初のオブジェクトは文字列としてキャストし、次にオブジェクトは整数などですが、プログラムの実行に沿っていくと、最初にFileオブジェクト、次にDoubleなどが取得されます。

このようにして、これらのケースを説明し、その場でいくぶん「動的な」キャスティングを行うことができます。

これがGoogleの検索結果に現れ続けるので、これが誰にも役立つことを願っています。

1
Acapulco

価値のあるものとして、ほとんどのスクリプト言語(Perlなど)および非静的コンパイル時言語(Pickなど)は、自動実行時の動的文字列から(比較的任意の)オブジェクトへの変換をサポートしています。これは、型安全性を失うことなくJavaでも実現でき、静的型付けされた優れた言語は、動的なキャストで邪悪なことを行う他の言語の厄介な副作用を提供しません。疑わしい数学を行うPerlの例:

print ++($foo = '99');  # prints '100'
print ++($foo = 'a0');  # prints 'a1'

Javaでは、これは私が「クロスキャスティング」と呼ぶ方法を使用することにより、よりよく達成されます(IMHO)。クロスキャストでは、リフレクションは、次の静的メソッドを介して動的に検出されるコンストラクターとメソッドの遅延読み込みキャッシュで使用されます。

Object fromString (String value, Class targetClass)

残念ながら、Class.cast()などの組み込みJavaメソッドは、StringからBigDecimal、StringからInteger、またはサポートするクラス階層がない他の変換に対してこれを行いません。私の場合、ポイントはこれを達成するための完全に動的な方法を提供することです-以前の参照は正しいアプローチではないと思います-すべての変換をコーディングする必要があります。簡単に言えば、実装は、正当/可能であれば、文字列からキャストするだけです。

したがって、解決策は、次のいずれかのパブリックメンバーを探す単純なリフレクションです。

STRING_CLASS_ARRAY =(新しいクラス[] {String.class});

a)メンバーmember = targetClass.getMethod(method.getName()、STRING_CLASS_ARRAY); b)メンバーmember = targetClass.getConstructor(STRING_CLASS_ARRAY);

すべてのプリミティブ(Integer、Longなど)とすべての基本(BigInteger、BigDecimalなど)、さらにはJava.regex.Patternもすべてこのアプローチでカバーされていることがわかります。私はこれを使用して、より厳密なチェックが必要な任意の文字列値入力が大量にある実動プロジェクトで大成功を収めました。このアプローチでは、メソッドがない場合、またはメソッドが呼び出された場合に例外がスローされます(BigDecimalへの非数値入力やパターンに対する不正なRegExなどの不正な値であるため)。ターゲットクラス固有のロジック。

これにはいくつかの欠点があります。

1)リフレクションをよく理解する必要があります(これは少し複雑で初心者向けではありません)。 2)Javaクラスと実際にはサードパーティのライブラリの一部は(サプライズ)適切にコーディングされていません。つまり、入力として単一の文字列引数を取り、ターゲットクラスのインスタンスを返すメソッドがありますが、それはあなたが思うことではありません...整数クラスを考えてみましょう:

static Integer getInteger(String nm)
      Determines the integer value of the system property with the specified name.

上記のメソッドは、プリミティブintをラップするオブジェクトとしての整数とはまったく関係ありません。 Reflectionは、decode、valueof、constructorメンバに対して、StringからIntegerを誤って作成する可能性のある候補として検出します。これらは、入力データを実際に制御できないが、単に整数かどうかを確認します。

上記の問題を解決するには、例外をスローするメソッドを探すことをお勧めします。そのようなオブジェクトのインスタンスを作成する無効な入力値shouldは例外をスローするからです。残念ながら、例外がチェック済みとして宣言されているかどうかは実装によって異なります。 Integer.valueOf(String)は、たとえばチェックされたNumberFormatExceptionをスローしますが、Pattern.compile()例外はリフレクション検索中に見つかりません。繰り返しますが、この動的な「クロスキャスティング」アプローチの失敗ではなく、オブジェクト作成メソッドでの例外宣言の非常に非標準的な実装と考えています。

上記の実装方法の詳細が必要な場合はお知らせください。ただし、このソリューションの方がはるかに柔軟性/拡張性があり、型安全性の良い部分を失うことなく、より少ないコードで実現できます。もちろん、「データを知る」ことは常に最善ですが、多くの人が知っているように、私たちは時々、管理されていないコンテンツの受信者にすぎず、それを適切に使用するために最善を尽くす必要があります。

乾杯。

1
Darrell Teague

これをしないでください。代わりに、適切にパラメーター化されたコンストラクターを用意してください。とにかく接続パラメーターのセットとタイプは固定されているため、これをすべて動的に行う意味はありません。

1
Bandi-T

最近、私もこれをしなければならないと感じましたが、コードをより見栄えよくし、より良いOOPを使用する別の方法を見つけました。

それぞれ特定のメソッドdoSomething()を実装する多くの兄弟クラスがあります。そのメソッドにアクセスするには、まずそのクラスのインスタンスが必要ですが、すべての兄弟クラスのスーパークラスを作成し、スーパークラスからメソッドにアクセスできるようになりました。

以下に、「動的なキャスト」に代わる2つの方法を示します。

// Method 1.
mFragment = getFragmentManager().findFragmentByTag(MyHelper.getName(mUnitNum));
switch (mUnitNum) {
case 0:
    ((MyFragment0) mFragment).sortNames(sortOptionNum);
    break;
case 1:
    ((MyFragment1) mFragment).sortNames(sortOptionNum);
    break;
case 2:
    ((MyFragment2) mFragment).sortNames(sortOptionNum);
    break;
}

そして私の現在使用されている方法、

// Method 2.
mSuperFragment = (MySuperFragment) getFragmentManager().findFragmentByTag(MyHelper.getName(mUnitNum));
mSuperFragment.sortNames(sortOptionNum);
0
Anonsage

ダイナミックキャスティングでこれを試してください。それが動作します!!!

    String something = "1234";
    String theType = "Java.lang.Integer";
    Class<?> theClass = Class.forName(theType);
    Constructor<?> cons = theClass.getConstructor(String.class);
    Object ob =  cons.newInstance(something);
    System.out.println(ob.equals(1234));
0
Sunil M M

私は、私が非常に有用であると思い、同様のニーズを経験する誰かにとって可能性のあるものを投稿すると思いました。

次のメソッドは、コントローラーが返されるたびにキャストする必要をなくし、オブジェクトbステートメントのオブジェクトxインスタンスの場合に書き込みを避けるために、JavaFXアプリケーション用に作成したメソッドです。

public <U> Optional<U> getController(Class<U> castKlazz){
    try {
        return Optional.of(fxmlLoader.<U>getController());
    }catch (Exception e){
        e.printStackTrace();
    }
    return Optional.empty();
}

コントローラを取得するためのメソッド宣言は

public <T> T getController()

クラスオブジェクトを介してメソッドに渡されたタイプUを使用することにより、メソッドgetコントローラーに転送して、返されるオブジェクトのタイプを通知できます。間違ったクラスが提供された場合、オプションのオブジェクトが返され、例外が発生した場合、空のオプションが返され、確認できます。

これは、メソッドの最後の呼び出しのように見えます(返されるオプションのオブジェクトが存在する場合、Consumer

getController(LoadController.class).ifPresent(controller->controller.onNotifyComplete());
0
Eladian