Подтвердить что ты не робот

Почему я не могу "статический импорт" метод "равен" в Java?

Мне нравится использовать этот метод здесь:

org.apache.commons.lang.ObjectUtils.equals(Object object1, Object object2)

Единственный недостаток (по сравнению с Google Guava, например) заключается в том, что я не могу статически импортировать этот метод. То есть это бесполезно:

import static org.apache.commons.lang.ObjectUtils.equals;

... поскольку мой компилятор Eclipse не будет правильно связывать этот метод при записи

equals(obj1, obj2);

Ошибка:

Метод equals (Object) в типе Object не применим для аргументов (...,...)

Почему? Является ли мой статически импортированный метод неприменимым, если есть метод с тем же именем (но не с той же подписью) в любом из супер типов? Формально ли это указано в JLS? Или проблема с компилятором Eclipse?

UPDATE

Это также не работает:

import static org.apache.commons.lang.ObjectUtils.defaultIfNull;

public class Test {
  void test() {
    defaultIfNull(null, null);
    // ^^ compilation error here
  }

  void defaultIfNull() {
  }
}

Сообщение об ошибке javac:

Test.java:5: defaultIfNull() in Test cannot be applied to (<nulltype>,<nulltype>)
defaultIfNull(null, null);
    ^
1 error
4b9b3361

Ответ 1

JLS 15.12.1. определяет две причины, почему метод может быть "в области видимости":

  • "... существует объявляющее объявление типа, в котором этот метод является членом"
  • "... из-за одного или нескольких одностатических импорта..."

Теперь к неожиданному результату способствуют два фактора:

  • В этот момент учитывается только имя метода, подписи появляются позже.
  • Две альтернативы, упомянутые выше, связаны с "в противном случае". В первом случае мы заканчиваем тем, что видим в охватывающем классе видимого метода. Во втором случае мы используем статический импорт.

Это "в противном случае" подразумевает, что область поиска ограничивается только попыткой любой из двух ветвей. Сначала мы должны решить, будем ли мы искать закрытый тип или использовать статический импорт. Тип приложения имеет более высокий приоритет, мы находим метод правильного имени (Test.defaultIfNull()), поиск заканчивается здесь. Когда позже мы обнаружим, что этот метод несовместим, не возникает попытки статического импорта.

В JLS ситуация не редкость, другие вопросы поиска методов организованы поэтапно, где частичное совпадение в одной фазе может препятствовать поиску лучшего совпадения в последующей фазе. Еще один пример этой концепции - согласование с фиксированной и сопоставленной переменными. Эффект во всех случаях заключается в том, что компиляторы не выполняют поиск по всему возможному пространству решения, но после принятия определенных решений целые ветки отключены и никогда не посещаются.

Эмпирическое правило может быть выведено из вышеизложенного: перегрузка может выбирать только среди методов одинаковой иерархии типов, она не может выбирать между методами типов, не связанных с наследованием.

Ответ 2

Фактическое столкновение с Object.equals(). Все классы наследуются от Object и поэтому имеют метод Object.equals(), который приводит к этому столкновению.

Вы импортируете по имени, а не по подписке. Из-за этого вы фактически не можете импортировать статический метод с именем equals. Вернее, вы можете импортировать его, но не использовать его. Я согласен с тем, что это должно работать.

(Сделал мои комментарии своим собственным ответом.)

Ответ 3

В соответствии со спецификацией языка Java

  • Если одностатическая импортная декларация импортирует член, простой     name - n, а блок компиляции также содержит однотипный импорт     объявление, которое импортирует тип, простым именем которого является n, время компиляции     возникает ошибка. (Эта ошибка возникает, даже если обе декларации относятся к     тот же тип, на том основании, что он вводит в заблуждение использование двух разных     механизмы для избыточного импорта одного и того же типа.)
  • Если одностатическая импортная декларация импортирует член, простой      name - n, а блок компиляции также объявляет тип верхнего уровня      простейшим именем которого является n, возникает ошибка времени компиляции.

Итак, в вашем случае упомянутая выше точка 2 - причина, по которой вы получаете ошибку времени компиляции. поэтому даже если сигнатуры методов различны, если имена одинаковы, это ошибка времени компиляции.

статический импорт JSR и JLS

Ответ 4

Я сделал несколько тестов. Первое, что я заметил, это то, что вам нужно только один статический оператор импорта для нескольких методов с тем же именем.

public class EqualsClass {
  public static boolean equals(Object o1, Object o2) {
    return o1 == null ? o2 == null : o1.equals(o2);
  }

  public static boolean equals(Object o1, Object o2, Object o3) {
    return equals(o1, o2) && equals(o2, o3);
  }
}

import static mypackage.EqualsClass.equals;

public class TestClass {
  public static void main() {
    Object o1 = new Object();
    Object o2 = new Object();

    equals(o1, o2); // Compiles - static context

    Object o3 = new Object();

    equals(o1, o2, o3); // No extra static import required
  }

Затем я заметил, что он не работает в контексте экземпляра:

