Может кто-нибудь объяснить эту тайну Java-области видимости? - PullRequest
4 голосов
/ 21 июня 2011

Одноранговый узел на работе расширил LinkedHashMap и переопределил removeEldestEntry подобно:

import java.util.LinkedHashMap;
import java.util.Map.Entry;

public class CompileTest {
    static class MyMap<K, V> extends LinkedHashMap<K, V> {
        protected boolean removeEldestEntry(Entry<K, V> eldest) {
            return true;
        }
    }
}

Обратите внимание, что класс параметров Entry, а не Map.Entry.
Eclipse сгенерировал сигнатуру метода,IntelliJ показывает ошибку, жалуется, что Entry имеет частный доступ в java.util.LinkedHashMap, и предпочитает Map.Entry.Но это все равно компилируется в любом случае.

Я написал меньший пример для эксперимента:

public class CompileTest {

    static class A{
        public class Inner {
        }

        public void doStuff(Inner a){}
    }

    static class B extends A{
        private class Inner {
        }
    }

    static class C extends B {
        public void doStuff(Inner a) { }
    }
}

Теперь IntelliJ не выдает ошибку, но класс не компилируется.Вот две ситуации, которые кажутся одинаковыми, когда как IDE, так и компилятор, кажется, чередуют поведение и никогда не соглашаются друг с другом.

Может кто-нибудь объяснить это?

Ответы [ 3 ]

4 голосов
/ 21 июня 2011

Из спецификации языка Java - 8.5 Объявления типов элементов

Если класс объявляет тип элемента с определенным именем, то объявление этого типа называется скрытымлюбые доступные декларации типов членов с одинаковыми именами в суперклассах и суперинтерфейсах класса.

Класс наследует от своего прямого суперкласса и прямых суперинтерфейсов все не приватные типы членов суперкласса и суперинтерфейсов, которые оба доступны для кода в классе и не скрыты объявлением в классе.

Мы можем сделать вывод, что:

B.Inner скрывает A.Inner.B не наследует A.Inner.A.Inner не является членом (8.2) типа B.C не может наследовать A.Inner от B.C не может наследовать B.Inner от B, потому что это личное.

Следовательно, C не имеет типа члена Inner.Предположим, что во вложенных областях действия C нет другого типа Inner (внешний класс; модуль компиляции; пакет), тогда имя типа Inner не может быть разрешено.

javac пытается сообщить о более подробной ошибке,но это только предположение, по вашему желанию.Даже лучшее сообщение об ошибке, вероятно, должно включать все вышеприведенные объяснения.

В 1-м примере о Entry оператор import объявляет Entry во всей области действия модуля компиляции (т.е. в файле java), поэтому Entry определяется как Map.Entry

IntelliJ 10.5 не жалуется на 1-й пример;видимо ошибка была исправлена.Это все еще неправильно на втором примере.

В private есть что-то смешное.Почему спецификация явно исключает private членов, хотя она уже требует, чтобы члены были "доступны"?На самом деле B.Inner доступно во всем теле CompileTest (6.6.1), включая C.C может иметь doStuff(B.Inner), и он будет компилироваться.

Вероятно, поэтому IntelliJ облажался во втором примере;он считает, что B.Inner доступен для C, поэтому C наследует B.Inner.Он упустил из виду дополнительное исключение private членов в наследстве.

Это выявляет 2 противоречивых мнения о сфере действия private декларации.Представление № 1 хочет, чтобы область действия была непосредственным включающим классом, представление № 2 хочет, чтобы это был класс включения верхнего уровня.# 2 менее известен программистам, они были бы удивлены, узнав, что C может получить доступ к B.Inner.№ 2 можно обосновать следующим образом: они все равно находятся в одном файле, поэтому нет необходимости в инкапсуляции, мы никого не защищаем, запрещая доступ;разрешение доступа более удобно в большинстве случаев.

Представление № 2, вероятно, также утверждало, что C должно наследовать B.Inner - что может пойти не так?Интересно, что # 2 выигрывает в общем правиле контроля доступа, но проигрывает в правиле наследования.

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

1 голос
/ 21 июня 2011

Это потому, что Entry и Map.Entry - это два разных класса.Первый имеет область действия по умолчанию и определен в HashMap , последний - открытый интерфейс .Таким образом, вы используете закрытый параметр в методе с более широкой областью действия.Это компилируется, но, вероятно, не ваше намерение.

0 голосов
/ 21 июня 2011

Вам просто нужно указать, на какой «Внутренний» вы ссылаетесь. IDE - это всего лишь инструмент, который поможет вам; компилятор всегда является окончательной ссылкой.

...
static class C extends B {

    public void doStuff(A.Inner a) {
    }
}
...

Чтобы уточнить, вы можете получить то же самое «тонкое / странное» поведение, используя стандартные переменные. Частное определение скрывает публичное определение.

public class CompileTest {

    static class A {

        public static Object foo;

    }

    static class B extends A {

        private static Object foo;

    }

    static class C extends B {

        public void doStuff() {
            foo = new Object(); // Will not compile - 'foo' is not visible
            A.foo = new Object(); // Works
        }
    }
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...