簡體   English   中英

Java Class.cast() 與 cast 運算符

[英]Java Class.cast() vs. cast operator

我在 C++ 時代學習過 C 風格強制轉換操作符的弊端,起初我很高興地發現在 Java 5 中java.lang.Class獲得了一個cast方法。

我認為最終我們有一種面向對象的方式來處理鑄造。

原來Class.cast與 C++ 中的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++ 的語言結構?

我只使用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) ),但我從未見過有人使用它 - 這使得將這種優化構建到編譯器中的努力變得毫無價值。

首先,強烈不鼓勵您進行幾乎任何演員表,因此您應該盡可能地限制它! 您失去了 Java 編譯時強類型特性的好處。

在任何情況下,當您通過反射檢索Class標記時,應該主要使用Class.cast() 寫起來更地道

MyObject myObject = (MyObject) object

而不是

MyObject myObject = MyObject.class.cast(object)

編輯:編譯時的錯誤

總的來說,Java 僅在運行時執行強制轉換檢查。 但是,如果編譯器可以證明此類轉換永遠不會成功,則編譯器可能會發出錯誤(例如,將一個類轉換為另一個不是超類型的類,並將最終類類型轉換為不在其類型層次結構中的類/接口)。 由於FooBar是不在彼此層次結構中的類,因此轉換永遠不會成功。

嘗試在語言之間翻譯結構和概念總是有問題的,而且經常會產生誤導。 鑄造也不例外。 特別是因為 Java 是一種動態語言,而 C++ 則有些不同。

Java 中的所有轉換,無論您如何操作,都是在運行時完成的。 類型信息在運行時保存。 C++ 更像是一種混合。 您可以將 C++ 中的結構轉換為另一個結構,這只是對表示這些結構的字節的重新解釋。 Java 不是這樣工作的。

Java 和 C++ 中的泛型也大不相同。 不要過分關心在 Java 中如何做 C++ 的事情。 您需要學習如何以 Java 的方式做事。

Class.cast()在 Java 代碼中很少使用。 如果使用它,則通常與僅在運行時已知的類型一起使用(即通過它們各自的Class對象和某些類型參數)。 它只在使用泛型的代碼中真正有用(這也是之前沒有引入它的原因)。

沒有類似reinterpret_cast ,因為它不會讓你在運行任何超過普通投不打破類型系統(即你可以打破泛型類型參數,但不能突破“真實”的類型)。

C 風格強制轉換運算符的缺點通常不適用於 Java。 看起來像 C 風格類型轉換的 Java 代碼與 Java 中具有引用類型的dynamic_cast<>()最相似(記住:Java 具有運行時類型信息)。

通常,將 C++ 轉換運算符與 Java 轉換進行比較是非常困難的,因為在 Java 中您只能轉換引用並且不會對對象進行轉換(只能使用此語法轉換原始值)。

通常,強制轉換運算符比 Class#cast 方法更受歡迎,因為它更簡潔,並且可以被編譯器分析以吐出代碼中的明顯問題。

Class#cast 負責在運行時而不是在編譯期間進行類型檢查。

Class#cast 肯定有用例,特別是在涉及反射操作時。

例如,由於 lambda 來到 Java,我個人喜歡將 Class#cast 與集合/流 API 一起使用,例如,如果我正在使用抽象類型。

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

C++ 和 Java 是不同的語言。

Java C 風格的強制轉換運算符比 C/C++ 版本受到更多限制。 實際上,Java 轉換就像 C++ dynamic_cast 如果您擁有的對象無法轉換為新類,您將獲得運行時(或者如果代碼中有足夠的信息編譯時)異常。 因此,不使用 C 類型轉換的 C++ 想法在 Java 中不是一個好主意

除了刪除大多數提到的丑陋的強制轉換警告之外,Class.cast 是運行時強制轉換,主要與泛型強制轉換一起使用,由於泛型信息將在運行時被刪除,以及每個泛型將如何被視為 Object,這導致不拋出一個早期的 ClassCastException。

例如 serviceLoder 在創建對象時使用這個技巧,檢查 S p = service.cast(c.newInstance()); 這將在 SP =(S) c.newInstance(); 時拋出類轉換異常; 不會並且可能會顯示警告“類型安全:未檢查從 Object 到 S 的強制轉換 。(與 Object P =(Object) 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
    }

就個人而言,我以前使用過它來構建 JSON 到 POJO 轉換器。 如果使用函數處理的 JSONObject 包含數組或嵌套的 JSONObjects(暗示這里的數據不是原始類型或String ),我嘗試以這種方式使用class.cast()調用 setter 方法:

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()合法用例之一,至少你現在有另一個例子。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM