在我写的程序中,我有一个类RestrictedUser
和类User
,它派生自RestrictedUser.
我试图通过强制转换为{{来隐藏用户特定的方法1}}但是当我进行转换时,User方法仍然可用。此外,当我运行调试器时,变量的类型显示为RestrictedUser
。
User
Java中的up cast是否会隐藏子类方法和字段,或者我做错了什么?有解决方法吗?
由于
答案 0 :(得分:16)
如果您尝试运行此代码:
User user = new User(...);
RestrictedUser restricted = user;
restricted.methodDefinedInTheUserClass(); // <--
你会收到编译错误。这不会以任何方式,形状或形式安全,因为即使您将RestrictedUser
传递给另一种方法,该方法也可以这样做:
if (restricted instanceof User) {
User realUser = (User)restricted;
realUser.methodDefinedInTheUserClass(); // !!
}
并且您的“受限制”用户不再受限制。
该对象在调试器中显示为User
对象,因为它是User
对象,即使对象引用存储在RestrictedUser
变量中也是如此。基本上,将子类的实例放在具有父类类型的变量中确实会“隐藏”子类的方法和字段,但不是安全的。
答案 1 :(得分:3)
我不确定你所问的是什么,因为你使用的术语有点不清楚但是这里有。如果你有一个超类和一个子类,你可以通过将它们设为私有来隐藏子类中超类的方法。如果你需要超类上的公共方法是隐形的,那你就不走运了。如果将子类强制转换为超类,则子类上的公共方法将不再可见。
可能有用的一个建议是使用组合而不是继承,将敏感方法提取到单独的类中,然后根据需要将该类的适当实例插入到对象中。如果需要,您可以将类中的方法委托给注入的类。
答案 2 :(得分:3)
您会混淆静态类型和动态类型。
静态类型是引用的类型。当您从Derived转发到Base时,您告诉编译器,只要您知道,指向的东西就是Base。也就是说,你承诺指向的对象是null,或Base,或从Base派生的东西。也就是说,它有Base的公共接口。
您将无法调用在Derived中声明的方法(而不是在Base中),但是当您调用Derived重写的任何Base方法时,您将获得Derived的重写版本。
答案 3 :(得分:3)
如果需要保护子类,可以使用Delegate模式:
public class Protect extends Superclass { // better implement an interface here
private final Subclass subclass;
public Protect(Subclass subclass) {
this.subclass = subclass;
}
public void openMethod1(args) {
this.subclass.openMethod1(args);
}
public int openMethod2(args) {
return this.subclass.openMethod2(args);
}
...
}
您还可以考虑使用java.lang.reflect.Proxy
[]]
答案 4 :(得分:2)
Peter's answer和John's answer是正确的:如果对象的真实类型(客户端代码可以转换为,调用反射方法等)的话,只是转换静态类型将无效可用。你必须以某种方式掩盖真实的类型(正如彼得的回答所提到的那样)。
实际上有一种模式:如果您有权访问JDK源代码,请查看Executors
类中的unconfigurable*
方法。
答案 5 :(得分:2)
<强>爪哇强>
在Java中,你永远不能从对象中“隐藏”某些东西。 编译器可以忘记您对特定实例的详细了解。 (就像它的子类一样)调试器比编译器知道得多,所以它会告诉你当前实例的实际类型,这可能就是你所经历的。
<强> OOP 强>
听起来你正在编写逻辑,需要知道你正在使用哪种类型的对象,这在OOP中是不推荐的,但是出于实际原因通常需要这样做。
限制形容词
正如我在对这个问题的评论中所说,你应该清楚这里的基类是什么。直觉RestrictedUser应该是User的子类,因为名称提示更专业的类型。 (名称上有一个额外的主动形容词。)在你的情况下这是特殊的,因为这是一个限制形容词,它可以让你把User作为RestrictedUser的子类完全没问题。我建议将这两个重命名为:BasicUser / UserWithId和NonRestrictedUser,以避免限制形容词,这是令人困惑的部分。
答案 6 :(得分:1)
另外,不要试图认为你可以隐藏匿名类中的方法。例如,这不会提供您期望的隐私:
Runnable run = new Runnable() {
@Override
public void run() {
System.out.println("Hello, world!");
}
public void secretSquirrel() {
System.out.println("Surprise!");
}
}
您可能无法命名run
的实际类型(为了直接转换为它),但您仍可以使用getClass()
获取其类,并且可以调用{{1使用反射。 (即使secretSquirrel
是私有的,如果没有secretSquirrel
,或者它已被设置为允许访问,则反射也可以调用私有方法。)
答案 7 :(得分:0)
我认为你可能做出了一个糟糕的命名选择:我认为RestrictedUser
将是User
的子类,而不是相反,所以我将使用UnrestrictedUser
作为子类。
如果您将UnrestrictedUser
转发为RestrictedUser
,则您的引用只能访问RestrictedUser
中声明的方法。但是,如果您将其转发回UnrestrictedUser
,您将可以访问所有方法。由于Java对象知道它们究竟是什么类型,因此无论您使用何种类型的引用(即使是UnrestrictedUser
),实际上Object
的任何内容都可以始终返回到它。这是不可避免的,也是语言的一部分。但是,你可以将它隐藏在某种代理之后,但在你的情况下可能会破坏目的。
此外,在Java中,默认情况下所有非静态方法都是虚拟的。这意味着,如果UnrestrictedUser
覆盖RestrictedUser
中声明的某些方法,则会使用UnrestrictedUser
版本,即使您通过RestrictedUser
引用访问它也是如此。如果需要调用父类版本,可以通过调用RestrictedUser.variableName.someMethod()
进行非虚拟调用。但是,你可能不应该经常使用它(最少的原因是它破坏了封装)。
答案 8 :(得分:0)
我也认为这个问题引出了另一个问题。你打算怎么称呼这种方法?似乎有一个Class Heirarchy,其中子类化引入新方法签名将需要在调用者中进行instanceof检查。
您是否可以更新您的问题以包含您可以调用这些方法的方案?也许我们能够提供更多帮助。
答案 9 :(得分:0)
John Calsbeek给出了技术答案。我想考虑一下设计问题。您要做的是阻止某些代码段或某些开发人员了解有关User类的任何信息。您希望他们将所有用户视为RestrictedUsers。
你这样做的方式是使用权限。您需要阻止有问题的开发人员访问User类。您可以将它放在一个包中并限制对包的访问。