Почему я не могу "статически импортировать" метод "равно" в Java? - PullRequest
28 голосов
/ 25 октября 2011

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

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?

ОБНОВЛЕНИЕ

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

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

Ответы [ 8 ]

16 голосов
/ 25 октября 2011

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

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

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

15 голосов
/ 04 ноября 2011

Согласно спецификации языка Java

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

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

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

5 голосов
/ 08 ноября 2011

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

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 голосов
/ 25 октября 2011

Я также прочесал 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 имеет совместимую подпись;подкласс все еще компилируется, но значение выражения молча меняется.

3 голосов
/ 26 февраля 2015

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

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

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

  1. На данный момент только имяметод считается, подписи приходят позже.
  2. Две упомянутые выше альтернативы связаны с «иначе».В первом случае мы в конечном итоге заглядываем во вложенный класс метода visible.Во втором случае мы используем статический импорт.

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

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

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

2 голосов
/ 26 октября 2011

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

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

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

0 голосов
/ 25 октября 2011

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

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

1 - Добавить статический тип в Organize Imports. Перейти к:

Window > Preferences > Java > Code Style > Organize Imports 

, затем нажмите «New Static», затем «Types», затем выберите свойкласс (в данном случае 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 + Пробел в любом месте вашего метода и получить метод "equals (Object, Object)" в качестве возможного содержимого.Если вы выберете этот метод, Eclipse должен автоматически вставить статический импорт для equals.

0 голосов
/ 25 октября 2011

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

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