Java Class.cast()とキャスト演算子


107

Cスタイルのキャストオペレーターの弊害についてC ++の時代に教えられた私は、Java 5でメソッドjava.lang.Classが取得されたことを最初に知って嬉しく思いましたcast

ようやく、キャスティングを処理するオブジェクト指向の方法があると思いました。

結局のところ、C ++ Class.castとは異なりstatic_castます。もっと似ていreinterpret_castます。予想される場合はコンパイルエラーを生成せず、代わりに実行時まで延期します。以下は、さまざまな動作を示す簡単なテストケースです。

package test;

import static org.junit.Assert.assertTrue;

import org.junit.Test;


public class TestCast
{
    static final class Foo
    {
    }

    static class Bar
    {
    }

    static final class BarSubclass
        extends Bar
    {
    }

    @Test
    public void test ( )
    {
        final Foo foo = new Foo( );
        final Bar bar = new Bar( );
        final BarSubclass bar_subclass = new BarSubclass( );

        {
            final Bar bar_ref = bar;
        }

        {
            // Compilation error
            final Bar bar_ref = foo;
        }
        {
            // Compilation error
            final Bar bar_ref = (Bar) foo;
        }

        try
        {
            // !!! Compiles fine, runtime exception
            Bar.class.cast( foo );
        }
        catch ( final ClassCastException ex )
        {
            assertTrue( true );
        }

        {
            final Bar bar_ref = bar_subclass;
        }

        try
        {
            // Compiles fine, runtime exception, equivalent of C++ dynamic_cast
            final BarSubclass bar_subclass_ref = (BarSubclass) bar;
        }
        catch ( final ClassCastException ex )
        {
            assertTrue( true );
        }
    }
}

だから、これらは私の質問です。

  1. Class.cast()ジェネリックの土地に追放されるべきですか?そこではかなりの数の正当な用途があります。
  2. Class.cast()使用すると、コンパイラーはコンパイルエラーを生成し、コンパイル時に不正な条件が判別されることがありますか?
  3. JavaはC ++と同様の言語構造としてキャスト演算子を提供する必要がありますか?

4
簡単な答え:(1)「ジェネリックの土地」はどこにありますか?これは、キャスト演算子が現在使用されている方法とどう違うのですか?(2)おそらく。しかし、これまでに書かれたすべてのJavaコードの99%で、それは非常に使用する人のための可能性は低いClass.cast()の不正な条件がコンパイル時に決定されることができるとき。その場合、あなた以外の全員が標準のキャスト演算子を使用します。(3)Javaには、言語構成要素としてキャスト演算子があります。C ++とは異なります。これは、Javaの言語構造の多くがC ++に似ていないためです。表面的な類似性にもかかわらず、JavaとC ++はまったく異なります。
ダニエルプライデン

回答:


116

私はこれまでClass.cast(Object)「ジェネリック地」での警告を回避するために使用したことがあります。私はしばしばこのようなことをしているメソッドを見ます:

@SuppressWarnings("unchecked")
<T> T doSomething() {
    Object o;
    // snip
    return (T) o;
}

多くの場合、次のものに置き換えるのが最善です。

<T> T doSomething(Class<T> cls) {
    Object o;
    // snip
    return cls.cast(o);
}

これが、Class.cast(Object)私が遭遇した唯一のユースケースです。

コンパイラの警告について:それClass.cast(Object)はコンパイラにとって特別なことではないと思います。静的に(つまりFoo.class.cast(o)ではなくcls.cast(o))使用すると最適化される可能性がありますが、これを使用している人は見たことがありません。


8
この場合の正しい方法は、最初に次のようにcheckのインスタンスを実行することだと思います。もちろん、タイプが正しいことが確かである場合を除きます。
Puce、2012年

1
2番目のバリアントが優れているのはなぜですか?呼び出し側のコードはとにかく動的キャストを行うため、最初のバリアントはより効率的ではありませんか?
user1944408 2014年

1
@ user1944408は、パフォーマンスについて厳密に述べている限り、ほとんど無視できます。明らかなケースがないのにClassCastExceptionsを取得するのは嫌です。さらに、もう一つはどこコンパイラができないT推論、例えばlist.add(。この<文字列> doSomethingの())対list.add(doSomethingの(String.class))良い作品
sfussenegger

