[英]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 );
}
}
}
所以,這些是我的問題。
Class.cast()
應該被放逐到泛型領域嗎? 在那里它有很多合法的用途。Class.cast()
並且在編譯時可以確定非法條件時,編譯器是否應該生成編譯錯誤?我只使用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 僅在運行時執行強制轉換檢查。 但是,如果編譯器可以證明此類轉換永遠不會成功,則編譯器可能會發出錯誤(例如,將一個類轉換為另一個不是超類型的類,並將最終類類型轉換為不在其類型層次結構中的類/接口)。 由於Foo
和Bar
是不在彼此層次結構中的類,因此轉換永遠不會成功。
嘗試在語言之間翻譯結構和概念總是有問題的,而且經常會產生誤導。 鑄造也不例外。 特別是因為 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.