  public void someInstanceMethod() {
    Object o1 = new Object();
    Object o2 = new Object();

    equals(o1, o2); // Does not compile - instance context

    Object o3 = new Object();

    equals(o1, o2, o3); // As expected does not compile
  }

}

Но тогда, если я скрою статический импорт с помощью собственного статического метода класса:

public static boolean equals(Object o1, Object o2) {
  return EqualsClass.equals(o1, o2); // Compiles
}

public void someInstanceMethod() {
  equals(new Object(), new Object()); // Compiles!!
  equals(new Object(), new Object(), new Object()); // Doesn't compile!
}

Тот факт, что он работает в статическом контексте, имеет для меня разумный смысл. Однако представляется, что существует значительная разница между разрешением статически импортированного метода и определенным статическим методом класса.

Резюме:

  • Методы с тем же именем, что и метод экземпляра, не могут быть доступны при статическом импорте из контекста экземпляра.
  • Статические методы из одного класса с тем же именем могут быть доступны из контекста экземпляра.
  • Статический импорт дает вам доступ ко всем статическим методам с тем же именем из этого класса, несмотря на подпись (параметры и возвращаемое значение).

Мне было бы интересно увидеть часть JLS или спецификацию компилятора, которая определяет разрешение статического импорта компилятором и как они сбиты локальными методами.

Ответ 5

Я также прочесал JLS3 и не смог найти окончательного ответа.

за 15.12.1, сначала нам нужно определить единственный класс, где объявлен/унаследован метод equals. Здесь у нас есть два класса-кандидата, и у спецификации, похоже, нет правила для разрешения конфликта.

Мы можем исследовать сопоставимую задачу. Простое имя типа может ссылаться как на импортированный тип, так и наследуемый тип (тип члена суперкласса). Джавак выбирает последнего. Вероятно, это связано с процедурой, описанной в 6.5.2, которая придает им наименьший приоритет.

Если применяется тот же принцип, импортированный ObjectUtils.equals должен уступить наследуемому Object.equals. Затем в соответствии с 15.12.2.1 в Object нет метода equals, который потенциально применим к выражению equals(obj1, obj2)

Лично я бы предпочел, чтобы импорт имел приоритет над наследованием, потому что импорт ближе. Он также стабилизирует значение имени. В текущей схеме предположим, что Object не имеет метода equals, выражение equals(obj1, obj2) относится к ObjectUtils.equals; теперь предположим, что Object добавляет метод equals, абсолютно невинный ход, внезапно подкласс не компилируется. Еще худший сценарий: новый метод equals имеет совместимую подпись; подкласс все еще компилируется, но значение выражения молча изменяется.

Ответ 6

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

package test;

public class Foo 
{
    public static void equal(Object o1)
    {
        System.out.println("Foo.equal Object");
    }   

    public static void equal(Integer o1)
    {
        System.out.println("Foo.equal Integer");
    }   
}

package test;

public class Bar 
{
    public static void equal(Number o1)
    {
        System.out.println("Bar.equal Number");
    }   
}

import static test.Foo.equal;
import static test.Bar.equal;

public static void main(String args[]) throws Exception
{
    equal((Object)null);
    equal((Number)null);
    equal((Integer)null);
}

Output: 
Foo.equal Object
Bar.equal Number
Foo.equal Integer

Это также может быть связано. Метод во внутреннем классе "скрывает" статический метод во внешнем классе с другой сигнатурой.

http://ideone.com/pWUf1

Похоже, что в компиляторе есть разные места, где искать методы и проверяет их один за другим, но только поиск по имени приводит к преждевременному завершению поиска.

Ответ 7

Это метод столкновения с java.awt, вам нужно ссылаться на пакет следующим образом:

 ObjectUtils.equals(a, b);

Ответ 8

На самом деле, я думаю, что это скорее проблема Eclipse, чем любая другая вещь. Если вы используете перегруженную версию equals(), которая получает два аргумента, не должно быть столкновений с объектом Object.equals() по умолчанию.

В Eclipse есть несколько трюков, которые вы можете использовать, чтобы распознать статический импорт:

1 - добавьте статический тип для организации импорта Перейдите к:

Window > Preferences > Java > Code Style > Organize Imports 

затем нажмите "Создать статический", затем "Типы", затем выберите свой класс (в данном случае org.apache.commons.lang.ObjectUtils)

Пока на панели "Организовать импорт" отмените выбор

"Do not create imports for types starting with lowercase letter" 

(не забывайте, это важно)

2 - добавьте этот тип в Content Assist Перейдите к:

Window > Preferences > Java > Editor > Content Assist Favorites

затем нажмите "Новый тип", затем выберите свой класс (в этом случае снова org.apache.commons.lang.ObjectUtils)

Теперь с этим вы должны иметь возможность Ctrl + Space в любом месте вашего метода и получить метод "equals (Object, Object)" как возможный контент. Если вы выберете этот метод, Eclipse должен автоматически вставить статический импорт для равных.