2
ただし、コンパイル時に警告が表示されていないときにcls.cast(o)を呼び出すと、ClassCastExceptionsが引き続き発生します。私は最初のバリアントを好みますが、たとえばキャストできない場合にnullを返す必要がある場合は、2番目のバリアントを使用します。その場合、cls.cast(o)をtry catchブロックでラップします。また、コンパイラーがTを推測できない場合にも、これがより良いことにも同意します。返信ありがとうございます。
user1944408 2014年

1
Class <T> clsを動的にする必要がある場合は、2番目のバリアントも使用します。たとえば、リフレクションを使用する場合などですが、他のすべてのケースでは1番目のものを使用します。まあそれは個人的な好みだと思います。
user1944408 2014年

20

まず、ほとんどすべてのキャストを行うことを強くお勧めしません。そのため、できる限り制限する必要があります。Javaのコンパイル時の厳密に型指定された機能の利点が失われます。

いずれの場合も、Class.cast()主にClassリフレクションを介してトークンを取得するときに使用する必要があります。書く方が慣用的です

MyObject myObject = (MyObject) object

のではなく

MyObject myObject = MyObject.class.cast(object)

編集:コンパイル時のエラー

全体として、Javaは実行時にのみキャストチェックを実行します。ただし、そのようなキャストが成功しないことを証明できる場合、コンパイラーはエラーを発行できます(たとえば、クラスをスーパータイプではない別のクラスにキャストし、最終的なクラスタイプを、そのタイプ階層にないクラス/インターフェイスにキャストします)。ここでは、FooBarは互いに階層にないクラスなので、キャストが成功することはありません。


キャストは控えめに使用する必要があることに完全に同意します。そのため、簡単に検索できるものがあると、コードのリファクタリング/保守に大きなメリットがあります。しかし問題は、一見するClass.castと目的に合っているように見えても、解決するよりも多くの問題が発生することです。
Alexander Pogrebnyak

16

言語間で構文や概念を翻訳しようとすると、常に問題が生じ、誤解を招くことがよくあります。キャスティングも例外ではありません。特に、Javaは動的言語であり、C ++は多少異なります。

Javaでのキャストはすべて、実行方法に関係なく、実行時に行われます。タイプ情報は実行時に保持されます。C ++はもう少し複雑です。C ++で構造体を別の構造体にキャストすることができます。これは、それらの構造体を表すバイトの単なる再解釈です。Javaはそのようには機能しません。

また、JavaとC ++のジェネリックは大きく異なります。JavaでC ++の処理を行う方法について、あまり気にしないでください。Javaで物事を行う方法を学ぶ必要があります。


実行時にすべての情報が保持されるわけではありません。私の例からわかるように(Bar) foo、コンパイル時にエラーが発生しますが、発生しBar.class.cast(foo)ません。私の意見では、この方法で使用する場合は、使用する必要があります。
Alexander Pogrebnyak

5
@アレクサンダーPogrebnyak:それをしないでください! Bar.class.cast(foo)実行時にキャストを実行することをコンパイラーに明示的に指示します。キャストの有効性のコンパイル時チェックが必要な場合は、(Bar) fooスタイルキャストを実行するしかありません。
Daniel Pryden、2009年

同じようにする方法は何だと思いますか?javaは複数のクラスの継承をサポートしていないためです。
Yamur

13

Class.cast()Javaコードで使用されることはほとんどありません。それが使用される場合、通常は実行時にのみ認識される型で(つまり、それぞれのClassオブジェクトと型パラメーターによって)。これは、ジェネリックスを使用するコードでのみ非常に役立ちます(これは、以前に導入されなかった理由でもあります)。

それはないに似てreinterpret_cast、それがされますので、ないあなたがこれ以上ない通常のキャストよりも、実行時に型システムを破ることができ(すなわち、あなたがすることができます破るジェネリック型パラメータをすることはできませんが、破る「本物」のタイプを)。

Cスタイルのキャスト演算子の弊害は、通常、Javaには当てはまりません。Cスタイルのキャストのように見えるJavaコードはdynamic_cast<>()、Javaの参照型を持つに最も似ています(Javaにはランタイム型情報があります)。

一般に、C ++のキャスト演算子とJavaのキャストの比較はかなり困難です。Javaでは参照をキャストできるだけであり、オブジェクトへの変換は行われないためです(この構文を使用して変換できるのはプリミティブ値のみです)。


dynamic_cast<>()参照タイプ。
トム・ホーティン-

