为什么通过实例调用静态方法不是 Java 编译器的错误呢?

我相信你们都知道我所指的行为——比如:

Thread thread = new Thread();
int activeCount = thread.activeCount();

引发编译器警告,为什么不是错误呢?

编辑:

要清楚: 问题与线程无关。我意识到在讨论这个问题的时候,经常会给出线程的例子,因为它们有可能把事情搞得一团糟。但真正的问题是,这样的用法是 一直都是的废话,你不能(胜任)写这样的呼叫,并认为它。这种类型的方法调用的任何示例都是愚蠢的。还有一个:

String hello = "hello";
String number123AsString = hello.valueOf(123);

这使得每个 String 实例看起来都带有一个“ String valueOf (int i)”方法。

33098 次浏览

简短的回答-语言允许它,所以它不是一个错误。

他们不能再让它成为一个错误,因为所有的代码已经在那里。

我同意你的观点,这应该是一个错误。 也许应该有一个选项/配置文件,让编译器将一些警告升级为错误。

更新: 当他们在1.4中引入 断言关键字时,他们把它改成了 只有将源模式显式设置为“1.4”时才可用,因为它与旧代码有类似的潜在兼容性问题。我想在一个新的源模式“ java7”中可能会出现一个错误。但我怀疑他们不会这么做,因为这会引起很多麻烦。正如其他人指出的那样,严格来说,没有必要阻止您编写令人困惑的代码。对 Java 的语言更改应该限制在这一点上是绝对必要的。

也许您可以在 IDE 中修改它(在 Eclipse Preferences-> Java-> Compiler-> Error/Warnings 中)

没有别的选择。在爪哇语中(像许多其他语言一样)您可以通过类的类名或该类的实例对象访问该类的所有静态成员。这取决于您和您的案例以及您应该使用哪种软件解决方案来提高可读性。

可能是同样的逻辑,使这不是一个错误:

public class X
{
public static void foo()
{
}


public void bar()
{
foo(); // no need to do X.foo();
}
}

从编译器的角度来看,真正重要的是它能够解析符号。对于静态方法,它需要知道要查找哪个类——因为它不与任何特定对象相关联。Java 的设计者显然决定,既然他们可以确定对象的类,他们也可以从对象的任何实例中解析该对象的任何静态方法的类。他们选择允许这一点——也许是受到@TofuBeer 观察的影响——给程序员带来一些方便。其他语言设计者做出了不同的选择。我可能会落入后者的阵营,但这对我来说没什么大不了的。我可能会允许@TofuBeer 提到的用法,但允许它存在后,我关于不允许从实例变量访问的立场就不那么站得住脚了。

为什么会是错误呢?该实例可以访问所有静态方法。静态方法不能更改实例的状态(尝试 编译错误)。

您给出的这个著名示例的问题非常特定于 丝线,而不是静态方法调用。看起来好像您得到的是 thread引用的线程的 activeCount(),但实际上您得到的是调用线程的计数。作为一个程序员,这是一个逻辑错误。在这种情况下,发出警告是编译器应该做的事情。注意警告并修复代码取决于您自己。

编辑: 我知道这种语言的语法是什么 允许你写误导性的代码,但是记住编译器和它的警告也是语言的一部分。这种语言允许您执行编译器认为可疑的操作,但是它会提醒您注意它可能会导致问题。

基本上,我认为 Java 设计者在设计语言时犯了一个错误,由于涉及到兼容性问题,现在修复它已经太晚了。是的,它会导致非常具有误导性的代码。是的,你应该避免。是的,您应该确保您的 IDE 被配置为将其视为一个错误,IMO。如果你曾经自己设计过一门语言,请记住它是一个需要避免的例子:)

为了回应 DJClayworth 的观点,以下是 C # 中允许的内容:

public class Foo
{
public static void Bar()
{
}
}


public class Abc
{
public void Test()
{
// Static methods in the same class and base classes
// (and outer classes) are available, with no
// qualification
Def();


// Static methods in other classes are available via
// the class name
Foo.Bar();


Abc abc = new Abc();


// This would *not* be legal. It being legal has no benefit,
// and just allows misleading code
// abc.Def();
}


public static void Def()
{
}
}

