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


76

我确定你们都知道我的意思-代码如下:

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

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

编辑:

需要明确的是:问题与线程无关。我意识到在讨论此问题时通常会给出线程示例,因为有可能将它们弄乱。但是,真正的问题在于,这种用法永远都是胡说八道,您不能(适当地)写这样的电话并表达自己的意思。这种方法调用的任何示例都是barmy。这是另一个:

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

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


15
为了扩展您的观点,冗余实例甚至可以为null:String hello = null; hello.valueOf(123);有效!
Thilo 2012年

Answers:


79

基本上,我相信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不允许您使用可能未初始化的变量来调用静态方法,尽管事实上它将要使用的唯一信息是变量的声明类型。这是一个前后矛盾,毫无用处的混乱局面。为什么允许它?

编辑:此编辑是对Clayton答案的回应,声称它允许继承静态方法。没有。静态方法不是多态的。这是一个简短但完整的程序,以证明这一点:

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被完全忽略。


我认为您可能缺少一些东西。看我的帖子。
DJClayworth

1
@DJClayworth:不。这在C#中是允许的,但是someInstanceOfMyClass.doComplexCalculation()不允许。
乔恩·斯基特

5
@Jon:我进行了类似的测试并确认了您的发现。基于此,我同意Java允许从实例var调用静态方法是错误的。无法获得任何功能(就像我最初想的那样),但是增加了混淆或错误的可能性。
克莱顿

b的执行时间值被完全忽略,因为您正在调用静态方法,该方法仅取决于b的类而不是其值。Base类型的所有值都具有相同的方法。您声明了b为基数,因此b是基数,而不是派生数。如果您声明了Derived b,那么它将输出“ Derived.foo()”。
马修(马修)

@Matthew:是的,我知道...我不确定您要提出什么观点。(我个人不会说“所有类型为Base的值都具有相同的方法” –实例根本没有“具有”方法...但是我想我知道您的意思。)
Jon Skeet

15

为什么要出错?该实例可以访问所有静态方法。静态方法不能改变实例的状态(努力一个编译错误)。

您提供的著名示例的问题非常特定于线程,而不是静态方法调用。看来您正在获得activeCount()所指代的线程thread,但是您实际上正在获得调用线程的计数。这是您作为程序员所犯的逻辑错误。在这种情况下,发出警告是编译器应采取的适当措施。由您决定要注意警告并修复代码。

编辑:我意识到该语言的语法使您可以编写误导性代码,但请记住,编译器及其警告也是该语言的一部分。该语言允许您执行编译器认为可疑的操作,但是会向您发出警告,以确保您意识到它可能会引起问题。


9
实例无关。唯一的事情就是变量的声明类型。这是一个可怕的可怕的错误特征,并且是语言设计师IMO的一个错误。
乔恩·斯基特

1
是的,它与实例无关,但是我认为从实例调用静态方法不应该是一个完整的错误。这真的是很糟糕的风格。恕我直言,一个类应该是全部静态方法,或者这些静态方法应该是私有的,但是我不希望编译器强制执行该方法。
比尔蜥蜴

1
@Bill:我认为它甚至在早期都没有发出警告。添加警告是因为它会误导人们。有什么好处的允许了吗?
乔恩·斯基特

4
@Bill:这么说吧-如果Java的没有允许它,有人建议,应该允许的话,将你的反应是什么呢?您真的会赞成吗?还是您会(就像我一样)认为这是一项毫无意义的功能,不必要地导致误导代码?
乔恩·斯基特

2
我不赞成使用“编译时间”的原因-很明显,您正在通过引用查找该方法。检查它是否静态需要多长时间?我认为将其视为语言设计错误会更容易。
乔恩·斯基特

9

由于已经存在所有代码,因此他们不能再使它成为错误。

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

更新:当他们在1.4中引入assert关键字时(与旧代码具有类似的潜在兼容性问题),只有在将源模式显式设置为“ 1.4”的情况下,他们才可用。我想在新的源代码模式“ java 7”中可能会使它出错。但是考虑到这样做会带来的所有麻烦,我怀疑他们会这样做。正如其他人所指出的那样,严格防止阻止您编写混乱的代码并不是必须的。此时,Java的语言更改应仅限于严格必要的范围。


