(请不要告诉我应该抽象X
更多并添加另一种方法。)
在C ++中,当我有x
类型的变量X*
时,如果它也是类型Y*
(Y
是子类,我想做某些特定的事情X
),我写这个:
if(Y* y = dynamic_cast<Y*>(x)) {
// now do sth with y
}
同样的事情在Java中似乎是不可能的(或者是吗?)。
我已经阅读了这个Java代码:
if(x instanceof Y) {
Y y = (Y) x;
// ...
}
有时候,如果你没有变量x
但是它是一个更复杂的表达式,只是因为这个问题,你需要一个Java中的虚拟变量:
X x = something();
if(x instanceof Y) {
Y y = (Y) x;
// ...
}
// x not needed here anymore
(常见的是something()
是iterator.next()
。在那里你看到你也不能真正称之为两次。你真的需要虚拟变量。)
在这里你根本不需要x
- 你只是拥有它,因为你无法一次性使用演员进行instanceof
检查。再次将它与相当常见的C ++代码进行比较:
if(Y* y = dynamic_cast<Y*>( something() )) {
// ...
}
因此,我引入了一个castOrNull
函数,可以避免虚拟变量x
。我现在可以写这个:
Y y = castOrNull( something(), Y.class );
if(y != null) {
// ...
}
castOrNull
的实施:
public static <T> T castOrNull(Object obj, Class<T> clazz) {
try {
return clazz.cast(obj);
} catch (ClassCastException exc) {
return null;
}
}
现在,有人告诉我using this castOrNull
function in that way is an evil thing do to。这是为什么? (或者提出更一般的问题:你是否同意并认为这是邪恶的?如果是,为什么会这样?或者你认为这是一个有效的(可能是罕见的)用例?)
如上所述,我不想讨论是否使用这种垂头丧气是一个好主意。但是,让我稍后澄清为什么我有时会使用它:
有时我会遇到这样的情况:我必须在为特定事物添加另一个新方法(在一个特定情况下仅适用于一个子类)或使用此类instanceof
检查之间进行选择。基本上,我可以选择添加函数doSomethingVeryVerySpecificIfIAmY()
或进行instanceof
检查。在这种情况下,我觉得后者更干净。
有时我有一些接口/基类的集合,对于Y
类型的所有条目,我想做一些事情,然后从集合中删除它们。 (例如,我有一个树结构的情况,我想删除所有空叶的孩子。)
答案 0 :(得分:8)
现在,我被告知以这种方式使用这个castOrNull函数是一件坏事。那是为什么?
我可以想到几个原因:
做一些非常简单的事情是一种模糊而棘手的方法。模糊和棘手的代码难以阅读,难以维护,潜在的错误来源(当有人不理解时)和因此邪恶。
JIT编译器无法优化castOrNull
方法最常用的模糊和棘手的方法。你最终会得到至少3个额外的方法调用,以及许多额外的代码来进行类型检查和反射转换。不必要地使用反射是邪恶的。
(相比之下,简单的方法(使用instanceof
后跟类强制转换)使用特定的字节码进行instanceof和类型转换。字节码序列几乎肯定会被优化,以便不超过一个null检查并且不再对本机代码中的对象类型进行一次测试。这是JIT编译器易于检测和优化的常见模式。)
当然,“邪恶”只是另一种说法,你真的不应该这样做。
您添加的两个示例都没有使用必要或可取的castOrNull
方法。 IMO,从可读性和性能角度来看,“简单方法”更好。
答案 1 :(得分:5)
在大多数编写良好/设计的Java代码中,都不会使用instanceof和强制转换。随着泛型的增加,不需要许多情况下的演员(因此也不需要)。它们确实有时会发生。
castOrNull方法是邪恶的,因为你使Java代码看起来“不自然”。从一种语言转换到另一种语言时最大的问题是采用新语言的惯例。临时变量在Java中很好用。事实上,你所做的所有方法都是隐藏临时变量。
如果您发现自己正在编写大量演员表,则应检查代码并查看原因,并寻找删除它们的方法。例如,在你提到添加“getNumberOfChildren”方法的情况下,你可以检查节点是否为空,从而能够在不进行转换的情况下修剪它(这是猜测,在这种情况下它可能不适合你)。
一般来说,在Java中,强制转换是“邪恶的”,因为它们通常是不需要的。你的方法更“邪恶”,因为它不是以大多数人期望编写Java的方式编写的。
话虽如此,如果你想这样做,那就去吧。它实际上并不是“邪恶的”,而是用Java做的“正确”方式。
答案 2 :(得分:4)
castOrNull
不是邪恶的,只是毫无意义。你似乎沉迷于摆脱一个临时变量和一行代码,而对我来说更大的问题是为什么你需要在你的代码中进行如此多的向下转换??在OO中,这几乎总是次优设计的症状。我宁愿解决根本原因而不是治疗症状。
答案 3 :(得分:2)
我不知道为什么这个人说它是邪恶的。然而,他们推理的一种可能性是你之后捕获异常,而不是在你转换之前检查。这是一种方法。
public static <T> T castOrNull(Object obj, Class<T> clazz) {
if ( clazz.isAssignableFrom(obj.getClass()) ) {
return clazz.cast(obj);
} else {
return null;
}
}
答案 4 :(得分:1)
从Java 14开始,您应该能够同时进行instanceof
和强制转换。参见https://openjdk.java.net/jeps/305。
代码示例:
if (obj instanceof String s) {
// can use s here
} else {
// can't use s here
}
如果instanceof
的值为true,则定义以上示例中的变量s。变量的范围取决于上下文。有关更多示例,请参见上面的链接。
答案 5 :(得分:0)
Java异常很慢。如果你试图通过避免双重演员来优化你的表现,那么你可以通过使用异常来代替逻辑来自我攻击。永远不要依赖于你可以合理检查和纠正的事情来捕捉异常(正是你正在做的事情)。