这个问题适用于Java和Android开发人员。
在Android上,我希望有一个函数返回从View扩展并实现Checkable的东西,两者都是Android API的一部分。
在这里和互联网上的其他地方搜索了一下后,我找到了这个解决方案:
private <T extends View & Checkable> T get() {
return (T) mView;
}
请注意,mView是任何扩展View的类型(并且有很多),但我保证它实现了Checkable,因为只有那些类型才能存在。
这显示了“类型安全:未经检查从视图转换为T”的警告,但这不是问题。问题是我无法正确使用此方法,例如:
View v=get(); //this works fine
Checkable c=get(); // this shows error
第二行显示错误:
绑定不匹配:类型为...的泛型方法get()不适用于arguments()。推断类型Checkable&View不是有界参数的有效替代
所以我尝试使用“,”而不是“&”。同样的错误。
但是,当我使用“,”并反转顺序时,我收到一个警告“类型参数视图隐藏了类型视图”,两行都可以正常工作。
这是代码:
private <T extends Checkable, View> T get() {
return null;
}
private void foo() {
final View v = get(); // this works fine
final Checkable c = get();// this works fine too
}
但是,当我将返回的类型发送到其他函数时,它们不会将类型本身作为扩展类和接口的东西,并且需要一直投射(到View)。
这样做的正确方法是什么?
如果我展示的最后一种方式是正确的,为什么我会收到警告呢?如果总有一个类最多可以扩展,为什么顺序很重要?
你得到错误,因为在第二个调用站点推断参数T
的类型,即Checkable c = get()
,只是Checkable
,因为错误消息正确报告。
如果强制调用使用兼容类型View & Checkable
,它将编译。要做到这一点,你必须明确提供类型,例如:
private <T extends View & Checkable> T get() {
return (T) mView;
}
private <T extends View & Checkable> void useGeneric() {
View v = this.get(); // no need for an explicit type
Checkable c = this.<T>get(); // an explicit type needed
T t = this.get(); // best way
}
private void useSpecific() {
class Specific extends View implements Checkable {}
Checkable c = this.<Specific>get(); // risk of ClassCastException
Specific s = this.get(); // best way; risk of ClassCastException
}
在分配给类的情况下,它不需要显式类型,而在接口情况下需要一个类型,在我看来就像类型推理算法工件,类/接口不对称。
在我深入了解仿制药之前....为什么不介绍一个CheckableView
并返回该类型?
public class CheckableView extends View implements Checkable {
}
private CheckableView getCheckableView() {
return checkableView;
}
如果View
将是一个界面,它的工作原理:
interface View {}
interface Checkable {}
class CheckableView implements Checkable, View {}
public class Main {
private static CheckableView checkableView;
private static <T extends View & Checkable> T get() {
return (T) checkableView;
}
public static void main(String[] args) {
View view = Main.get();
Checkable checkable = Main.get();
}
}
我认为理解泛型的问题在于T extends View & Checkable
在实际情况下并不意味着你可以返回任何扩展View
并实现Checkable
的类型。
这意味着客户端可以将返回的类型强制转换为扩展View
并实现Checkable
的任何类型。
客户可以这样做:
class CheckableView extends View implements Checkable {}
class OtherView extends View implements Checkable {}
private static <T extends View & Checkable> T get() {
return (T) checkableView;
}
OtherView otherView = Main.get();
CheckableView checkableView = Main.get();
但是你现在想要实施什么呢?
private <T extends View & Checkable> T get() {
return ...;
}
该方法应该返回什么?如果你返回一个CheckableView
,编译器会说你必须将它强制转换为T
,因为客户端可能会将它强制转换为任何类型的T
。但这也意味着你将从CheckableView获得一个未经检查的强制转换为T警告,因为如果客户端将其转换为除CheckableView
之外的任何其他类型,例如OhterView
,你会得到一个ClassCastException
。
由于get
方法实现者无法知道客户端将它投射到哪种类型,因此无法确定返回哪个对象。
如果实现者返回特定对象(例如CheckableView
),则客户端只能将其转换为该类型。那么为什么实现者不会更改方法签名以返回该类型呢? ....或为此案例引入特殊类型或界面?
也许这是一个解决方案
创建一个get
方法将返回的接口,例如
public interface CheckableView {
}
更改get方法以返回CheckableView
private CheckableView get() {
}
编写一个适配器,使你的View
s实现Checkable
到CheckableView
public class CheckableViewAdapter<T extends View & Checkable> implements CheckableView {
private T checkableView;
public CheckableViewAdapter(T checkableView) {
this.checkableView = checkableView;
}
}
现在你可以为每个实现CheckableView
的View
创建Checkable
的实例
class SomeView extends View implements Checkable {}
SomeView someView = ...;
CheckableView checkableView = new CheckableViewAdapter<SomeView>(someView);
将get
方法的客户端需要的方法添加到CheckableView
接口并在CheckableViewAdapter
中实现它。
public interface CheckableView {
public void bringToFront();
}
public class CheckableViewAdapter<T extends View & Checkable> implements CheckableView {
private T checkableView;
public CheckableViewAdapter(T checkableView) {
this.checkableView = checkableView;
}
public void bringToFront(){
checkableView.bringToFront();
}
}
要不就
public interface CheckableView {
public View getView();
public Checkable getCheckable();
}
问题是你有逗号而不是&符号!这个定义:
private <T extends Checkable, View> T get() {
不是十字路口!相反,它声明了2个泛型类型,一个名为T
,另一个名为View
,它不在您的类中使用 - 它与编码相同:
private <T extends Checkable, V> T get() {
所以,将其更改为:
private <T extends View & Checkable> T get() {
注意通用交叉点,类必须在接口之前。