@トム:その編集は正しいですか?私のC ++は非常に錆びており、Googleでその多くを再度
Joachim Sauer、

+1:「Cスタイルのキャスト演算子の弊害は通常、Javaには当てはまりません。」かなり本当です。私はちょうどそれらの正確な言葉を質問へのコメントとして投稿しようとしていました。
Daniel Pryden

4

一般に、キャストオペレーターはClass#castメソッドよりも簡潔であり、コンパイラーで分析してコードの露骨な問題を吐き出すことができるため、Classキャストメソッドよりも推奨されます。

Class#castは、コンパイル時ではなく実行時に型チェックを行います。

特にリフレクティブ操作に関しては、Class#castのユースケースは確かにあります。

ラムダがJavaになって以来、私は個人的に、たとえば、抽象型を扱う場合は、コレクション/ストリームAPIでClass#castを使用するのが好きです。

Dog findMyDog(String name, Breed breed) {
    return lostAnimals.stream()
                      .filter(Dog.class::isInstance)
                      .map(Dog.class::cast)
                      .filter(dog -> dog.getName().equalsIgnoreCase(name))
                      .filter(dog -> dog.getBreed() == breed)
                      .findFirst()
                      .orElse(null);
}

3

C ++とJavaは異なる言語です。

Java Cスタイルのキャスト演算子は、C / C ++バージョンよりもはるかに制限されています。事実上、JavaキャストはC ++ dynamic_castのようなものであり、新しいクラスにキャストできないオブジェクトの場合、ランタイム例外(またはコードに十分な情報がある場合はコンパイル時)例外が発生します。したがって、Cの型キャストを使用しないというC ++の考えは、Javaでは良い考えではありません。


0

最も言及されている醜いキャストの警告を削除することに加えて、Class.castはランタイムキャストであり、主にジェネリックキャストで使用されます。初期のClassCastExceptionをスローします。

たとえば、serviceLoderはオブジェクトを作成するときにこのトリックを使用します。Sp = service.cast(c.newInstance());を確認してください。これは、SP =(S)c.newInstance();の場合にクラスキャスト例外をスローします。しないし、「タイプセーフ:オブジェクトからSへの未チェックのキャスト」という警告が表示される場合があります(オブジェクトP =(オブジェクト)c.newInstance();と同じ)。

-単に、キャストされたオブジェクトがキャストクラスのインスタンスであることを確認し、キャスト演算子を使用して、警告を抑制し、キャストして非表示にします。

動的キャストのJava実装:

@SuppressWarnings("unchecked")
public T cast(Object obj) {
    if (obj != null && !isInstance(obj))
        throw new ClassCastException(cannotCastMsg(obj));
    return (T) obj;
}




    private S nextService() {
        if (!hasNextService())
            throw new NoSuchElementException();
        String cn = nextName;
        nextName = null;
        Class<?> c = null;
        try {
            c = Class.forName(cn, false, loader);
        } catch (ClassNotFoundException x) {
            fail(service,
                 "Provider " + cn + " not found");
        }
        if (!service.isAssignableFrom(c)) {
            fail(service,
                 "Provider " + cn  + " not a subtype");
        }
        try {
            S p = service.cast(c.newInstance());
            providers.put(cn, p);
            return p;
        } catch (Throwable x) {
            fail(service,
                 "Provider " + cn + " could not be instantiated",
                 x);
        }
        throw new Error();          // This cannot happen
    }

0

個人的には、JSONからPOJOへのコンバーターを作成するためにこれを以前に使用しました。関数で処理されたJSONObjectに配列またはネストされたJSONObjectsが含まれている場合(ここのデータがプリミティブ型またはでないことを意味しますString)、次の方法でセッターメソッドを呼び出そうとしclass.cast()ます。

public static Object convertResponse(Class<?> clazz, JSONObject readResultObject) {
    ...
    for(Method m : clazz.getMethods()) {
        if(!m.isAnnotationPresent(convertResultIgnore.class) && 
            m.getName().toLowerCase().startsWith("set")) {
        ...
        m.invoke(returnObject,  m.getParameters()[0].getClass().cast(convertResponse(m.getParameters()[0].getType(), readResultObject.getJSONObject(key))));
    }
    ...
}

これが非常に役立つかどうかはわかりませんが、前に述べたように、リフレクションはclass.cast()私が考えることができる非常に数少ない正当なユースケースの1つです。少なくとももう1つの例があります。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.