为什么我认为这是误导?因为如果我看代码 someVariable.SomeMethod(),我希望它是 使用 someVariable的值。如果 SomeMethod()是一个静态方法,那么这个期望是无效的; 代码在欺骗我。这怎么可能是 很好的东西?

奇怪的是,Java 不允许您使用可能未初始化的变量来调用静态方法,尽管事实上它将使用的唯一信息是变量的声明类型。这是一个前后矛盾,毫无帮助的烂摊子。为什么允许?

编辑: 这个编辑是对克莱顿的回答的回应,克莱顿声称它允许对静态方法进行继承。不会的。静态方法不是多态的。下面是一个简短但完整的程序来演示:

class Base
{
static void foo()
{
System.out.println("Base.foo()");
}
}


class Derived extends Base
{
static void foo()
{
System.out.println("Derived.foo()");
}
}


public class Test
{
public static void main(String[] args)
{
Base b = new Derived();
b.foo(); // Prints "Base.foo()"
b = null;
b.foo(); // Still prints "Base.foo()"
}
}

如您所见,完全忽略了 b的执行时间值。

这不是一个错误,因为它是规范的一部分,但是你显然是在询问基本原理,我们都可以猜到这一点。

我的猜测是,这个问题的根源实际上是允许类中的一个方法在同一个类中调用一个静态方法。因为调用 x ()是合法的(即使没有 self 类名) ,所以调用 this. x ()也应该是合法的,因此通过任何对象调用也是合法的。

这也有助于鼓励用户在不改变状态的情况下将私有函数转换为静态函数。

此外,当不可能导致直接错误时,编译器通常会尽量避免声明错误。因为静态方法不会改变状态或关心调用对象,所以它不会导致允许这样做的实际错误(只是混淆)。一个警告就足够了。

实例变量参考的目的只是提供包含静态资料的类别。如果您查看通过 instance.staticMethod 或 EnclosingClass.staticMethod 调用静态方法的字节码,会生成相同的调用静态方法字节码。未显示对实例的引用。

答案就是为什么它会在那里,它就是在那里。只要你用这门课。而不是通过一个实例,你将有助于避免在未来的混乱。

我只是在想:

instanceVar.staticMethod();

简而言之:

instanceVar.getClass().staticMethod();

如果你总是这样做:

SomeClass.staticMethod();

那么您就不能利用继承来获得静态方法。

也就是说,通过实例调用静态方法,您不需要知道实例在编译时是什么具体类,只需要知道它在继承链的某个地方实现了 staticMethod ()。

编辑: 这个答案是错误的。详见评论。

这是一个相当老的话题,但仍然是最新的,令人惊讶地带来了更高的影响今天。正如 Jon 所提到的,这可能只是 Java 设计人员在开始时犯的一个错误。但我不认为它会对安全造成影响。

许多程序员都知道 Apache Velocity,它是一个灵活而强大的模板引擎。它是如此强大,以至于它允许向模板提供一组命名对象——严格地说,这些对象来自于编程语言(Java 最初)。这些对象可以像在编程语言中那样从模板中访问,因此例如 Java 的 String 实例可以与它的所有公共字段、属性和方法一起使用

$input.isEmpty()

其中 输入绳子,直接通过 JVM 运行,并将 没错假的返回给 Velocity 解析器的输出)。目前为止还不错。

但是在 Java 中,所有对象都继承自 反对,因此我们的最终用户也可以将其放到模板中

$input.getClass()

获取 绳子类的实例。

有了这个引用,他们还可以调用 静电干扰方法 ForName (String)

$input.getClass().forName("java.io.FileDescriptor")

使用任何类名,并使用它到任何网络服务器的帐户可以做(破坏,窃取数据库内容,检查配置文件,...)

这个漏洞以某种方式(在特定的上下文中)在这里描述: https://github.com/veracode-research/solr-injection#7-cve-2019-17558-rce-via-velocity-template-by-_s00py

如果禁止从类的实例引用调用静态方法,那么就不可能。

我并不是说一个特定的编程框架比另一个好,只是想做个比较。有一个 Apache Velocity 的港口。NET.在 C # 中,不可能仅仅从实例的引用调用静态方法,这使得这样的利用没有用处:

$input.GetType().GetType("System.IO.FileStream, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089")