在我正在编写的程序中,我有一个类RestrictedUser
和一个User
派生RestrictedUser.
自我试图通过强制转换来隐藏用户特定方法的类,RestrictedUser
但是当我进行强制转换时,用户方法仍然可用。此外,当我运行调试器时,变量的类型显示为User
.
RestrictedUser restricted = regularUser;
在 Java 中进行强制转换是否隐藏了子类方法和字段,还是我做错了什么?有解决方法吗?
谢谢
如果您要尝试运行此代码:
User user = new User(...);
RestrictedUser restricted = user;
restricted.methodDefinedInTheUserClass(); // <--
你会得到一个编译错误。这在任何方式、形状或形式上都不会是安全的,因为即使您要传递RestrictedUser
到另一个方法,例如,该方法也可以这样做:
if (restricted instanceof User) {
User realUser = (User)restricted;
realUser.methodDefinedInTheUserClass(); // !!
}
并且您的“受限制”用户不再受限制。
对象在调试器中显示为User
对象,因为它是User
对象,即使对象引用存储在RestrictedUser
变量中。基本上,将子类的实例放在具有父类类型的变量中确实会“隐藏”子类的方法和字段,但不安全。
我不确定你到底在问什么,因为你使用的术语有点不清楚,但这里有。如果您有一个超类和一个子类,您可以通过将超类中的方法设置为私有来对子类隐藏超类中的方法。如果您需要超类上的公共方法不可见,那么您就不走运了。如果将子类转换为超类,则子类上的公共方法将不再可见。
一个可能对您有所帮助的建议是使用组合而不是继承,将敏感方法提取到单独的类中,然后根据需要将该类的适当实例插入到对象中。如果需要,您可以将类中的方法委托给注入的类。
您混淆了静态类型和动态类型。
静态类型是引用的类型。当您从 Derived 向上转换为 Base 时,您是在告诉编译器,据您所知,指向的东西是 Base。也就是说,您承诺指向的对象要么是 null,要么是 Base,或者是从 Base 派生的东西。也就是说,它有Base的公共接口。
然后您将无法调用在 Derived(而不是在 Base)中声明的方法,但是当您调用任何被 Derived 覆盖的 Base 方法时,您将获得 Derived 的覆盖版本。
如果需要保护子类,可以使用委托模式:
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
[]]
爪哇
在 Java 中,您永远无法从对象中“隐藏”某些内容。编译器可能会忘记您对特定实例的详细了解。(比如它是什么子类)调试器比编译器知道的多得多,所以它会告诉你当前实例是什么实际类型,这可能是你正在经历的。
面向对象
听起来您正在编写需要知道您正在使用的对象类型的逻辑,这在 OOP 中不推荐,但出于实际原因通常需要。
限制形容词
正如我在对该问题的评论中所说,您应该清楚这里的基类是什么。直觉上 RestrictedUser 应该是 User 的子类,因为名称暗示了一种更专业的类型。(名称上有一个额外的活动形容词。)在你的情况下,这很特别,因为这是一个限制形容词,它可以让你把 User 作为 RestrictedUser 的子类完全没问题。我建议将两者重命名为:BasicUser/UserWithId 和 NonRestrictedUser 以避免限制形容词,这是令人困惑的部分。
另外,也不要认为可以在匿名类中隐藏方法。例如,这不会提供您期望的隐私:
Runnable run = new Runnable() {
@Override
public void run() {
System.out.println("Hello, world!");
}
public void secretSquirrel() {
System.out.println("Surprise!");
}
}
您可能无法命名的真实类型run
(以便直接转换为),但您仍然可以使用 获取其类getClass()
,并且可以secretSquirrel
使用反射调用。(即使secretSquirrel
是私有的,如果你没有SecurityManager
,或者如果它被设置为允许可访问性,反射也可以调用私有方法。)
我认为您可能做出了一个糟糕的命名选择:我认为RestrictedUser
将是 的子类User
,而不是相反,所以我将UnrestrictedUser
用作子类。
如果将 an 向上UnrestrictedUser
转换为 a RestrictedUser
,则您的引用将只能访问在 中声明的方法RestrictedUser
。但是,如果您将其向下转换为UnrestrictedUser
,您将可以访问所有方法。UnrestrictedUser
由于 Java 对象知道它们真正是什么类型,因此无论您使用什么类型的引用(甚至是) ,任何实际上是 an 的东西都可以始终转换回它Object
。有不可避免的和语言的一部分。但是,您可以将其隐藏在某种代理后面,但在您的情况下,这可能会违背目的。
此外,在 Java 中,所有非静态方法默认都是虚拟的。这意味着如果UnrestrictedUser
覆盖在 中声明的某些方法RestrictedUser
,则将使用该UnrestrictedUser
版本,即使您通过RestrictedUser
引用访问它也是如此。如果需要调用父类版本,可以通过调用来进行非虚调用RestrictedUser.variableName.someMethod()
。但是,您可能不应该经常使用它(最不重要的原因是它破坏了封装)。
我也认为这个问题引出了另一个问题。你打算如何调用这个方法?似乎有一个类层次结构,其中子类化引入新的方法签名将需要在调用者中进行 instanceof 检查。
您能否更新您的问题以包含一个您会调用这些方法的场景?也许我们能提供更多帮助。
John Calsbeek 给出了技术答案。我想考虑设计问题。您要做的是阻止某些代码段或某些开发人员了解有关 User 类的任何信息。您希望他们将所有用户视为 RestrictedUser。
您这样做的方式是使用权限。您需要防止有问题的开发人员访问 User 类。您可以通过将其放入包中并限制对包的访问来做到这一点。