为什么通过实例调用静态方法不会给 Java 编译器带来错误?
题
我相信你们都知道我的意思 - 代码例如:
Thread thread = new Thread();
int activeCount = thread.activeCount();
引发编译器警告。为什么不是错误呢?
编辑:
需要明确的是:问题与线程无关。我意识到在讨论这个问题时经常会给出线程的例子,因为它们可能会把事情搞砸。但真正的问题是这样的用法是 总是 胡言乱语,你不能(有能力)写出这样的电话并认真执行。这种类型的方法调用的任何例子都是愚蠢的。这是另一个:
String hello = "hello";
String number123AsString = hello.valueOf(123);
这使得每个 String 实例看起来都带有一个“String valueOf(int i)”方法。
解决方案
基本上我认为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
的执行时间值完全被忽略。
其他提示
为什么会出错?该实例可以访问所有静态方法。静态方法无法更改实例的状态(尝试 编译错误)。
您提供的众所周知示例的问题非常特定于线程,而不是静态方法调用。看起来你正在获取 thread
引用的线程的 activeCount()
,但你真的得到了调用线程的计数。这是程序员正在制作的逻辑错误。在这种情况下,编译器发出警告是适当的。由你来注意警告并修复你的代码。
由于已经存在的所有代码,它们不能再成为错误。
我和你在一起,这应该是一个错误。 也许应该有一个选项/配置文件供编译器将某些警告升级为错误。
更新:当他们在1.4中引入 assert 关键字时,它与旧代码存在类似的潜在兼容性问题,他们将其设为仅在您明确将源模式设置为”1.4“时可用。我想在新的源模式“java 7”中可能会出现错误。但我怀疑他们会这样做,考虑到它会造成的所有麻烦。正如其他人所指出的那样,并不是必须阻止您编写令人困惑的代码。对Java的语言更改应限制在此时严格必要。
简短回答 - 语言允许,因此不是错误。
可能同样的逻辑使得这不是一个错误:
public class X
{
public static void foo()
{
}
public void bar()
{
foo(); // no need to do X.foo();
}
}
从编译器的角度来看,非常重要的是它能够解析符号。在静态方法的情况下,它需要知道要查找的类 - 因为它不与任何特定对象相关联。 Java的设计者显然决定,既然他们可以确定一个对象的类,他们也可以从该对象的任何实例中解析该对象的任何静态方法的类。他们选择允许这种 - 或许,通过@TofuBeer的观察 - 为程序员提供一些便利。其他语言设计师做出了不同的选择。我可能会陷入后一阵营,但这对我来说并不是什么大不了的事。我可能会允许使用@TofuBeer提到的用法,但允许我在不允许从实例变量访问时的位置是不太可行的。
这不是一个错误,因为它是规范的一部分,但你显然在询问我们都可以猜到的基本原理。
我的猜测是,它的来源实际上是允许类中的方法在没有麻烦的情况下在同一个类中调用静态方法。由于调用x()是合法的(即使没有自类名),调用this.x()也应该是合法的,因此通过任何对象进行调用也是合法的。
如果不改变状态,这也有助于鼓励用户将私有函数转换为静态函数。
此外,编译器通常会尝试避免在无法导致直接错误的情况下声明错误。由于静态方法不会更改状态或关注调用对象,因此它不会导致实际错误(只是混淆)。警告就足够了。
实例变量引用的目的只是提供包含静态的类型。如果查看调用静态的字节代码,则通过instance.staticMethod或EnclosingClass.staticMethod生成相同的调用静态方法字节码。不会出现对实例的引用。
答案也是为什么它在那里,它就是。只要你使用这门课。而不是通过实例,你将有助于避免将来混淆。
您可以在IDE中更改它(在Eclipse首选项中 - > Java - >编译器 - >错误/警告)
没有它的选择。在java(与许多其他语言一样)中,您可以通过其类名或该类的实例对象访问类的所有静态成员。这取决于您和您的案例和软件解决方案,您应该使用哪种解决方案为您提供更多可读性。
我只是考虑这个:
instanceVar.staticMethod();
是这个的简写:
instanceVar.getClass().staticMethod();
如果你总是必须这样做:
SomeClass.staticMethod();
那么您将无法利用静态方法的继承。
也就是说,通过实例调用静态方法,您不需要在编译时知道实例是什么具体类,只需知道它在继承链中的某个位置实现了 staticMethod() 即可。
编辑:这个答案是错误的。详情请参阅评论。