Почему вызов статического метода в качестве экземпляра не является ошибкой для компилятора Java? - PullRequest
75 голосов
/ 04 марта 2009

Я уверен, что вы все знаете, какое поведение я имею в виду - такой код:

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

вызывает предупреждение компилятора. Почему это не ошибка?

EDIT:

Чтобы было ясно: вопрос не имеет ничего общего с потоками. Я понимаю, что при обсуждении этого часто приводятся примеры из-за того, что с ними действительно можно что-то испортить. Но на самом деле проблема в том, что такое использование всегда бессмыслица, и вы не можете (компетентно) написать такой вызов и подразумевать его. Любой пример этого типа вызова метода был бы ужасным. Вот еще один:

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

Что выглядит так, как будто каждый экземпляр String поставляется с методом "String valueOf (int i)".

Ответы [ 11 ]

77 голосов
/ 04 марта 2009

По сути, я считаю, что разработчики 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 полностью игнорируется.

13 голосов
/ 04 марта 2009

Почему это должно быть ошибкой? Экземпляр имеет доступ ко всем статическим методам. Статические методы не могут изменить состояние экземпляра (при попытке это ошибка компиляции).

Проблема с хорошо известным примером, который вы приводите, очень специфична для потоков , а не для вызовов статических методов. Выглядит так, как будто вы получаете activeCount() для потока, на который ссылается thread, но вы действительно получаете счет для вызывающего потока. Это логическая ошибка, которую вы, как программист, делаете. В этом случае компилятору следует сделать предупреждение. Вы должны прислушаться к предупреждению и исправить свой код.

РЕДАКТИРОВАТЬ: я понимаю, что синтаксис языка - это то, что позволяет писать неверный код, но помните, что компилятор и его предупреждения также являются частью языка. Язык позволяет вам делать то, что компилятор считает сомнительным, но он дает вам предупреждение, чтобы вы знали, что это может вызвать проблемы.

9 голосов
/ 04 марта 2009

Они больше не могут сделать это ошибкой из-за всего кода, который уже существует.

Я с вами на том, что это должно быть ошибкой. Возможно, должна быть опция / профиль для компилятора, чтобы обновить некоторые предупреждения до ошибок.

Обновление: Когда в 1.4 было введено ключевое слово assert , которое имеет аналогичные потенциальные проблемы совместимости со старым кодом, они сделали его доступным, только если вы явно задали источник режим «1,4» . Я полагаю, что это может сделать ошибку в новом исходном режиме "Java 7". Но я сомневаюсь, что они это сделают, учитывая, что это вызовет все хлопоты. Как уже отмечали другие, не обязательно запрещать вам вводить в заблуждение код. И языковые изменения в Java должны быть ограничены строго необходимыми на данный момент.

6 голосов
/ 04 марта 2009

Краткий ответ - язык это позволяет, так что это не ошибка.

2 голосов
/ 04 марта 2009

Цель ссылки на переменную экземпляра состоит только в том, чтобы предоставить тип, который содержит статический элемент. Если вы посмотрите на байт-код, вызывающий статический объект через instance.staticMethod или EnclosingClass.staticMethod, выдает тот же байт-код статического метода вызова. Ссылка на экземпляр не отображается.

Ответ так же, почему он там, ну, это просто так. Пока вы используете класс. а не через экземпляр вы поможете избежать путаницы в будущем.

2 голосов
/ 04 марта 2009

Это не ошибка, потому что это часть спецификации, но вы, очевидно, спрашиваете об обосновании, о котором мы все можем догадаться.

Я предполагаю, что источником этого является то, что метод в классе может вызывать статический метод в том же классе без хлопот. Поскольку вызов x () является допустимым (даже без имени собственного класса), вызов this.x () также должен быть допустимым, и поэтому вызов через любой объект также стал допустимым.

Это также помогает пользователям превратить частные функции в статические, если они не меняют состояние.

Кроме того, компиляторы, как правило, стараются избегать объявления ошибок, когда нет возможности привести к прямой ошибке. Так как статический метод не изменяет состояние и не заботится о вызывающем объекте, он не вызывает фактическую ошибку (просто путаницу), позволяющую это сделать. Достаточно предупреждения.

2 голосов
/ 04 марта 2009

Действительно, с точки зрения компилятора, важно то, что он способен разрешать символы. В случае статического метода ему нужно знать, какой класс ему искать, поскольку он не связан ни с каким конкретным объектом. Очевидно, что разработчики Java решили, что, поскольку они могут определить класс объекта, они также могут разрешить класс любого статического метода для этого объекта из любого экземпляра объекта. Они решили позволить это - возможно, под влиянием наблюдения @ TofuBeer - чтобы дать программисту некоторое удобство. Другие дизайнеры языка сделали другой выбор. Возможно, я бы попал в последний лагерь, но для меня это не так уж важно. Я, вероятно, разрешил бы использование, которое упоминает @TofuBeer, но, допустив это, моя позиция о запрете доступа из переменной экземпляра менее вероятна.

2 голосов
/ 04 марта 2009

Вероятно, для той же логики, которая делает это не ошибкой:

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

    public void bar()
    {
        foo(); // no need to do X.foo();
    }
}
1 голос
/ 04 марта 2009

Возможно, вы можете изменить его в своей IDE (в Eclipse Preferences -> Java -> Compiler -> Errors / Warnings)

0 голосов
/ 04 марта 2009

Я просто считаю это:

instanceVar.staticMethod();

для краткости:

instanceVar.getClass().staticMethod();

Если вам всегда приходилось делать это:

SomeClass.staticMethod();

тогда вы не сможете использовать наследование для статических методов.

То есть, вызывая статический метод через экземпляр, вам не нужно знать, каким конкретным классом является экземпляр во время компиляции, только чтобы он реализовал staticMethod () где-то в цепочке наследования.

РЕДАКТИРОВАТЬ: Этот ответ является неправильным. Подробности смотрите в комментариях.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...