Eclipse编译器中有这样的选项(indirectStatic,help.eclipse.org/help33/index.jsp?topic=/…
PeterŠtibraný09年

1
但是他们在Java 1.4中引入assert关键字时就这样做了。他们为什么不能以同样的方式再做一次?
Hosam Aly

你是对的。对于Assert,您必须将编译器级别专门设置为1.4,否则它仍然会在没有断言的情况下进行编译。我认为1.5批注和泛型的工作原理相同。因此可能会有一个Java 7编译级别,这将使其成为错误。
Thilo


2

可能导致错误的逻辑相同:

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

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

2

从编译器的角度来看,真正重要的是它能够解析符号。对于静态方法,它需要知道要查找的类-因为它不与任何特定对象关联。Java的设计师显然决定,因为他们可以确定对象的类,所以他们也可以从对象的任何实例中为该对象解析任何静态方法的类。他们选择允许这样做-也许被@TofuBeer的观察所左右-为程序员提供了一些便利。其他语言设计师做出了不同的选择。我可能会进入后一个阵营,但这对我来说没什么大不了的。我可能会允许@TofuBeer提到的用法,


2

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

我的猜测是,其来源实际上是允许类中的方法调用同一类中的静态方法而没有麻烦。由于调用x()是合法的(即使没有自类名),因此调用this.x()也应该合法,因此通过任何对象进行的调用也都合法。

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

此外,编译器通常在无法避免可能导致直接错误的情况下尽量避免声明错误。由于静态方法不会更改状态或在乎调用对象,因此它不会导致实际错误(仅引起混淆)。一个警告就足够了。


2
@Uri:无需指定类名就可以很容易地使其在当前类中可用。C#对此进行了管理,而Java编译器设法说明了这种区别(因为在这种情况下您不会收到警告),那么为什么要这样指定呢?
乔恩·斯基特

2

实例变量引用的目的仅在于提供包含静态变量的类型。如果您查看通过instance.staticMethod或EnclosingClass.staticMethod调用静态方法的字节代码,则会生成相同的调用静态方法字节代码。不会出现对该实例的引用。

为什么它也在那里的答案也是如此。只要您使用该类。而不是通过实例,这将有助于避免将来造成混乱。


1

可能您可以在IDE中对其进行更改(在Eclipse Preferences-> Java-> Compiler-> Errors / Warnings中)


0

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


0

这是一个很老的话题,但是仍然是最新的,并且如今令人惊讶地带来了更大的影响。正如乔恩(Jon)所述,这可能只是Java设计师一开始就犯的错误。但是我无法想象它会对安全产生影响。

许多编码人员都知道Apache Velocity,灵活而强大的模板引擎。它是如此强大,以至于它可以为模板提供一组命名对象-严格被认为是编程语言(最初是Java)中的对象。可以像使用编程语言一样从模板内访问这些对象,例如,Java的String实例可以与其所有公共字段,属性和方法一起使用。

$input.isEmpty()

其中inputString,直接通过JVM运行,并向Velocity解析器的输出返回truefalse)。到现在为止还挺好。

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

$input.getClass()

获取String Class的实例。

并且使用此引用,他们还可以在此上调用静态方法forName(String)

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

使用任何类名并将其用于Web服务器的帐户可以执行的任何操作(破坏,窃取数据库内容,检查配置文件等)

对此漏洞进行了某种描述(在特定上下文中):https//github.com/veracode-research/solr-injection#7-cve-2019-17558-rce-via-velocity-template-by-_s00py

如果禁止从对类实例的引用中调用静态方法,那将是不可能的。

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

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

-9

我只是考虑一下:

instanceVar.staticMethod();

简而言之:

instanceVar.getClass().staticMethod();

如果您始终必须这样做:

SomeClass.staticMethod();

那么您将无法利用继承的静态方法。

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

编辑:这个答案是错误的。有关详细信息,请参见评论。


1
很好,这是为什么不应该允许它的另一个原因-因为您也被误导了。它不会像您想要的那样起作用。在您的示例中,它将使用声明的类型“ instanceVar”。哎呀,instanceVar的值甚至可以为null。
乔恩·斯基特

1
@Clayton:是的,但是没有调用getClass()。我将用一个示例来编辑答案。
乔恩·斯基特

3
(如果它确实调用了getClass(),则无法再调用staticMethod(),因为这不是Class <T>上的方法。)
Jon Skeet

5
@Jon:我要把它留在这里。我从错误中学到的东西和从其他东西学到的东西一样多,所以它可能会帮助别人看到这一点。谢谢你的帮助。
克莱顿

3
@Clayton:您可能想制作此社区Wiki,以便我们可以对其进行投票,而不会对您造成损失。
比尔蜥蜴
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.